Сообщений 10 Оценка 255 Оценить |
WS_EX_LAYERED SetLayeredWindowAttributes OpenGL Заключение |
Начиная с Windows 2000, компания Microsoft включила в состав операционной системы поддержку окон, созданных с расширенным стилем WS_EX_LAYERED. В результате стало возможным очень просто создавать окна с различной степенью прозрачности, объявлять прозрачные (для данного окна) цвета и так далее. Все это достигается при помощи двух функций WinAPI: SetLayeredWindowAttributes и UpdateLayeredWindow.
Однако когда автор попробовал работать с OpenGL-контекстом, результата, в смысле прозрачности окон, удалось добиться далеко не сразу. Эта статья и расскажет о том, как работать с OpenGL в полупрозрачных окнах.
Напомню, чтобы использовать “слоистые” окна (Layered Windows), они должны быть созданы при помощи функции CreateWindowEx, которая отличается от CreateWindow наличием еще одного (первого параметра), задающего расширенный стиль окна, в нашем случае он должен быть WS_EX_LAYERED.
Кроме того, вы можете вызвать функцию SetWindowLong(hWnd, GWL_EXSTYLE, WS_EX_LAYERED) для установки требуемого стиля уже существующего окна.
При использовании констант и функций, имеющих отношение к “слоистым” окнам, в вашем приложении необходимо показать, что вы пишете код именно для Windows 2000 (и выше), например, можно так: define _WIN32_WINNT 0x0500.
В примере я буду использовать функцию SetLayeredWindowAttributes, так как она проще в использовании и, в отличие от UpdateLayeredWindow, не усложняет суть дела.
Функция очень простая. Описание функции приведено ниже:
WINUSERAPI BOOL WINAPI SetLayeredWindowAttributes(HWND hwnd, COLORREF crKey, BYTE bAlpha, DWORD dwFlags); |
hWnd – это хэндл окна, для которого хотим применить эту функцию. crKey – цвет, который мы хотим сделать прозрачным. bAlpha – степень прозрачности окна. При bAlpha, равном нулю, окно делается полностью прозрачным. При bAlpha, равном 255 (0xff), окно становится совершенно непрозрачным. dwFlags может принимать два значения – LWA_COLORKEY и LWA_ALPHA, которые могут использоваться совместно при помощи операции «логического ИЛИ». В случае, если используется значение LWA_COLORKEY, то всё окно делается абсолютно прозрачным, а прозрачность цвета, совпадающего с указанным в crKey, определяется параметром bAlpha. Например, если указать
SetLayeredWindowAttributes(hWnd, RGB(0, 0, 0), 0x0, LWA_COLORKEY) |
то прозрачным окажутся только те части окна, которые закрашены в чёрный цвет.
Наоборот, если будет указано
SetLayeredWindowAttributes(hWnd, RGB(0xff, 0xff, 0xff), 0x0, LWA_COLORKEY) |
то прозрачными окажутся только те части, которые закрашены белым цветом.
Если флаг LWA_COLORKEY не установлен, то прозрачным делается всё окно, независимо от того, какими цветами оно раскрашено. Таким образом, можно создать окно с полностью прозрачными частями (crKey) или все окно будет полупрозрачным с определенной степенью (bAlphа=0…255). Инициализация и использование может выглядеть так:
// для использования функции SetLayeredWindowAttributes и // связанных с ней констант #define _WIN32_WINNT 0x0500 #include <windows.h> // …const TCHAR szAppName[] = TEXT("LayeredWnds"); // Название приложенияconst TCHAR szAppTitle[] = TEXT("LayeredWnds"); // Заголовок главного окна // …int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) { // … HWND hWnd = CreateWindowEx(WS_EX_LAYERED, szAppName, szAppTitle, WS_POPUP | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 200, 200, NULL, NULL, hInstance, NULL); // пока не будет вызвана SetLayeredWindowAttributes или UpdateLayeredWindow// окно никак иначе отобразить не удастся SetLayeredWindowAttributes(hWnd, 0x0, 100, LWA_COLORKEY); UpdateWindow(hWnd); } |
Ниже приведён возможный код обработки сообщений WM_PAINT:
HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hWnd, &ps); // текущее положение окна (клиентской части) RECT r; GetClientRect(hWnd, &r); // создаем шрифт HFONT font = CreateFont(90, 30, 0, 0, 150, 0, 0, 0, ANSI_CHARSET, OUT_DEVICE_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH, _T("Arial")); // выбираем шрифт SelectObject(hdc, font); // и удаляем verify(DeleteFont(font)); ..// verify – это макрос (см. примеры на CD)// пишем в соответсвующем режиме SetBkMode(hdc, TRANSPARENT); DrawText(hdc, _T("RSDN"), 4, &r, DT_VCENTER | DT_CENTER | DT_SINGLELINE ); // конец рисованию EndPaint(hWnd, &ps); |
В зависимости от параметров SetLayeredWindowAttributes в данном случае можно получить следующее:
При работе с SetLayeredWindowAttributes можно пользоваться не только средствами GDI, но и библиотекой GDI+, которая может значительно упростит работу при выводе графических примитивов.
А теперь, собствено то, что навело меня на мысль о написании этой статьи. В своей работе я использую OpenGL. Однако в отличие от GDI+ при использовании SetLayeredWindowAttributes в приложении с OpenGL окно как было непрозрачным, так и осталось. Причина этого кроется в механизме вывода OpenGL контекста.
ПРИМЕЧАНИЕ В дальнейшем я буду полагать, что читатель знаком с OpenGL и в состоянии написать минимальное WinAPI-приложение с использованием OpenGL. См. например: Баяковский Ю., Игнатенко А., Фролов А. “Графическая библиотека OpenGL. Методическое пособие”. |
Схема работы такого приложения выглядит примерно так (механизм двойной буферизации включён):
Стандартное OpenGL приложение
где Off-screen Buffer может являться как DIB-секцией (DIB-section, см. ниже), так и аппаратно реализованной частью графического ускорителя. Однако вы не сможете получить прямой доступ к этому буферу, а можете только вывести его функцией SwapBuffers в соответствующую часть окна. Инициализация подобного приложения заканчивается созданием OpenGL контекста HGLRC.
Изменим процесс инициализации сообразно нашим целям. Схема работы приложения станет несколько иной:
OpenGL посредством GDI
В качестве bitmap будет выступать DIB-секция (DIB section) – bitmap (по сути просто область памяти) в который приложение может напрямую записывать все, что захочет. Его можно создать функцией CreateDIBSection:
HDC pdcDIB; // контекст устройства в памяти HBITMAP hbmpDIB; // и его текущий битмапvoid *pBitsDIB(NULL); // содержимое битмапаint cxDIB(200); int cyDIB(300); // его размеры (например для окна 200х300) BITMAPINFOHEADER BIH; // и заголовок// …// создаем DIB section// создаем структуру BITMAPINFOHEADER, описывающую наш DIBint iSize = sizeof(BITMAPINFOHEADER); // размер memset(&BIH, 0, iSize); BIH.biSize = iSize; // размер структуры BIH.biWidth = cxDIB; // геометрия BIH.biHeight = cyDIB; // битмапа BIH.biPlanes = 1; // один план BIH.biBitCount = 24; // 24 bits per pixel BIH.biCompression = BI_RGB;// без сжатия// создаем новый DC в памяти pdcDIB = CreateCompatibleDC(NULL); // создаем DIB-секцию hbmpDIB = CreateDIBSection( pdcDIB, // контекст устройства (BITMAPINFO*)&BIH, // информация о битмапе DIB_RGB_COLORS, // параметры цвета &pBitsDIB, // местоположение буфера (память выделяет система) NULL, // не привязываемся к отображаемым в память файлам 0); // выберем новый битмап (DIB section) для контекста устройства в памяти SelectObject(pdcDIB, hbmpDIB); |
Теперь опишем сам OpenGL-контекст. Необходимо заставить OpenGL выводить графику в подготовленный нами bitmap. Для этого используется функция SetPixelFormat и структура PIXELFORMATDESCRIPTOR:
// создаем контекст OpenGL // главное отличие (после расположения буффера) - // флаг PFD_DRAW_TO_BITMAP, т.е. показываем, что надо // рисовать "в картинку" DWORD dwFlags=PFD_SUPPORT_OPENGL | PFD_DRAW_TO_BITMAP; // заполним соответсвующим образом PIXELFORMATDESCRIPTOR PIXELFORMATDESCRIPTOR pfd ; memset(&pfd, 0x0, sizeof(PIXELFORMATDESCRIPTOR)); // сначала нулями pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); // размер pfd.nVersion = 1; // номер версии pfd.dwFlags = dwFlags ; // флаги (см. выше) pfd.iPixelType = PFD_TYPE_RGBA ; // тип пикселя pfd.cColorBits = 24 ; // 24 бита напиксель pfd.cDepthBits = 32 ; // 32-битный буффер глубины pfd.iLayerType = PFD_MAIN_PLANE ; // тип слоя// выберем для нашего контекста созданный формат пикселяint nPixelFormat = ChoosePixelFormat(pdcDIB, &pfd); // и установим его BOOL bResult = SetPixelFormat(pdcDIB, nPixelFormat, &pfd); // собственно, контекст для рендеринга готов m_hrc = wglCreateContext(pdcDIB); |
В отличие от программ, в которых используется классическая двойная буферизация, нет необходимости вызывать SwapBuffers() из функции отрисовки сцены, достаточно вызвать лишь glFlush, например:
void display() { // очищаем буффер цвета и глубины glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); auxSolidTeapot(0.5); // рисуем… чайник glFlush(); // используем glFlush вместо SwapBuffers } |
После вызова glFlush результат работы OpenGL-функций окажется в DIB-секции и связанном с ней контексте устройства в памяти (pdcDIB). После этого необходимо содержимое этого контекста перенести в контекст, связанным с нашим окном. Это можно сделать при помощи BitBlt или SetDIBitsToDevice. С учетом всего этого обработчик сообщения WM_PAINT может иметь следующий вид:
PAINTSTRUCT ps; HDC hDC = BeginPaint(hWnd, &ps); display(); // OpenGL -> DIB BitBlt(hDC, 0, 0, cxDIB, cyDIB, pdcDIB, 0, 0, SRCCOPY); // DIB -> hDC// можно и так:// BITMAPINFO bmp_info;// memset(&bmp_info, 0x0, sizeof(bmp_info));// bmp_info.bmiHeader=BIH;// SetDIBitsToDevice(hDC, 0, 0, cxDIB, cxDIB, 0, 0, 0, cyDIB, bmp_cnt, &bmp_info,// DIB_RGB_COLORS); EndPaint(hWnd, &ps); |
При изменении размеров окна необходимо в дополнение к действиям, обычным для данной ситуации (glViewport и изменения соотвествующих матриц), произвести повторную инициализацию DIB-секции. Этот процесс аналогичен ее созданию, меняются только размеры окна (переменные cxDIB и cуDIB, см. выше).
Если все сделано правильно, то после вызова функции display окно будет имет вид:
Вот, собственно, и все. Таким способом можно создавать очень красивые окна или элементы управления. Однако не забывайте, что скорость у такого рода приложений на порядок меньше, чем у “чистого” OpenGL.
ПРИМЕЧАНИЕ Схемы в данной статье сделаны по мотивам рисунков статьи Dale Rogerson, ”OpenGL VI: Rendering on DIBs with PFD_DRAW_TO_BITMAP”, april 18, 1995 из MSDN. |
Сообщений 10 Оценка 255 Оценить |