Программирование на C++ глазами хакера

         

Безбашенные окна


Вы уже научились делать окна на основе простых фигур (овал, прямоугольник) и их сочетания, и теперь нам предстоит узнать, как создать окно совершенно произвольной формы. Такое окно уже невозможно будет сделать парой комбинаций. Тут уже нужны более сложные манипуляции.

На 3.9 представлена картинка с красным фоном. Как сделать окно, которое будет содержать это изображение, а красный цвет сделать прозрачным, чтобы окно приняло форму картинки? Сложно себе представить, как это сделать с помощью комбинирования регионов из простых фигур (хотя и возможно, просто их понадобится много).

3.9. Маска для будущего окна

В WinAPI есть еще регионы типа полигонов, но это не сильно упростит задачу.

Итак, создадим один большой регион, который опишет наше изображение. Код будет универсальным, и вы сможете подставить вместо моей картинки что-то другое. Как мы будем это делать? Очень просто.

Что представляет собой изображение? Это двухмерный массив из точек. Нам нужно взглянуть на каждую строку, как на отдельный элемент региона, т. е. мы будем строить прямоугольный регион по каждой строке, а потом скомбинируем все вместе. Алгоритм будет выглядеть следующим образом:

Сканируем строку и находим первый пиксел, отличный от прозрачного. Это будет начало нашего прямоугольника (координата X1).

Сканируем остаток строки, чтобы найти границу прозрачности (последний непрозрачный пиксел, координата Х2). Если прозрачных пикселов нет, то регион строится до конца строки.

Координату Y1 принимаем равной номеру строки, a Y2 — равной Y1+1. Таким образом, высота прямоугольника, описывающего одну строку, равна одному пикселу.

Строим регион по найденным координатам.

Переходим к следующей строке.

Объединяем созданные регионы и назначаем их окну.

Это упрощенный алгоритм, а в реальной жизни может быть два и более прямоугольных региона на одну строку, когда в середине строки встречаются прозрачные области.

Описанный алгоритм реализован в виде кода на языке C++ и представлен в листинге 3.4. Чуть позже я его рассмотрю.




А пока поговорим о том, каким должен быть графический файл. Это может быть любой Windows BITMAP-файл. Его размеры можно рассчитать в зависимости от величины изображения, но в данном случае ограничимся заранее определенными значениями (200x200 пикселов). Самостоятельно попробуйте сделать код еще более универсальным.

В самой картинке цвет пиксела с координатами 0:0 считается прозрачным, поэтому при подготовке изображения надо учесть, что все прозрачные области в окне должны быть окрашены этим цветом. Это более универсально, чем использовать заранее определенный цвет, потому что он может быть необходим изображению. А вот левый верхний угол чаще всего свободен, но даже если и нет, один пиксел всегда можно сделать прозрачным (т.е. не учитывать). На общую картину это не повлияет.

Создайте новый проект Win32 Project. Найдите функцию InitInstance и измените функцию создания окна следующим образом:

hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 200, 200, NULL, NULL, hInstance, NULL);

Здесь числа 200 указывают ширину и высоту окна. Если ваше изображение другого размера, то измените эти значения.

Кроме того, нужно убрать меню, потому что использовать его нет смысла. Для этого найдите функцию MyRegisterClass и строку, где изменяется свойство wcex.lpszMenuName. Ему нужно присвоить нулевое значение:

wcex.lpszMenuName = 0;

В разделе глобальных переменных нужно добавить следующие две переменные:

HBITMAP maskBitmap;
HWND hWnd;

Первая переменная будет использоваться для хранения изображения, а вторую мы уже неоднократно использовали для хранения указателя на окно. Объявление переменной hWnd надо удалить из функции InitInstance, чтобы использовать глобальную переменную.

Теперь измените функцию _tWinMain в соответствии с листингом 3.4, и можно считать, что ваша программа готова.

Прежде чем запускать программу, просто откомпилируйте ее и перейдите в папку, в которой находятся исходные коды. Если вы будете работать с программой в режиме отладки, то у вас должна появиться в этом месте подпапка Debug, иначе — Release. Поместите в нее подготовленный графический файл. Если файла не будет, то произойдет ошибка. Если вы компилировали в разных режимах, то лучше поместить файл сразу в обе папки на случай повторного переключения режима.



Листинг 3.4. Создание окна произвольной формы на основе маски
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { // TODO: Place code here. MSG msg; HACCEL hAccelTable;

// Initialize global strings LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_MASKWINDOW, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance);

// Perform application initialization: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; }

hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_MASKWINDOW);

