Графический интерфейс GDI в Microsoft Windows© Александр Фролов, Григорий ФроловТом 14, М.: Диалог-МИФИ, 1993, 288 стр. 3.5. Приложение PALETTEДля демонстрации методов работы с цветовыми палитрами мы подготовили приложение PALETTE. Это приложение создает палитру из 256 градаций серого цвета и рисует в своем окне вертикальные прямоугольники с использованием палитры. На рис. 3.8 изображен внешний вид окна приложения PALETTE. Из-за ограниченных возможностей типографии вместо плавных переходов оттенков серого на этом рисунке использованы смешанные цвета. Кстати, если вы запустите приложение PALETTE в стандартном режиме VGA с использованием 16 цветов, внешний вид окна на экране монитора будет полностью соответствовать рис. 3.8.
Рис. 3.8. Окно приложения PALETTE Перед началом работы приложение выводит на экран диалоговую панель, в которой сообщает о том, используется ли в текущем видеорежиме механизм цветовых палитр и каков размер системной палитры. Вы можете попробовать работу этого приложения в различных видеорежимах, убедившись в том, что оно работает правильно во всех видеорежимах, кроме стандартного режима VGA. В последнем случае вместо оттенков серого цвета используются смешанные цвета. В режиме среднего цветового разрешения используется механизм цветовых палитр. Если же вы запустите приложение в режиме высокого цветового разрешения, несмотря на то, что палитры не используются, приложение по-прежнему будет рисовать правильное изображение. На дискете, которая продается вместе с книгой, в каталоге palette есть несколько bmp-файлов, содержащих 256-цветные изображения. Загружая эти изображения в графический редактор Paint Brush, вы сможете изменить системную палитру и посмотреть, как это отразится на окне приложения PALETTE. Запустите приложения PALETTE и Paint Brush. Измените размеры окон этих приложений так, чтобы окна были видны одновременно. Затем загрузите в приложение Paint Brush изображение sky.bmp. Вы увидите, что внешний вид окна приложения PALETTE изменился. Переключитесь на приложение PALETTE. Внешний вид его окна восстановится, но качество изображения в окне Paint Brush ухудшится. Описанное явление возникает из за того, что размер системной палитры цветов равен 256. Когда вы загружаете в Paint Brush файл sky.bmp, системная палитра изменяется так, чтобы наилучшим образом соответствовать цветам изображения. Если при этом было запущено приложение PALETTE (создающее свою собственную палитру из 256 градаций серого цвета), и оно находится в фоновом режиме, для него используется фоновый алгоритм реализации палитры. В первую очередь будут удовлетворен запрос на системную палитру для активного приложения (Paint Brush), а затем уже фонового (PALETTE). Проследите, как изменяется внешний вид окна приложения PALETTE при загрузке других bmp-файлов, имеющихся на дискете в каталоге palette. Обратите внимание, что при загрузке файла gray.bmp качество изображения в окне PALETTE практически не изменяется, так как изображение gray.bmp содержит только оттенки серого цвета. Обратимся теперь к исходному тексту приложения PALETTE (листинг 3.3). Листинг 3.3. Файл palette/palette.cpp
// ----------------------------------------
// Приложение PALETTE
// Демонстрация использования цветовых палитр
// ----------------------------------------
#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <mem.h>
// Размер создаваемой логической палитры
#define PALETTESIZE 256
// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
void PaletteInfo(void);
// Имя класса окна
char const szClassName[] = "PaletteClass";
// Заголовок окна
char const szWindowTitle[] = "Palette Demo";
// Размеры внутренней области окна
short cxClient, cyClient;
// Идентификаторы палитр
HPALETTE hPal, hOldPalette;
// Указатель на логическую палитру
NPLOGPALETTE pLogPal;
// =====================================
// Функция WinMain
// =====================================
#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,
int nCmdShow)
{
MSG msg; // структура для работы с сообщениями
HWND hwnd; // идентификатор главного окна приложения
// Инициализируем приложение
if(!hPrevInstance)
if(!InitApp(hInstance))
return FALSE;
// Выводим сведения о палитре
PaletteInfo();
// После успешной инициализации приложения создаем
// главное окно приложения
hwnd = CreateWindow(
szClassName, // имя класса окна
szWindowTitle, // заголовок окна
WS_OVERLAPPEDWINDOW, // стиль окна
CW_USEDEFAULT, // задаем размеры и расположение
CW_USEDEFAULT, // окна, принятые по умолчанию
CW_USEDEFAULT,
CW_USEDEFAULT,
0, 0, hInstance, NULL);
// Если создать окно не удалось, завершаем приложение
if(!hwnd)
return FALSE;
// Рисуем главное окно
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений
while(GetMessage(&msg, 0, 0, 0))
{
DispatchMessage(&msg);
}
return msg.wParam;
}
// =====================================
// Функция InitApp
// Выполняет регистрацию класса окна
// =====================================
BOOL
InitApp(HINSTANCE hInstance)
{
ATOM aWndClass; // атом для кода возврата
WNDCLASS wc; // структура для регистрации
// класса окна
// Записываем во все поля структуры нулевые значения
memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = NULL;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса
aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}
// =====================================
// Функция WndProc
// =====================================
LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
switch (msg)
{
case WM_CREATE:
{
int i;
// Получаем память для палитры
pLogPal = (NPLOGPALETTE) LocalAlloc(LMEM_FIXED,
(sizeof (LOGPALETTE) +
(sizeof (PALETTEENTRY) * (PALETTESIZE))));
// Заполняем заголовок палитры
pLogPal->palVersion = 0x300;
pLogPal->palNumEntries = PALETTESIZE;
// Заполняем палитру градациями
// серого цвета
for (i=0; i < 256; i++)
{
pLogPal->palPalEntry[i].peRed =
pLogPal->palPalEntry[i].peGreen =
pLogPal->palPalEntry[i].peBlue = i;
pLogPal->palPalEntry[i].peFlags = 0;
}
// Создаем логическую палитру
hPal = CreatePalette((LPLOGPALETTE) pLogPal);
return 0;
}
// При изменении размеров окна сохраняем
// новые значения для ширины и высоты
case WM_SIZE:
{
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0;
}
// Рисование в окне
case WM_PAINT:
{
RECT rc;
int i, nWidth;
HBRUSH hBrush;
// Получаем контекст отображения для
// рисования во внутренней области окна
hdc = BeginPaint(hwnd, &ps);
// Выбираем палитру
hOldPalette = SelectPalette(hdc, hPal, FALSE);
// Реализуем логическую палитру
RealizePalette(hdc);
// Координаты первого прямоугольника
nWidth = 2;
rc.left = rc.top = 0;
rc.right = nWidth;
rc.bottom = cyClient;
// Рисуем 256 прямоугольников во внутренней
// области окна
for (i=0; i < 256; i++)
{
// Выбираем кисть. Вы можете использовать одну из
// двух приведенных ниже строк, переместив символ
// комментария
// Косвенная ссылка на палитру
// hBrush = CreateSolidBrush(PALETTERGB(i, i, i));
// Прямая ссылка на палитру
hBrush = CreateSolidBrush(PALETTEINDEX(i));
// Закрашиваем прямоугольную область
FillRect(hdc, &rc, hBrush);
// Координаты следующего прямоугольника
rc.left = rc.right;
rc.right += nWidth;
// Удаляем кисть
DeleteBrush(hBrush);
}
// Выбираем старую палитру
SelectPalette(hdc, hOldPalette, TRUE);
// Освобождаем контекст отображения
EndPaint(hwnd, &ps);
return 0;
}
// Это сообщение приходит при изменении
// системной палитры. Наше приложение в ответ
// на это сообщение заново реализует свою логическую
// палитру и при необходимости перерисовывает окно
case WM_PALETTECHANGED:
{
// Если это не наше окно, передаем управление
// обработчику сообщения WM_QUERYNEWPALETTE
if (hwnd == (HWND) wParam)
break;
}
// В ответ на это сообщение приложение должно
// реализовать свою логическую палитру и
// обновить окно
case WM_QUERYNEWPALETTE:
{
HDC hdc;
HPALETTE hOldPal;
int nChanged;
// Выбираем логическую палитру в
// контекст отображения
hdc = GetDC(hwnd);
// При обработке сообщения WM_QUERYNEWPALETTE
// палитра выбирается для активного окна,
// а при обработке сообщения WM_PALETTECHANGED -
// для фонового
hOldPal = SelectPalette(hdc, hPal,
(msg == WM_QUERYNEWPALETTE) ? FALSE : TRUE);
// Реализуем логическую палитру и выбираем
// ее в контекст отображения
nChanged = RealizePalette(hdc);
SelectPalette(hdc, hOldPal, TRUE);
// Освобождаем контекст отображения
ReleaseDC(hwnd, hdc);
// Если были изменения палитры,
// перерисовываем окно
if(nChanged)
InvalidateRect(hwnd, NULL, TRUE);
return nChanged;
}
case WM_DESTROY:
{
// Удаляем созданную нами
// логическую палитру
DeletePalette(hPal);
// Освобождаем память, выделенную для палитры
LocalFree(pLogPal);
PostQuitMessage(0);
return 0;
}
default:
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
// --------------------------------------------------------
// Функция PaletteInfo
// Вывод некоторых сведений о палитре
// --------------------------------------------------------
void PaletteInfo(void)
{
HDC hdc;
int iPalSize, iRasterCaps;
char szMsg[256];
char szPal[20];
// Получаем контекст отображения для
// всего экрана
hdc = GetDC(NULL);
// Определяем размер палитры и слово,
// описывающее возможности драйвера
// видеоконтроллера как растрового устройства
iPalSize = GetDeviceCaps(hdc, SIZEPALETTE);
iRasterCaps = GetDeviceCaps(hdc, RASTERCAPS);
// Проверяем, используется ли механизм палитр
if(iRasterCaps & RC_PALETTE)
{
iRasterCaps = TRUE;
lstrcpy(szPal, "используются");
}
else
{
iRasterCaps = FALSE;
lstrcpy(szPal, "не используются");
}
// Освобождаем контекст отображения
ReleaseDC(NULL, hdc);
// Выводим сведения о палитре
wsprintf(szMsg, "Палитры %s\n"
"Размер системной палитры: %d\n",
(LPSTR)szPal, iPalSize);
MessageBox(NULL, szMsg, "Palette Demo", MB_OK);
}
В начале файла определена константа PALETTESIZE, значение которой равно размеру создаваемой приложением логической палитры: #define PALETTESIZE 256 Для того чтобы можно было запустить несколько копий приложения PALETTE, мы выполняем регистрацию класса окна только для первой копии приложения:
if(!hPrevInstance)
if(!InitApp(hInstance))
return FALSE;
После регистрации класса окна вызывается функция PaletteInfo, которая предназначена для определения факта использования палитр в текущем видеорежиме. Получив контекст отображения для всего экрана видеомонитора, эта функция вызывает функцию GetDeviceCaps, определяя размер системной палитры и растровые возможности устройства вывода (в данном случае, драйвера видеомонитора): iPalSize = GetDeviceCaps(hdc, SIZEPALETTE); iRasterCaps = GetDeviceCaps(hdc, RASTERCAPS); Если используется цветовые палитры, в слове iRasterCaps должен быть установлен бит RC_PALETTE:
if(iRasterCaps & RC_PALETTE)
{
iRasterCaps = TRUE;
lstrcpy(szPal, "используются");
}
else
{
iRasterCaps = FALSE;
lstrcpy(szPal, "не используются");
}
На обработчик сообщения WM_CREATE возложена задача создания палитры. Прежде всего заказываем память для структуры, содержащей палитру: pLogPal = (NPLOGPALETTE) LocalAlloc(LMEM_FIXED, (sizeof (LOGPALETTE) + (sizeof (PALETTEENTRY) * (PALETTESIZE)))); Размер нужного буфера равен размеру структуры LOGPALETTE (заголовок палитры), плюс размер самой палитры, равный количеству элементов (PALETTESIZE), умноженному на размер одного элемента (sizeof (PALETTEENTRY) ). В заголовке палитры необходимо заполнить два поля - версию и размер палитры: pLogPal->palVersion = 0x300; pLogPal->palNumEntries = PALETTESIZE; Далее обработчик сообщения WM_PAINT заполняет в цикле всю палитру оттенками серого, причем в поле peFlags записывается нулевое значение (для использования стандартного алгоритма реализации цветовой палитры):
for (i=0; i < 256; i++)
{
pLogPal->palPalEntry[i].peRed =
pLogPal->palPalEntry[i].peGreen =
pLogPal->palPalEntry[i].peBlue = i;
pLogPal->palPalEntry[i].peFlags = 0;
}
После заполнения структуры данных вызывается функция CreatePalette, создающая палитру: hPal = CreatePalette((LPLOGPALETTE) pLogPal); В глобальную переменную hPal записывается идентификатор созданной логической палитры. Обработчик сообщения WM_SIZE определяет и сохраняет размеры внутренней области окна приложения, необходимые для рисования. Рисование выполняется, как и следовало ожидать, при обработке сообщения WM_PAINT. После получения контекста отображения приложение выбирает в него и реализует логическую палитру: hOldPalette = SelectPalette(hdc, hPal, FALSE); RealizePalette(hdc); Далее приложение в цикле рисует 256 прямоугольников шириной 2 пиксела. Высота этих прямоугольников равна высоте внутренней области окна приложения. Для каждого прямоугольника создается кисть, причем цвет кисти определяется как ссылка на элемент логической палитры с использованием макрокоманды PALETTEINDEX:
for (i=0; i < 256; i++)
{
hBrush = CreateSolidBrush(PALETTEINDEX(i));
FillRect(hdc, &rc, hBrush);
rc.left = rc.right;
rc.right += nWidth;
DeleteBrush(hBrush);
}
После использования кисти она удаляется. Вы можете попробовать создавать кисть при помощи макрокоманды PALETTERGB: hBrush = CreateSolidBrush(PALETTERGB(i, i, i)); Перед возвращением управления обработчик сообщения WM_PAINT выбирает в контекст отображения старую палитру: SelectPalette(hdc, hOldPalette, TRUE); Рассмотрим теперь обработчики сообщений WM_PALETTECHANGED и WM_QUERYNEWPALETTE. Обработчик сообщения WM_PALETTECHANGED получает управление, когда какое-либо приложение изменяет системную палитру. Так как наше приложение тоже может изменить системную палитру, оно также может выступать инициатором рассылки этого сообщения. Параметр wParam сообщения WM_PALETTECHANGED содержит идентификатор окна приложения, изменившего палитру. Если этот параметр равен идентификатору нашего окна, ничего делать не надо, поэтому мы просто выходим из функции окна:
case WM_PALETTECHANGED:
{
if (hwnd == (HWND) wParam)
break;
}
В противном случае управление передается обработчику сообщения WM_QUERYNEWPALETTE, который выполняет реализацию логической палитры приложения и перерисовку окна. Если бы мы при обработке сообщения WM_PALETTECHANGED не выполнили проверку идентификатора окна, инициировавшего изменение системной палитры, а просто реализовали палитру приложения, это привело бы к "зацикливанию" приложения (так как в ответ на изменение палитры наше окно снова получит сообщение WM_PALETTECHANGED). Теперь займемся обработчиком сообщения WM_QUERYNEWPALETTE. Для нашего приложения он выполняет почти те же самые действия, что и обработчик сообщения WM_PALETTECHANGED, поэтому для экономии места мы объединили эти обработчики. При получении сообщения WM_QUERYNEWPALETTE или WM_PALETTECHANGED (как результата изменения системной палитры другим приложением) наше приложение получает контекст отображения и выбирает в него логическую палитру: hOldPal = SelectPalette(hdc, hPal, (msg == WM_QUERYNEWPALETTE) ? FALSE : TRUE); Заметьте, что для сообщения WM_QUERYNEWPALETTE палитра выбирается как для активного окна, а для WM_PALETTECHANGED - как для фонового. Затем палитра реализуется в контексте отображения: nChanged = RealizePalette(hdc); После этого мы выбираем в контекст отображения старую палитру и освобождаем контекст отображения: SelectPalette(hdc, hOldPal, TRUE); ReleaseDC(hwnd, hdc); Если реализация логической палитры нашим приложением привела к изменению системной палитры, необходимо перерисовать окно. Для этого приложение вызывает функцию InvalidateRect: if(nChanged) InvalidateRect(hwnd, NULL, TRUE); Перед завершением работы приложение удаляет созданную логическую палитру и освобождает созданный для нее блок памяти (блок памяти можно было освободить и сразу после создания логической палитры): DeletePalette(hPal); LocalFree(pLogPal); Файл определения модуля для приложения PALETTE приведен в листинге 3.4. Листинг 3.4. Файл palette/palette.def ; ============================= ; Файл определения модуля ; ============================= NAME PALETTE DESCRIPTION 'Приложение PALETTE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple |

