До сих пор мы в наших приложениях практически не использовали цвет. Теперь настало время рассказать о том, как с помощью средств GDI можно раскрасить окна ваших приложений.
Не секрет, что цветовые возможности приложений ограничиваются в основном цветовым разрешением аппаратуры. В настоящее время чаще всего используются монохромные видеомониторы VGA (как правило, в портативных компьютерах типа Notebook), цветные видеомониторы VGA и SVGA. Все реже встречаются видеомониторы EGA и CGA, которые мало подходят для работы в Windows.
При использовании монохромных мониторов VGA вместо цветов используются градации серого, поэтому приложения не ограничены в выборе черным и белым цветами. Для мониторов EGA и VGA в распоряжении приложений Windows имеется всего лишь 16 цветов.
Цветовое разрешение для мониторов SVGA зависит от типа видеоконтроллера и от объема установленной в нем видеопамяти. Обычные видеоконтроллеры SVGA могут отображать одновременно только 256 цветов. Самые хорошие видеоконтроллеры SVGA, способные работать в режиме True Color, могут отображать 16,777,216 цветов. Это достаточно много, хотя цветовое разрешение человеческого глаза еще выше.
Для использования режимов с высоким разрешением необходимо использовать драйверы, которые обычно поставляются вместе с видеоконтроллерами. Приведем возможные режимы работы для драйверов Cirrus 542x версии 1.0, которые поставляются с видеоконтроллерами на базе видеопроцессоров фирмы Cirrus Logic при объеме видеопамяти 1 Мбайт:
Разрешение в пикселах |
Цветовое разрешение |
1024x768 |
16 |
1024x768 |
256 |
800x600 |
16 |
800x600 |
256 |
800x600 |
65,536 |
640x480 |
16 |
640x480 |
256 |
640x480 |
65,536 |
640x480 |
16,777,216 |
В нашей книге разрешение 16 цветов мы будем называть низким цветовым разрешением, 256 цветов - средним, 65,536 и 16,777,216 цветов - высоким.
В операционной системе Windows приложения определяют цвет, задавая интенсивности трех его RGB-компонент: красной (R), зеленой (G) и голубой (B). Интенсивность каждой компоненты задается числом в диапазоне от 0 (минимальная интенсивность) до 255 (максимальная интенсивность). Такая система позволяет приложению указать любой из 16,777,216 цветов (256*256*256 = 16,777,216).
Раскрашивая изображение, приложение Windows может использовать любые цвета. Однако это не означает, что цвет изображения, полученного на экране (или принтере) будет в точности такой, какой был указан при выводе.
Windows учитывает цветовое разрешение устройств вывода, ограничивая соответствующим образом цветовую гамму изображения или работая со смешанными цвета (смешанный цвет образуется из чистых цветов, при этом изображение состоит из точек, имеющих чистые цвета). Соответствующий механизм достаточно сложен и зависит от текущего цветового разрешения.
В режиме с низким цветовым разрешением используются 16 различных цветов (режим совместимости с VGA). Определяя цвет пера для рисования линий, вы можете указать любой цвет, однако в результате будет выбран один из 16 цветов, максимально соответствующий заказанному. Цвет кисти может быть либо чистым (в этом случае используется один из 16 цветов), либо смешанным.
В режиме среднего цветового разрешения может использоваться механизм цветовых палитр. Приложению доступны сотни тысяч цветов, однако одновременно оно может использовать не более 256 цветов, составляющих палитру. К сожалению, механизм палитр не является прозрачным для приложений и сложен в использовании.
В режиме True Color палитры не используются, а полученный на экране цвет изображения в точности соответствует заказанному.
Несмотря на то что стоимость видеоконтроллеров True Color постоянно снижается, такие контроллеры все же дороже обычных видеоконтроллеров SVGA на несколько десятков долларов (есть еще одна проблема, связанная с увеличением объема памяти, необходимого для хранения битовых изображений с высоким цветовым разрешением).
Поэтому когда пользователю не нужна высокая скорость работы в Windows, но нужна возможность работы с разрешением 256 цветов, он может остановить свой выбор на более дешевом видеоконтроллере. Кроме того, даже если в компьютере установлен видеоадаптер True Color, он может использоваться в режимах со средним или низким цветовым разрешением. Для программиста это означает необходимость использования цветовых палитр, так как только в этом случае будут реализованы все цветовые возможности такого видеоконтроллера.
Что такое цветовая палитра ?
Если вы видели художника за работой, вы уже знаете ответ на этот вопрос. Цветовая палитра - это не более чем набор цветов. Художник создает палитру из различных красок, смешивая их. Полученный набор красок используется для рисования.
В GDI встроены средства для работы с 256-цветными палитрами. Если видеоконтроллер способен работать с палитрами, создается одна системная палитра, которая содержит отображаемые на экране цвета. Вы можете думать об этой палитре как о таблице цветов, хранящейся в памяти видеоконтроллера.
Часть системной палитры (20 элементов) зарезервированы для использования операционной системой. В зарезервированных элементах хранятся статические цвета, которые нужны для рисования таких объектов, как рамки окон, полосы просмотра и т. п., а также изображений, рисуемых приложением. Если видеоконтроллер работает в режиме низкого цветового разрешения или приложение не использует цветовые палитры (несмотря на наличие соответствующих возможностей аппаратуры), цветовая гамма приложения ограничена статическими цветами.
Приложения никогда не изменяют статические цвета, записанные в зарезервированных ячейках системной палитры. Содержимое остальных 236 ячеек системной палитры может изменяться в процессе реализации приложениями своих собственных цветовых палитр.
Схематически системная цветовая палитра изображена на рис. 3.1.
Рис. 3.1. Системная цветовая палитра
Ниже мы перечислим все статические цвета, указав для каждого цвета комбинацию соответствующих RGB-компонент (в шестнадцатеричном представлении).
Индекс в системной палитре |
R (красный цвет) |
G (зеленый цвет) |
B (голубой цвет) |
Цвет в палитре |
0 |
00 |
00 |
00 |
черный |
1 |
80 |
00 |
00 |
темно-красный |
2 |
00 |
80 |
00 |
темно-зеленый |
3 |
80 |
80 |
00 |
темно-желтый |
4 |
00 |
00 |
80 |
темно-голубой |
5 |
80 |
00 |
80 |
темно-малиновый |
6 |
00 |
80 |
80 |
темно-синий |
7 |
C0 |
C0 |
C0 |
светло-серый |
8 |
С0 |
DC |
C0 |
светло-зеленый |
9 |
A6 |
CA |
F0 |
светло-голубой |
246 |
FF |
FB |
F0 |
кремовый |
247 |
A0 |
A0 |
A4 |
светло-серый |
248 |
80 |
80 |
80 |
серый |
249 |
FF |
0 |
0 |
красный |
250 |
0 |
FF |
0 |
зеленый |
251 |
FF |
FF |
0 |
желтый |
252 |
0 |
0 |
FF |
синий |
253 |
FF |
0 |
FF |
малиновый |
254 |
0 |
FF |
FF |
голубой (циан) |
255 |
FF |
FF |
FF |
белый |
Странное на первый взгляд расположение статических цветов в системной палитре (десять цветов находятся в начале таблицы, десять - в конце) выбрано для обеспечения правильной работы часто используемой растровой операции "ИСКЛЮЧАЮЩЕЕ ИЛИ".
Приложения, которые не хотят ничего знать про палитры, могут указывать логический цвет изображений, составляя его из RGB-компонент, указывая их количественный состав. Однако, если видеоконтроллер не работает в режиме True Color, для вывода на экран будут использованы только статические цвета или смешанные цвета, состоящие из статических цветов. В результате полученный на экране физический цвет может не соответствовать запрошенному логическому цвету.
Многие функции программного интерфейса GDI (например, функции, создающие перья и кисти) требуют в качестве одного из своих параметров ссылку на используемый цвет. Цвет указывается при помощи переменной, имеющей тип COLORREF :
HBRUSH WINAPI CreateSolidBrush(COLORREF clrref);
Тип COLORREF определен в файле windows.h следующим образом:
typedef DWORD COLORREF;
В простейшем случае цвет можно определить с помощью макрокоманды RGB, комбинирующей цвет из отдельных компонент:
#define RGB(r,g,b) \ ((COLORREF)(((BYTE)(r) | \ ((WORD)(g)<<8)) | \ (((DWORD)(BYTE)(b))<<16)))
Эта макрокоманда упаковывает отдельные цветовые компоненты в двойное слово, причем (что важно) старший байт этого слова должен быть равен нулю (рис. 3.2).
Рис. 3.2. Представление цвета, полученное с помощью макрокоманды RGB
В файле windows.h определены также макрокоманды, извлекающие из переменной типа COLORREF, упакованной с помощью макрокоманды RGB, отдельные цветовые компоненты:
#define GetRValue (rgb) ((BYTE)(rgb)) #define GetGValue (rgb) ((BYTE)(((WORD)(rgb)) >> 8)) #define GetBValue (rgb) ((BYTE)((rgb)>>16))
Как мы уже говорили, в зависимости от текущего цветового разрешения Windows может предоставить приложению приближенный цвет, который максимально соответствует запрошенному логическому цвету. Функция GetNearestColor возвращает для запрошенного логического цвета clrref физический цвет, составленный только из компонент чистого цвета:
COLORREF WINAPI GetNearestColor(HDC hdc, COLORREF clrref);
Через параметр hdc необходимо передать идентификатор контекста отображения.
Как выбрать цвета для объектов приложения?
Самый простой (и самый плохой) способ заключается в том, что в исходном тексте приложения вы указываете цвета как комбинации RGB-компонент. Очевидный недостаток этого способа заключается в том, что пользователь не сможет изменить эти цвета. Даже если у вас идеальный вкус и вы сможете подобрать превосходную цветовую гамму, следует учитывать, что ваше приложение может быть запущено на монохромном мониторе, где отдельные цвета преобразуются в градации серого цвета. В результате некоторые элементы изображения могут стать плохо различимыми или пропадут вовсе.
Более удачный способ заключается в использовании так называемых системных цветов. Системные цвета - это цвета, с помощью которых операционная система Windows рисует отдельные элементы окон и органов управления.
Приложение Control Panel, которое входит в состав Windows, позволяет вам изменять системные цвета, обеспечивая приемлемую цветовую палитру практически для любого типа видеомонитора.
Для того чтобы узнать цвет той или иной системной компоненты экрана Windows, вы можете вызвать функцию GetSysColor :
COLORREF WINAPI GetSysColor(int nDspElement);
В качестве единственного параметра следует передать этой функции идентификатор компоненты:
Идентификатор |
Описание |
COLOR_ACTIVEBORDER |
Рамка вокруг активного окна |
COLOR_ACTIVECAPTION |
Заголовок активного окна |
COLOR_APPWORKSPACE |
Фон окна приложения MDI (приложение,
использующее многооконный интерфейс) |
COLOR_BACKGROUND |
Окно Desktop |
COLOR_BTNFACE |
Кнопка |
COLOR_BTNHIGHLIGHT |
Выбранная кнопка |
COLOR_BTNSHADOW |
Тень, "отбрасываемой" кнопкой |
COLOR_BTNTEXT |
Текст надписи на поверхности кнопки |
COLOR_CAPTIONTEXT |
Текст заголовка окна, кнопки изменения
размера, кнопки полосы просмотра |
COLOR_GRAYTEXT |
Текст серого цвета |
COLOR_HIGHLIGHT |
Фон выбранного элемента в органе
управления |
COLOR_HIGHLIGHTTEXT |
Текст для выбранного органа управления |
COLOR_INACTIVEBORDER |
Рамка вокруг неактивного окна |
COLOR_INACTIVECAPTION |
Заголовок неактивного окна |
COLOR_INACTIVECAPTIONTEXT |
Текст заголовка для неактивного окна |
COLOR_MENU |
Фон меню |
COLOR_MENUTEXT |
Текст меню |
COLOR_SCROLLBAR |
Полоса просмотра |
COLOR_WINDOW |
Фон окна |
COLOR_WINDOWFRAME |
Рамка окна |
COLOR_WINDOWTEXT |
Текст в окне |
Ваше приложение может выбрать для использования некоторые из системных цветов, при этом пользователь сможет влиять на внешний вид вашего приложения с помощью Control Panel, настраивая цвета на свой вкус.
Вы можете создать приложение, изменяющее системные цвета. Для этого обратите внимание на функцию SetSysColors :
void WINAPI SetSysColors( int cDspElements, const int FAR* lpnDspElements, const COLORREF FAR* lpdwRgbValues);
Параметр cDspElements определяет количество элементов, для которых изменяются цвета.
Параметр lpnDspElements представляет собой указатель на массив идентификаторов элементов изображения, список которых приведен выше.
Перед вызовом функции вам надо подготовить также массив из cDspElements элементов, содержащих новые значения для цветов, передав функции адрес этого массива через параметр lpdwRgbValues.
Внесенные изменения сохраняются только до очередного перезапуска операционной системы Windows.
После вызова этой функции все запущенные приложения получают сообщение WM_SYSCOLORCHANGE, которое информирует их об изменении системных цветов. Windows также перерисовывает на экране все видимые окна.
Как мы только что сказали, сообщение WM_SYSCOLORCHANGE посылается всем активным окнам верхнего уровня при изменении системных цветов. В ответ на это сообщение приложения, которые создают свои перья и кисти на базе системных цветов, должны удалить эти перья и кисти, а затем создать их заново.
Так как после изменения системных цветов все активные окна получают сообщение WM_PAINT, обработчик сообщения WM_SYSCOLORCHANGE не должен ничего перерисовывать в окне.
Сообщение WM_SYSCOLORCHANGE не имеет параметров, поэтому значения, передаваемые через wParam и lParam следует проигнорировать.
В составе DLL-библиотеки commdlg.dll есть функция ChooseColor, которая предназначена для выбора цвета. Эта функция выводит на экран диалоговую панель (рис. 3.3), с помощью которой пользователь может выбрать цвет из основного набора цветов "Basic Color" и дополнительного "Custom Colors". Возможность использования цветовых палитр при выборе не обеспечивается, так что с помощью этой функции можно выбирать только чистые статические или смешанные цвета.
Рис. 3.3. Диалоговая панель для выбора цвета
Если нажать на кнопку "Define Custom Colors...", внешний вид диалоговой панели изменится (рис. 3.4).
Рис. 3.4. Расширенная диалоговая панель
Пользуясь правой половиной диалоговой панели, пользователь сможет добавить новый цвет в набор "Custom Colors" и затем выбрать его из этого набора.
Функция ChooseColor описана в файле commdlg.h:
BOOL WINAPI ChooseColor(CHOOSECOLOR FAR* lpcc);
Перед вызовом функции следует заполнить структуру CHOOSECOLOR, передав функции ее адрес через параметр lpcc.
В случае успешного выбора цвета функция возвращает TRUE. Если пользователь отказался от выбора или, если произошла ошибка, возвращается значение FALSE.
Структура CHOOSECOLOR и указатель на нее описаны в файле commdlg.h:
typedef struct tagCHOOSECOLOR { DWORD lStructSize; HWND hwndOwner; HWND hInstance; COLORREF rgbResult; COLORREF FAR* lpCustColors; DWORD Flags; LPARAM lCustData; UINT (CALLBACK* lpfnHook)(HWND, UINT, WPARAM, LPARAM); LPCSTR lpTemplateName; } CHOOSECOLOR; typedef CHOOSECOLOR FAR *LPCHOOSECOLOR;
Опишем назначение и правила использования отдельных полей этой структуры.
Поле lStructSize заполняется перед вызовом функции. Оно должно содержать размер структуры в байтах.
Поле Flags также заполняется до вызова функции. В него следует записать флаги инициализации, влияющие на использование других полей этой структуры.
Флаг |
Описание |
CC_RGBINIT |
Для цвета, выбранного по умолчанию,
используется цвет, указанный в поле rgbResult |
CC_FULLOPEN |
Если указан этот флаг, на экране
появляется полный вариант диалоговой панели,
обеспечивающий возможность определения
произвольного цвета. Если этот флаг не указан,
для определения произвольного цвета
пользователь должен нажать на кнопку "Define Custom
Color" |
CC_PREVENTFULLOPEN |
Кнопка "Define Custom Color" блокируется,
таким образом, при использовании этого флага
пользователь не может определить произвольный
цвет |
CC_SHOWHELP |
Флаг разрешает отображение кнопки
"Help". Если указан этот флаг, в поле нельзя
указывать значение hwndOwner |
CC_ENABLEHOOK |
Если указан этот флаг, используется
функция фильтра, адрес которой указан в поле
lpfnHook. С помощью этой функции можно организовать
дополнительную обработку сообщений от
диалоговой панели |
CC_ENABLETEMPLATE |
Используется шаблон диалоговой панели,
определяемый содержимым поля hInstance. Адрес строки,
содержащей имя шаблона, должен быть указан в поле
lpTemplateName |
CC_ENABLETEMPLATEHANDLE |
Используется шаблон диалоговой панели,
идентификатор которого записан в поле hInstance.
Содержимое поля lpTemplateName игнорируется |
В поле hwndOwner перед вызовом функции следует записать идентификатор окна, создавшего диалоговую панель, или NULL. В последнем случае нельзя использовать флаг CC_SHOWHELP.
Поле hInstance заполняется в тех случаях, когда приложение использует свой собственный шаблон диалоговой панели (вместо стандартного шаблона, расположенного в ресурсах DLL-библиотеки). В этом случае перед вызовом функции в это поле следует записать идентификатор модуля, содержащего шаблон диалоговой панели. В поле Flags необходимо указать флаги CC_ENABLETEMPLATE или CC_ENABLETEMPLATEHANDLE.
В поле lpTemplateName следует записать адрес текстовой строки идентификатора ресурса шаблона диалоговой панели (если этот шаблон используется).
Поле rgbResult предназначено для передачи приложению цвета, выбранного пользователем. Если записать в это поле значение NULL, сразу после вывода диалоговой панели выбора цвета по умолчанию будет выбран черный цвет. Вы, однако, можете использовать для начального выбора любой другой цвет, записав его в это поле и указав флаг CC_RGBINIT.
Перед вызовом функции вы должны подготовить массив из 16 двойных слов, содержащих цвета для использования в меню "Custom Colors". Адрес этого массива следует передать через параметр lpCustColors.
Поле lCustData используется для передачи данных функции фильтра (если она определена).
Адрес функции фильтра передается через параметр lpfnHook. Для использования функции фильтра следует указать флаг CC_ENABLEHOOK.
Приложение GETCOLOR демонстрирует использование системных цветов и только что описанной функции ChooseColor. Оно также обрабатывает сообщение WM_SYSCOLORCHANGE.
В окне приложения (рис. 3.5) во время обработки сообщения WM_PAINT рисуется прямоугольник (при помощи специально созданной кисти и пера). Для цвета фона и кисти используются системные цвета. Для выбора цвета пера следует щелкнуть мышью в окне приложения, при этом будет вызвана функция ChooseColor.
Рис. 3.5. Главное окно приложения GETCOLOR
Экспериментируя с приложением GETCOLOR, попробуйте изменить системные цвета при помощи приложения Control Panel. Вы увидите, что после выполнения изменений изменится цвет фона окна и цвет, которым закрашена внутренняя область прямоугольника (этот цвет совпадает с цветом заголовка окна). Цвет рамки не зависит от системных цветов и устанавливается при помощи диалоговой панели.
Обратите также внимание на то, что хотя диалоговая панель позволяет вам выбирать смешанные цвета, для пера всегда используются только чистые цвета.
Так как функция ChooseColor не поддерживает работу с цветовыми палитрами, в режиме со средним цветовым разрешением (256 цветов) вам будет доступен тот же самый набор цветов, что и в режиме с низким цветовым разрешением.
Если у вас есть такая возможность, запустите приложение GETCOLOR в режиме высокого цветового разрешения True Color. Убедитесь, что в этом случае вы можете выбрать для пера любой цвет. Все цвета будут чистыми, так как в этом режиме смешанные цвета не используются.
Исходный текст приложения приведен в листинге 3.1.
Листинг 3.1. Файл getcolor/getcolor.cpp
// ---------------------------------------- // Приложение GETCOLOR // Выбор цвета, работа с системными цветами // ---------------------------------------- #define STRICT #include <windows.h> #include <windowsx.h> #include <commdlg.h> #include <mem.h> // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); BOOL GetPenColor(HWND, COLORREF *); // Имя класса окна char const szClassName[] = "GetColorClass"; // Заголовок окна char const szWindowTitle[] = "Get Color"; // Идентификатор копии приложения HINSTANCE hInst; // Идентификаторы кистей и перьев HBRUSH hbrush, hbrushOldBrush; HPEN hpen, hpenOldPen; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Инициализируем приложение if(!InitApp(hInstance)) return FALSE; // Сохраняем идентификатор копии приложения hInst = hInstance; // Создаем главное окно приложения 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.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1); 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.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: { // Создаем кисть и перо для рисования прямоугольника hbrush = CreateSolidBrush(GetSysColor(COLOR_ACTIVECAPTION)); hpen = CreatePen(PS_SOLID, 10, RGB(0,0,0)); return 0; } case WM_SYSCOLORCHANGE: { // Если кисть была создана, удаляем ее // и затем создаем заново if(hbrush) { DeleteBrush(hbrush); hbrush = CreateSolidBrush(GetSysColor(COLOR_ACTIVECAPTION)); } return 0; } // Рисование в окне case WM_PAINT: { RECT rc; // Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps); // Выбираем новую кисть и новое перо hbrushOldBrush = SelectBrush(hdc, hbrush); hpenOldPen = SelectPen(hdc, hpen); // Рисуем прямоугольник Rectangle(hdc, 10, 20, 300, 50); // Выбираем старую кисть и старое перо SelectBrush(hdc, hbrushOldBrush); SelectPen(hdc, hpenOldPen); // Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; } case WM_LBUTTONDOWN: { COLORREF clrref; // Выбираем новый цвет для пера if(GetPenColor(hwnd, &clrref)) { // Если выбрали новый цвет, удаляем перо, // а затем создаем новое DeletePen(hpen); hpen = CreatePen(PS_SOLID, 10, clrref); // Перерисовываем окно InvalidateRect(hwnd, NULL, TRUE); } return 0; } case WM_DESTROY: { // Удаляем созданные нами кисть и перо DeleteBrush(hbrush); DeletePen(hpen); PostQuitMessage(0); return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); } // --------------------------------------------------- // Функция GetPenColor // Выбор цвета пера // --------------------------------------------------- BOOL GetPenColor(HWND hwnd, COLORREF *clrref) { CHOOSECOLOR cc; COLORREF aclrCust[16]; int i; // Подготавливаем массив цветов // "Custom Colors" for (i = 0; i < 16; i++) aclrCust[i] = RGB(255, 255, 255); // Записываем нулевые значения во все // неиспользуемые поля структуры CHOOSECOLOR memset(&cc, 0, sizeof(CHOOSECOLOR)); // Заполняем необходимые поля cc.lStructSize = sizeof(CHOOSECOLOR); cc.hwndOwner = hwnd; cc.rgbResult = RGB(0, 0, 0); cc.lpCustColors = aclrCust; cc.Flags = 0; // Выбираем цвет и возвращаем результат if (ChooseColor(&cc)) { *clrref = cc.rgbResult; return TRUE; } else return FALSE; }
Обратите внимание, что в в файл исходного текста приложения включается файл commdlg.h:
#include <commdlg.h>
В этом файле описана функция ChoseColor, необходимые для нее константы и структура данных.
При регистрации класса окна для фона окна устанавливается системный цвет COLOR_APPWORKSPACE, соответствующей цвету окна MDI-приложений (примером такого приложения является Program Manager):
wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
Когда вы изменяете системные цвета, GDI автоматически перерисовывает фон окна, определенный в классе окна (если для фона окна выбран системный цвет).
В процессе создания окна обработчик сообщения WM_CREATE создает кисть и перо, с помощью которых будет нарисован прямоугольник:
hbrush = CreateSolidBrush(GetSysColor(COLOR_ACTIVECAPTION)); hpen = CreatePen(PS_SOLID, 10, RGB(0,0,0));
Перед завершением работы приложения обработчик сообщения WM_DESTROY удаляет созданные перо и кисть:
DeleteBrush(hbrush); DeletePen(hpen);
Задача обработчика сообщения WM_PAINT заключается в том, чтобы выбрать в контекст отображения перо и кисть, нарисовать прямоугольник, а затем выбрать в контекст отображения старые перо и кисть:
hbrushOldBrush = SelectBrush(hdc, hbrush); hpenOldPen = SelectPen(hdc, hpen); Rectangle(hdc, 10, 20, 300, 50); SelectBrush(hdc, hbrushOldBrush); SelectPen(hdc, hpenOldPen);
Что происходит, когда вы с помощью приложения Control Panel изменяете системные цвета?
Все окна верхнего уровня активных приложений получают сообщение WM_SYSCOLORCHANGE. В ответ на это сообщение наше приложение удаляет кисть и создает ее заново:
case WM_SYSCOLORCHANGE: { if(hbrush) { DeleteBrush(hbrush); hbrush = CreateSolidBrush(GetSysColor(COLOR_ACTIVECAPTION)); } return 0; }
Так как системные цвета изменились, константа COLOR_ACTIVECAPTION может теперь соответствовать другому цвету, поэтому в результате этой операции цвет кисти hbrush может измениться (но может и не измениться).
Заметьте, что приложение не выполняет никаких дополнительных действий, направленных на то, чтобы цвет фона окна изменялся в соответствии с изменением системных цветов - так как для цвета фона окна используется системный цвет, GDI выполняет все изменения самостоятельно.
Когда вы щелкаете левой клавишей мыши в окне приложения, обработчик сообщения WM_LBUTTONDOWN вызывает функцию GetPenColor, определенную в нашем приложении. Эта функция вызывает функцию ChooseColor, и, если вы выбрали с ее помощью цвет для пера, записывает этот цвет в переменную clrref. Затем обработчик сообщения от мыши удаляет существующее перо и создает новое, используя выбранный вами цвет. После этого он вызывает функцию InvalidateRect, что вызывает перерисовку окна приложения.
Функция GetPenColor подготавливает массив из 16 переменных типа COLORREF, содержащих белый цвет. Этот массив будет использован для инициализации цветов в области "Custom Colors" диалоговой панели выбора цветов.
Затем она инициализирует нужные поля структуры CHOOSECOLOR, записывая в остальные поля нулевые значения, после чего вызывает функцию ChooseColor.
Файл определения модуля для приложения GETCOLOR приведен в листинге 3.2.
Листинг 3.2. Файл getcolor/getcolor.def
; ============================= ; Файл определения модуля ; ============================= NAME GETCOLOR DESCRIPTION 'Приложение GETCOLOR, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
В большинстве случаев вам не потребуется использовать палитры Windows, так как далеко не все приложения работают с большим количеством цветов. Приложения для обычной презентационной графики (схемы, диаграммы и т. п.), обработки текстовых документов и другие аналогичные приложения выглядят вполне удовлетворительно при использовании 16 цветов, обеспечиваемых стандартным драйвером VGA. Более того, увеличение цветового разрешения, как правило, сказывается отрицательно на производительности Windows. И это понятно - чем больше используется цветов, тем больше объем данных, передаваемых из процессора в видеоконтроллер. Несмотря на то что акселераторы Windows значительно ускоряют работу, очень много пользователей имеют дешевые видеоконтроллеры, работающие в режиме низкого или, в крайнем случае, среднего цветового разрешения.
Если же ваше приложение должно работать с многокрасочными реалистическими изображениями или выводить на экран битовые изображения, полученные с помощью цветного сканера, задача использования цветовых палитр выдвигается на первый план.
Оценивая отношение цветового разрешения к цене для разрешения 256 цветов, многие пользователи склоняются не к акселераторам с объемом видеопамяти 1-2 Мбайт (цена которых находится в диапазоне 100-400 долларов), а к обычным видеоадаптерам SVGA с объемом видеопамяти 512 Кбайт (цена порядка 40 долларов). Возможно, это неправильно, так как акселераторы и в самом деле заметно ускоряют работу Windows, однако едва ли вам понравится идея убеждать потенциальных покупателей вашего приложения обновить свой компьютер.
Поэтому приложение должно использовать все цветовые возможности обычных видеоадаптеров SVGA, которые реализуются с помощью механизма цветовых палитр.
Вы можете спросить: будет ли ваше приложение, рассчитанное на использование цветовых палитр, работать с современными видеоадаптерами в режиме True Color, для которых механизм палитр не предусмотрен (так как в этом случае он не нужен)? Будет, так как GDI все равно сможет (и даже с большим успехом) удовлетворить цветовые запросы приложения.
В самом начале этой главы мы рассказали вам о системной цветовой палитре. Вы знаете, что в системной палитре, состоящей из 256 ячеек, 20 ячеек зарезервированы для статических цветов. Остальные 236 ячеек доступны для приложений. Что же приложения могут с ними сделать?
Любое приложение может создать свою собственную палитру цветов в виде массива размером до 256 элементов, содержащего данные типа PALETTEENTRY, которые могут хранить RGB-цвета или номера цветов в системной палитре.
Подготовив массив, содержащий цвета, приложение может создать логическую палитру, вызвав функцию CreatePalette. Затем палитру нужно выбрать в контекст отображения функцией SelectPalette. Так как на экране могут отображаться только цвета, находящиеся в системной палитре, в процессе реализации палитры приложение должно перенести (или отобразить) цвета из логической палитры в системную палитру, вызвав функцию RealizePalette. Последний шаг называется реализацией палитры.
В зависимости от того, как подготовлена логическая палитра, а также от того, является приложение активным или фоновым, механизм реализации может выполняться по-разному. Рассмотрим обычный способ реализации.
Первоначально в системной палитре 20 ячеек отмечены как занятые для статических цветов и 236 - как свободные. Когда первое приложение реализует свою логическую палитру, цвета из этой палитры переписываются в свободные ячейки системной палитры, после чего они становятся доступными для отображения. Если свободных ячеек не хватает для размещения всей логической палитры, оставшиеся цвета будут отображены на ближайшие имеющиеся в системной палитре. При этом неизбежны цветовые искажения.
Однако в Windows одновременно может работать несколько приложений, одно из которых является активным, а остальные - фоновыми. Для активных и фоновых приложений используется разный механизм реализации логической палитры.
Активное приложение имеет больший приоритет в использовании свободных ячеек системной палитры. Как правило, запрос активного приложения (точнее говоря, активного окна) на реализацию логической палитры выполняется полностью, так как перед реализацией все ячейки системной палитры (кроме 20 зарезервированных) отмечаются как свободные.
Фоновые приложения довольствуются теми свободными ячейками, что остались после реализации логической палитры активного приложения. Поэтому для фоновых приложений качество "цветопередачи" может быть не очень высоким.
На рис. 3.6 показан процесс реализации логической палитры для активного окна.
Рис. 3.6. Реализация логической палитры для активного окна
На этом рисунке для наглядности мы уменьшили размер системной палитры. Разные цвета показаны различной штриховкой соответствующих прямоугольников. Из рисунка видно, что после реализации логической палитры активного окна в системной палитре осталось три свободные ячейки.
Теперь допустим, что фоновое приложение реализует логическую палитру для своего окна. В его распоряжении есть три свободные ячейки. Процесс реализации логической палитры для фонового окна показан на рис. 3.7.
Рис. 3.7. Реализация логической палитры для фонового окна
Часть цветов логической палитры фонового окна уже есть в системной палитре, поэтому они просто отображаются на соответствующие ячейки системной палитры. Три свободные ячейки используются для тех цветов, которых нет в системной палитре.
Однако в нашей логической палитре есть цвета, которым нет точного соответствия в системной палитре, причем свободных ячеек тоже не осталось. В этом случае GDI отображает эти цвета на близкие из системной палитры (на рис. 3.7 такое отображение показано пунктирной линией).
После такого упрощенного описания процесса реализации логической палитры перейдем к конкретным шагам, необходимым для использования палитр.
Далеко не все устройства отображения способны работать с палитрами. Например, драйвер видеоадаптера VGA палитры не поддерживает, несмотря на то что аппаратура VGA может работать с палитрой. Последнее обстоятельство связано с тем, что размер палитры VGA составляет всего 64 ячейки, что явно недостаточно.
Самый лучший способ убедиться в том, что драйвер видеоадаптера способен работать с палитрами, заключается в вызове функции GetDeviceCaps с параметром RASTERCAPS (мы обсуждали эту функцию в 11 томе "Библиотеки системного программиста"). Если в возвращенном слове установлен бит RC_PALETTE, приложение может использовать цветовые палитры.
Стандартный размер системной палитры равен 256 ячеек, однако вы можете уточнить это значение, вызвав функцию GetDeviceCaps с параметром SIZEPALETTE. Если драйвер видеоконтроллера не работает с палитрами, размер палитры, определенный с помощью функции GetDeviceCaps, может быть равным 0.
Для определения количества системных цветов используйте эту же функцию с параметром NUMRESERVED.
С помощью функции GetDeviceCaps можно также определить цветовое разрешение устройства вывода. При этом следует учитывать, что некоторые устройства работают с цветовыми плоскостями. Количество этих плоскостей можно определить, пользуясь значением PLANES. Отметим, что количество цветов, которые могут быть представлены устройством с цветовыми плоскостями, равно 2n, где n - количество цветовых плоскостей.
Если устройство работает с цветовыми плоскостями и использует несколько бит для представления цвета одного пиксела, количество одновременно отображаемых цветов можно определить по формуле:
nColors = 2(nPixel * nPlanes),
где nPixel - количество битов, используемых для представления цвета пиксела (значение BITSPIXEL ); nPlanes - количество цветовых плоскостей (значение PLANES).
Значение NUMCOLORS равно количеству статических цветов. Так как палитра может быть изменена (перезагружена), фактически вы можете использовать больше цветов, чем указано в NUMCOLORS. Но в этом случае вы должны сами перезагружать палитру. Для устройств, работающих с палитрами, правильное количество используемых цветов возвращается при использовании значения COLORRES - количество бит цветового разрешения. Для режима с использованием 256 цветов это значение равно 18, что соответствует 6 битам для представления каждого из трех базовых цветов.
В 11 томе "Библиотеки системного программиста" мы описали приложение DCAPS, с помощью которого можно исследовать возможности драйверов различных видеоконтроллеров. Приведем параметры, имеющие отношение к цветовому разрешению, полученные с помощью этого приложения для разных режимов.
Параметр |
VGA, 16 цветов |
SVGA Cirrus Logic, 256 цветов |
SVGA Cirrus Logic, 65536 цветов |
SVGA Cirrus Logic, 16.7 млн. цветов |
RASTERCAPS, бит RC_PALETTE |
0 |
1 |
0 |
0 |
SIZEPALETTE |
0 |
256 |
0 |
0 |
NUMRESERVED |
0 |
20 |
0 |
0 |
PLANES |
4 |
1 |
1 |
1 |
BITSPIXEL |
1 |
8 |
16 |
24 |
NUMCOLORS |
16 |
20 |
4096 |
4096 |
COLORRES |
0 |
18 |
0 |
0 |
Из этой таблицы видно, что драйвер VGA не работает с палитрами. В режимах высокого цветового разрешения палитры также не используются.
Для того чтобы создать палитру, ваше приложение должно заполнить структуру LOGPALETTE, описывающую палитру, и массив структур PALETTEENTRY, определяющий содержимое палитры.
Структура LOGPALETTE и указатели на нее определены в файле windows.h:
typedef struct tagLOGPALETTE { WORD palVersion; WORD palNumEntries; PALETTEENTRY palPalEntry[1]; } LOGPALETTE; typedef LOGPALETTE* PLOGPALETTE; typedef LOGPALETTE NEAR* NPLOGPALETTE; typedef LOGPALETTE FAR* LPLOGPALETTE;
Поле palVersion для Windows версии 3.0 и 3.1 должно содержать значение 0x300.
В поле palNumEntries нужно записать размер палитры (количество элементов в массиве структур PALETTEENTRY).
Сразу после структуры LOGPALETTE в памяти должен следовать массив структур PALETTEENTRY, описывающих содержимое палитры:
typedef struct tagPALETTEENTRY { BYTE peRed; BYTE peGreen; BYTE peBlue; BYTE peFlags; } PALETTEENTRY; typedef PALETTEENTRY FAR* LPPALETTEENTRY;
Поле peFlags определяет тип элемента палитры и может иметь значения NULL, PC_EXPLICIT, PC_NOCOLLAPSE и PC_RESERVED.
Если поле peFlags содержит значение NULL, в полях peRed, peGreen и peBlue находятся RGB-компоненты цвета. В процессе реализации логической палитры для этого элемента используется описанный нами ранее алгоритм.
Если поле peFlags содержит значение PC_EXPLICIT, младшее слово элемента палитры содержит индекс цвета в системной палитре.
Если поле peFlags содержит значение PC_NOCOLLAPSE, в процессе реализации логической палитры данный элемент будет отображаться только на свободную ячейку системной палитры. Если же свободных ячеек нет, используется обычный алгоритм реализации.
Последнее возможное значение для поля peFlags (PC_RESERVED) используется для анимации палитры с помощью функции AnimatePalette. Анимация палитры позволяет динамически вносить изменения в палитру. Такой элемент палитры после реализации не подвергается изменениям при реализации других палитр, он становится зарезервированным.
После подготовки структуры LOGPALETTE и массива структур PALETTEENTRY приложение может создать логическую палитру, вызвав функцию CreatePalette :
HPALETTE WINAPI CreatePalette(const LOGPALETTE FAR* lplgpl);
В качестве параметра следует передать функции указатель на заполненную структуру LOGPALETTE.
Функция возвращает идентификатор созданной палитры или NULL при ошибке.
Созданная палитра перед использованием должна быть выбрана в контекст отображения. Выбор палитры выполняется функцией SelectPalette :
HPALETTE WINAPI SelectPalette( HDC hdc, HPALETTE hpal, BOOL fPalBack);
Функция выбирает палитру hpal в контекст отображения hdc, возвращая в случае успеха идентификатор палитры, которая была выбрана в контекст отображения раньше. При ошибке возвращается значение NULL.
Указав для параметра fPalBack значение TRUE, вы можете заставить GDI в процессе реализации палитры использовать алгоритм, соответствующий фоновому окну. Если же этот параметр равен FALSE, используется алгоритм для активного окна (т. е. все ячейки системной палитры, кроме зарезервированных для статических цветов, отмечаются как свободные и используются для реализации палитры).
Процедура реализации палитры заключается в вызове функции RealizePalette :
UINT WINAPI RealizePalette(HDC hdc);
Возвращаемое значение равно количеству цветов логической палитры, которое удалось отобразить в системную палитру.
Итак, вы создали палитру, выбрали ее в контекст отображения и реализовали. Теперь приложение может пользоваться цветами из созданной палитры. Но как?
Если приложению нужно создать перо или кисть, определить цвет текста функцией SetTextColor или закрасить область функцией FloofFill (т. е. вызвать одну из функций, которой в качестве параметра передается переменная типа COLORREF, содержащая цвет), вы можете вместо макрокоманды RGB воспользоваться одной из следующих макрокоманд:
#define PALETTEINDEX (i) \ ((COLORREF)(0x01000000L | (DWORD)(WORD)(i))) #define PALETTERGB (r,g,b) (0x02000000L | RGB(r,g,b))
Макрокоманда PALETTEINDEX позволяет указать вместо отдельных компонент цвета индекс в логической палитре, соответствующий нужному цвету.
Макрокоманда PALETTERGB имеет параметры, аналогичные знакомой вам макрокоманде RGB, однако работает по-другому.
Если цвет определен с помощью макрокоманды RGB, в режиме низкого и среднего цветового разрешения для рисования будет использован ближайший к указанному статический цвет. В режиме высокого цветового разрешения полученный цвет будет полностью соответствовать запрошенному.
Если же для определения цвета использована макрокоманда PALETTERGB, GDI просмотрит системную палитру и подберет из нее цвет, наилучшим образом соответствующий указанному в параметрах макрокоманды.
В любом случае при работе с палитрой GDI не использует для удовлетворения запроса из логической палитры смешанные цвета.
Какой из двух макрокоманд лучше пользоваться?
На этот вопрос нет однозначного ответа.
Макрокоманда PALETTEINDEX работает быстрее, однако с ее помощью можно использовать только те цвета, которые есть в системной палитре. Если ваше приложение будет работать в режиме True Color, лучшего эффекта можно добиться при использовании макрокоманды PALETTERGB, так как для режимов высокого цветового разрешения эта макрокоманда обеспечит более точное цветовое соответствие.
Так как палитра является объектом, принадлежащим GDI, а не создавшему ее приложению, после использования палитры приложение должно обязательно ее удалить. Для удаления логической палитры лучше всего воспользоваться макрокомандой DeletePalette, определенной в файле windowsx.h:
#define DeletePalette(hpal) \ DeleteObject((HGDIOBJ)(HPALETTE)(hpal))
В качестве параметра этой макрокоманде следует передать идентификатор удаляемой палитры.
Учтите, что как и любой другой объект GDI, нельзя удалять палитру, выбранную в контекст отображения. Перед удалением следует выбрать старую палитру, вызвав функцию SelectPalette.
Если ваше приложение активно работает с палитрами, оно должно иметь в виду, что одновременно вместе с ним могут работать и другие приложения, претендующие на изменение системной палитры. Для того чтобы при передаче фокуса ввода другому приложению изображение, построенное с использованием палитры, не оказалось испорчено в результате изменения системной палитры другим приложением, ваше приложение должно обрабатывать сообщения WM_QUERYNEWPALETTE и WM_PALETTECHANGED.
Сообщение WM_QUERYNEWPALETTE посылается окну, которое становится активным. Это сообщение не имеет параметров.
Главное окно приложения может менять свой статус с активного на фоновое несколько раз. Каждый раз, когда оно становится активным, ему посылается сообщение WM_QUERYNEWPALETTE. В ответ на это сообщение приложение должно заново реализовать свою логическую палитру, так как пока его главное окно было неактивно, другое приложение могло изменить системную палитру. Если палитра изменилась, обработчик сообщения WM_QUERYNEWPALETTE должен перерисовать окно.
Если обработчик сообщения WM_QUERYNEWPALETTE изменил системную палитру, он должен вернуть ненулевое значение, а если нет - нулевое.
Когда любое приложение изменяет системную палитру, все перекрывающиеся (overlapped) и временные (pop up) окна получают сообщение WM_PALETTECHANGED. Это сообщение посылается также в окно приложения, которое выполнило изменение системной палитры.
Параметр wParam сообщения WM_PALETTECHANGED содержит идентификатор окна, изменившего системную палитру.
Если приложение обрабатывает это сообщение, оно должно вернуть нулевое значение.
В ответ на сообщение WM_PALETTECHANGED приложение должно заново реализовать палитру и, если палитра изменилась, перерисовать окно. Вместо полной перерисовки окна можно обновить цвета в окне, вызвав функцию UpdateColors :
int WINAPI UpdateColors(HDC hdc);
Следует, однако, иметь в виду, что обновление цветов может привести к деградации качества изображения, поэтому при изменении палитры лучше перерисовать окно заново.
Для демонстрации методов работы с цветовыми палитрами мы подготовили приложение 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
В завершение третьей главы приведем исходные тексты приложения SYSPAL, предназначенного для просмотра системной палитры. Оно работает только тогда, когда драйвер видеоадаптера использует цветовые палитры, поэтому вы не сможете запустить его в режимах с низким или высоким цветовым разрешением.
С помощью приложения SYSPAL вы сможете визуально проследить за изменениями системной палитры, например, при загрузке bmp-файлов в приложение Paint Brush, при запуске приложения PALETTE или других приложений, изменяющих системную палитру.
Это приложение во многом напоминает предыдущее, поэтому для экономии места мы сократили количество комментариев в его исходном тексте (листинг 3.5).
Листинг 3.5. Файл syspalet/syspal.cpp
// ---------------------------------------- // Приложение SYSPAL // Просмотр системной цветовой палитры // ---------------------------------------- #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); BOOL PaletteInfo(void); char const szClassName[] = "SysPalClass"; char const szWindowTitle[] = "System Palette"; 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; if(!PaletteInfo()) return FALSE; 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; // Младшее слово структуры PALETTEENTRY содержит поля // peRed и peGreen, а старшее - peBlue и peFlags. // Отмечаем все элементы палитры флагом PC_EXPLICIT for (i = 0; i < PALETTESIZE; i++) { pLogPal->palPalEntry[i].peBlue = 0; *((PWORD)(&pLogPal->palPalEntry[i].peRed)) = i; pLogPal->palPalEntry[i].peFlags = PC_EXPLICIT; } // Создаем логическую палитру 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; for (i=0; i < 256; 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: { if (hwnd == (HWND) wParam) break; } case WM_QUERYNEWPALETTE: { HDC hdc; HPALETTE hOldPal; int nChanged; hdc = GetDC(hwnd); 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 // Проверка возможности работы с палитрами // -------------------------------------------------------- BOOL PaletteInfo(void) { HDC hdc; int iRasterCaps; hdc = GetDC(NULL); iRasterCaps = GetDeviceCaps(hdc, RASTERCAPS); ReleaseDC(NULL, hdc); // Проверяем, используется ли механизм палитр if(iRasterCaps & RC_PALETTE) return TRUE; else return FALSE; }
Так же, как и приложение PALETTE, приложение SYSPAL создает логическую палитру для 256 цветов. Однако палитра заполняется по другому:
for (i = 0; i < PALETTESIZE; i++) { pLogPal->palPalEntry[i].peBlue = 0; *((PWORD)(&pLogPal->palPalEntry[i].peRed)) = i; pLogPal->palPalEntry[i].peFlags = PC_EXPLICIT; } hPal = CreatePalette((LPLOGPALETTE) pLogPal);
Все элементы палитры отмечаются флагом PC_EXPLICIT. Это означает, что палитра содержит не цвета, а индексы цветов системной палитры. Точнее, младшее слово каждого элемента палитры содержит индекс цвета системной палитры. Структура PALETTEENTRY описана в файле windows.h следующим образом:
typedef struct tagPALETTEENTRY { BYTE peRed; BYTE peGreen; BYTE peBlue; BYTE peFlags; } PALETTEENTRY;
При этом младшее слово структуры PALETTEENTRY содержит поля peRed и peGreen, а старшее - peBlue и peFlags. Мы пользуемся этим обстоятельством, записывая в младшее слово значения от 0 до 255 (индекс в системной табице цветов), в поле peBlue - нулевое значение, а в поле peFlags - значение PC_EXPLICIT.
Созданная таким образом палитра используется также, как и в приложении PALETTE.
Файл определения модуля приложения SYSPAL приведен в листинге 3.6.
Листинг 3.6. Файл syspalet/syspal.def
; ============================= ; Файл определения модуля ; ============================= NAME SYSPAL DESCRIPTION 'Приложение SYSPAL, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple