В связи с нехваткой времени - crash tutorial. Сорри. :о(
Расчитан на продвинутых новичков, которые:
a) уже умеют создавать проекты
b) имеют представление о том, что такое есть Device Context
c) что-нить рисовали (линия, прямоугольник и тд) в функциях типа OnPaint, OnDraw
Итак, для эксперимента нам понадобится какая-нить студия (я использовал 6ю)
Для "облегчения" кода выбираем MFC exe. У кого аллергия на MFC - могут сделать
тоже самое с API функциями, необходимо только добавить хендлы в качестве первого
параметра.
Создаём проект (я назвал его "Test"), тип прилоложения (MDI, SDI, Dialog) - SDI.
Единственная функция которая нас будет интересовать - реакция на сообщение винды
WM_PAINT. Принимаем все настройки проекта по умолчанию.
Если вы выбрали в качестве имени проекта "Test", то мастер создал класс CTestView.
С виртуальной функцией OnDraw.
функция выглядит примерно так:
void CTestView::OnDraw(CDC* pDC)
{
CTestDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
}
Поскольку мы не собираемся работать с документом, то код можно удалить:
void CTestView::OnDraw(CDC* pDC)
{
}
Нарисуем какой-нить объект, который в дальнейшем мы хотим обрезать, я
выбрал простую линию.
void CTestView::OnDraw(CDC* pDC)
{
// сохраним оригинальный ДК
int nOrigDC = pDC->SaveDC();
// это будет цвет нашего фона
COLORREF crBk = RGB(0xAB,0xCD,0xEF);
// заполним им клиентскую область
CRect rcBk;
GetClientRect(rcBk);
pDC->FillSolidRect(rcBk,crBk);
// нарисуем жёлтую линию в основном ДК
// по диагонали, от левого верхнего угла к правому нижнему
CPen p(PS_SOLID,4,RGB(0xff,0xff,0x00));
pDC->SelectObject(p);
pDC->MoveTo(0,0);
pDC->LineTo(rcBk.right,rcBk.bottom);
//***************************************
// сюда будет вставляться дальнейший код
//***************************************
// восстановим сохранённый ДК
pDC->RestoreDC(nOrigDC);
}
Всё, самая тяжёлая - подготовительная часть закончена. Этот код не будет изменяться,
поэтому я тоже не буду его повторять, в целях экономии. Компилируем. Смотрим.
Теперь формулируем задание - показать только часть рисунка - в данном случае линии.
Я выбрал для "показа" прямоугольное окошко с рамкой в 50 точек. Т.е. мы будем
видеть линию только в пределах выбранного прямоугольника, всё остальное - рамка -
заполняется цветом фона. Говоря другими словами - сделаем маску, через которую будет
видна часть рисунка. В качестве задания предлагаю самостоятельно поиграться с масками
более сложной формы, используя чёрно-белые рисунки.
Итак - определяю цвета (здесь нет никаких правил, целиком ваше желание, фантазия и
способности
):
Всё что в маске белого цвета должно остаться без изменения, т.е. прозрачная область.
Чёрный цвет маски - определяет нивидимую часть рисунка, или просто фон.
Таким образом моя маска выглядит как чёрный прямоугольник, размером с клиентскую
область, в центре которого находится белый прямоугольник.
// зададим прямоугольник - я использовал отступ в 50 точек
CRect rc(rcBk.left+50, rcBk.top+50,rcBk.right-50,
rcBk.bottom-50);
Для создания маски воспользуемся ещё одним ДевайсКонтекстом, совместимым (compatible)
с рабочим, который потом трансформируем в рабочий ДК.
CDC dcMem;
dcMem.CreateCompatibleDC(pDC);
Первоначально у нового ДК нет размеров, точнее он имеет нулевые размеры
создадим их, взяв за основу ВМР размером с клиентскую область
CBitmap bmpMem;
bmpMem.CreateCompatibleBitmap(pDC,rcBk.Width(),rcBk.Height());
dcMem.SelectObject(&bmpMem);
Нарисуем в новом ДК прямоугольник белым цветом
dcMem.FillSolidRect(rc, RGB(0xff,0xff,0xff));
Теперь осталось совместить новый контекст с рабочим.
Для этой цели в винде предусмотрена функция BitBlt (Bit Block Transfer).
Которая получает след параметры:
координаты лево, верх, (х у) ширина, высота - цели
в нашем случае это вся клиетская область т.е. 0,0, rcBk.Width(), rcBk.Height(),
(я для наглядности использовал вызов функцмй Width() и Height(), в реальных проектах
лучше использовать разницу соответствующих координат - работает быстрее, чем вызов функции).
указатель на ДК источника (откуда будет производится трансформация)
координаты лево, верх, (х у) источника
и последний самый загадочный параметр т.н. ROP-код (Raster operation code), который мы
разберём подробнее несколько позже.
Итак, трансформируем ДК с маской в основной ДК
pDC->BitBlt(0, 0, rcBk.Width(), rcBk.Height(),
&dcMem, 0, 0, 0x00B8074A);
Компилируем. Смотрим. Всё бы ничего, да только рамка вокруг получилась белая.
Давайте исправим этот недостаток. Для этого достаточно выбрать в рабочем контексте
кисть с цветом фона. Причём сделать это надо до вызова BitBlt
CBrush br(BS_SOLID, crBk);
pDC->SelectObject(br);
pDC->BitBlt(0, 0, rcBk.Width(), rcBk.Height(),
&dcMem, 0, 0, 0x00B8074A);
Окончательная версия выглядит так:
void CTestView::OnDraw(CDC* pDC)
{
// сохраним оригинальный ДК
int nOrigDC = pDC->SaveDC();
// это будет цвет нашего фона
COLORREF crBk = RGB(0xab,0xcd,0xef);
// заполним им клиентскую область
CRect rcBk;
GetClientRect(rcBk);
pDC->FillSolidRect(rcBk,crBk);
// нарисуем жёлтую линию тлщиной 4 точки в основном ДК
// по диагонали, от левого верхнего угла к правому нижнему
CPen p(PS_SOLID,4,RGB(0xff,0xff,0x00));
pDC->SelectObject(p);
pDC->MoveTo(0,0);
pDC->LineTo(rcBk.right,rcBk.bottom);
// зададим прямоугольник - я использовал отступ в 50 точек
CRect rc(rcBk.left+50, rcBk.top+50,rcBk.right-50,
rcBk.bottom-50);
CDC dcMem;
dcMem.CreateCompatibleDC(pDC);
CBitmap bmpMem;
bmpMem.CreateCompatibleBitmap(pDC,rcBk.Width(),
rcBk.Height());
dcMem.SelectObject(&bmpMem);
dcMem.FillSolidRect(rc, RGB(0xff,0xff,0xff));
CBrush br(BS_SOLID, crBk);
pDC->SelectObject(br);
pDC->BitBlt(0, 0, rcBk.Width(), rcBk.Height(),
&dcMem, 0, 0, 0x00B8074A);
// восстановим сохранённый ДК
pDC->RestoreDC(nOrigDC);
}
Компилируем. Смотрим.
Я немного изменил код и вынес механизм маски в отдельные функции (MFC и API версии)
void PaintMask(CDC *pDC, CRect rcBk, CRect rc, COLORREF crBk)
{
CDC dcMem;
dcMem.CreateCompatibleDC(pDC);
CBitmap bmpMem;
bmpMem.CreateCompatibleBitmap(pDC,rcBk.Width(),
rcBk.Height());
dcMem.SelectObject(&bmpMem);
dcMem.FillSolidRect(rc, RGB(0xff,0xff,0xff));
CBrush br(BS_SOLID, crBk);
pDC->SelectObject(br);
pDC->BitBlt(0, 0, rcBk.Width(), rcBk.Height(),
&dcMem, 0, 0, 0x00B8074A);
}
void PaintMask(HDC hdcOrig, LPRECT lpRectBk, LPRECT lpRect,
COLORREF crBk)
{
int nWidthBk = lpRectBk->right-lpRectBk->left;
int nHeightBk = lpRectBk->bottom-lpRectBk->top;
HDC hdcMem = ::CreateCompatibleDC(hdcOrig);
HBITMAP hbmMem = ::CreateCompatibleBitmap(hdcOrig,
nWidthBk, nHeightBk);
::SelectObject(hdcMem, hbmMem);
::SetBkColor(hdcMem, RGB(0xff,0xff,0xff));
::ExtTextOut(hdcMem, 0, 0, ETO_OPAQUE, lpRect, NULL,
0, NULL);
HBRUSH hBrush, hBrushOrig;
hBrush = ::CreateSolidBrush(crBk);
hBrushOrig = (HBRUSH)::SelectObject(hdcOrig,hBrush);
::BitBlt(hdcOrig, 0,0, nWidthBk, nHeightBk, hdcMem,
0,0, 0x00B8074A);
::SelectObject(hdcOrig, hBrushOrig);
::DeleteObject(hBrush);
::DeleteObject(hbmMem);
::DeleteDC(hdcMem);
}
void CTestView::OnDraw(CDC* pDC)
{
// сохраним оригинальный ДК
int nOrigDC = pDC->SaveDC();
// это будет цвет нашего фона
COLORREF crBk = RGB(0xab,0xcd,0xef);
// заполним им клиентскую область
CRect rcBk;
GetClientRect(rcBk);
pDC->FillSolidRect(rcBk,crBk);
// нарисуем жёлтую линию в основном ДК
// по диагонали, от левого верхнего угла к правому нижнему
CPen p(PS_SOLID,4,RGB(0xff,0xff,0x00));
pDC->SelectObject(p);
pDC->MoveTo(0,0);
pDC->LineTo(rcBk.right,rcBk.bottom);
// зададим прямоугольник - я использовал отступ в 50 точек
CRect rc(rcBk.left+50, rcBk.top+50,rcBk.right-50,
rcBk.bottom-50);
// Тест MFC
//PaintMask(pDC, rcBk, rc, crBk);
/**/ // <- для теста MFC-версии удалите слэш после звёздочки
// Тест API
RECT rectBk, rect;
rectBk.left = rcBk.left;
rectBk.top = rcBk.top;
rectBk.right = rcBk.right;
rectBk.bottom = rcBk.bottom;
rect.left = rc.left;
rect.top = rc.top;
rect.right = rc.right;
rect.bottom = rc.bottom;
PaintMask(pDC->GetSafeHdc(), &rectBk, &rect, crBk);
/**/
// восстановим сохранённый ДК
pDC->RestoreDC(nOrigDC);
}
Вот так это выглядит на практике, а теперь немного теории. Сразу скажу,что подробная
инфа хорошо описана в литературе (Петцольд) и MSDN. Ключевые слова: "Ternary Raster Operations"
"ROP3". Посмотрите так-же описание и использование функций BitBlt, PatBlt, и StretchBlt.
"Итак, ближе к телу". Как мы увиедли, всё работает довольно просто:
взяли оригинал, создали маску, выбрали кисть и потом всё это смешали. Нетрудно заметить,
что самая сложная часть - смешивание - функция BitBlt. В которой все параметры довольно ясны -
координаты источника, координаты второго ДевайсКонтекста, сам второй ДК.
Загадкой остаётся последний параметр, да ещё его загадочное значение - 0x00B8074A.
Почему 0x00B8074A? А может быть, например, 0x00B80FFF?
Пойдём по порядку. Итак у нас есь три источника информации - оригинал, маска и кисть (Brush)
под кистью в данном случае понимается GDI объект который может содержать цвет и текстуру.
Представим их ввиде слоёв, которые накладываются друг на друга. Каждая точка любого из этих
слоёв, совпадает позиционно с точкой любого другого слоя и представляет собой численное значение
цвета. Биты этих значений "смешиваются" с помощью операций not or and xor. Формулы, такого
смешивания определяем мы, исходя из определённых нами условий.
Сделаем это для приведённого выше примера, но сначала договоримся о терминологии.
Итак оригинальный слой обозначим через D (Destination bitmap), слой кисти обозначим
через Р(Рattern) и слой маски через S (Source bitmap)
Распишем возможные комбинации бит для этих слоёв (для получения кода важен порядок записи
- требования винды):
P S D
-----
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
С этого рисунка вы будете всегда начинать ваши эксперименты, он остаётся постоянным. Теперь
определяем алгоритм. Я выбрал в качестве "прозрачного" окна белый цвет, обычно белый цвет
обозначают 1, а чёрный 0. Т.е. я хочу, чтобы на месте тех битов маски (S), которые равны 1
биты оригинала (D) остались неизменёнными. Запишем справа в столбике 1 эти биты:
Шаг 1.
P S D 1
-----
0 0 0
0 0 1
0 1 0 0
0 1 1 1
1 0 0
1 0 1
1 1 0 0
1 1 1 1
Для чёрной области (нулевые биты маски) я определил цвет фона. Т.е. в этих точках должна
оставаться информация со слоя P запишем их пока в столбец 2:
Шаг 2.
P S D 1 2
-----
0 0 0 0
0 0 1 0
0 1 0 0
0 1 1 1
1 0 0 1
1 0 1 1
1 1 0 0
1 1 1 1
Совмеcтим столбец 1 и 2 и прочитаем биты снизу вверх получим 1011 1000 = 0xB8. Таким образом
мы плучили команду (или как она называется в MSDN "boolean function") каким образом наши
биты должны смешиваться, или первую часть (старшее машинное слово - 16 бит) кода растровой
операции. Который представляет собой двойное маш. слово (32 бит).
Остальное не наша забота. Вторая часть представляет собой вспомогательные операции для стековой
машины (подробного описания как это делается я не нашёл) и берётся из таблицы (MSDN "Platform SDK:
Windows GDI" "Ternary Raster Operations").
Для этого находим в левом столбце полученный код 0хВ8 и смотрим чему равно значение
ROP кода - 0x00B8074A.
Для некоторых кодов есть даже свои определённые "именные" константы, напр. SRCCOPY - 0x00CC0020
Попробуйте расписать булевскую функцию и понять, что она делает.
Успехов.
зы Всё писалось буквально со спичками в глазах - так что о замеченных ошибках просьба сообщать
не стесняясь. Единственное, что могу гарантировать - код компилируется.
Буду очень признателен ежели кто-нить из титанов (a la Never) возмётся "причесать"
и перевести это в удобоваримую для начинающих форму.