// Следующий код вы должны добавить // Сначала убираем обрамление int Style; Style = GetWindowLong(hWnd, GWL_STYLE); Style=Style || WS_CAPTION; Style=Style || WS_SYSMENU; SetWindowLong(hWnd, GWL_STYLE, Style); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);

// Загружаем картинку maskBitmap = (HBITMAP)LoadImage(NULL, "mask.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);

if (!maskBitmap) return NULL;

// Описание необходимых переменных BITMAP bi; BYTE bpp; DWORD TransPixel; DWORD pixel; int startx; INT i, j;

HRGN Rgn, ResRgn = CreateRectRgn(0, 0, 0, 0);

GetObject(maskBitmap, sizeof( BITMAP ), bi);

bpp = bi.bmBitsPixel 3; BYTE *pBits = new BYTE[ bi.bmWidth * bi.bmHeight * bpp ];

// Получаем битовый массив int p = GetBitmapBits( maskBitmap, bi.bmWidth * bi.bmHeight * bpp, pBits );

// Определяем цвет прозрачного символа TransPixel = *(DWORD*)pBits;

TransPixel = 32 - bi.bmBitsPixel;

// Цикл сканирования строк for (i = 0; i bi.bmHeight; i++) { startx=-1; for (j = 0; j bi.bmWidth; j++) { pixel = *(DWORD*)(pBits + (i * bi.bmWidth + j) * bpp) (32 - bi.bmBitsPixel); if (pixel != TransPixel) { if (startx0) { startx = j; } else if (j == (bi.bmWidth - 1)) { Rgn = CreateRectRgn( startx, i, j, i + 1 ); CombineRgn( ResRgn, ResRgn, Rgn, RGN_OR); startx=-1; } } else if (startx=0) { Rgn = CreateRectRgn(startx, i, j, i + 1); CombineRgn(ResRgn, ResRgn, Rgn, RGN_OR); startx=-1; } } } delete pBits; SetWindowRgn(hWnd, ResRgn, TRUE); InvalidateRect(hWnd, 0, false); //Конец добавляемого кода



// Main message loop: while (GetMessage(msg, NULL, 0, false)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, msg)) { TranslateMessage(msg); DispatchMessage(msg); } }

return (int) msg.wParam; }

В самом начале из окна убирается системное меню и обрамление. После этого загружается картинка уже знакомой функцией LoadImage. Изображение читается из файла, поэтому первый параметр равен NULL, второй — содержит имя файла, а в последнем — указан флаг LR_LOADFROMFILE. Так как мы указали только имя файла (без полного пути), то программа будет искать его в том же каталоге, где находится программа. Именно поэтому мы должны были скопировать mask.bmp в папку Debug или/и Release.

Необходимо проверить наличие файла изображения. Если переменная maskBitmap равна нулю, то картинка не была найдена, и произойдет выход из программы:

if (!maskBitmap) return NULL;

Это обязательная проверка, потому что дальнейшее обращение к памяти, где должны быть данные, приведет к ненужной ошибке.

Последующий код довольно сложный, для его понимания нужно достаточно хорошо знать работу с указателями, поэтому описывать его в данной книге не имеет смысла.

Если вы сейчас запустите пример, то увидите окно, как на 3.10. Окно действительно приняло форму изображения, но оно пустое. Был создан только регион, но в самом окне ничего не нарисовано. Чтобы содержимое окна наполнить изображением картинки, надо в обработчик события WM_PAINT функции wndProc добавить следующий код (полный код примера смотрите на компакт-диске):

case WM_PAINT: hdc = BeginPaint(hWnd, ps); // TODO: Add any drawing code here... hdcBits=::CreateCompatibleDC(hdc); SelectObject(hdcBits, maskBitmap); BitBlt(hdc, 0, 0, 200, 200, hdcBits, 0, 0, SRCCOPY); DeleteDC(hdcBits); EndPaint(hWnd, ps); break;

Здесь просто выводится изображение точно так же, как при рисовании кнопки Пуск. Вот теперь программа закончена, и вы можете увидеть результат ее работы на 3.11.



3.10. Окно в форме рисунка



3.11. Приложение с окном произвольной формы

Примечание
Исходный код примера , описанного в этом разделе , вы можете найти на компакт - диске в каталоге \Demo\Chapter3\MaskWindow.

Содержание раздела