On-Line Библиотека www.XServer.ru - учебники, книги, статьи, документация, нормативная литература.
       Главная         В избранное         Контакты        Карта сайта   
    Навигация XServer.ru








 

Что такое цветовая палитра ?

Если вы видели художника за работой, вы уже знаете ответ на этот вопрос. Цветовая палитра - это не более чем набор цветов. Художник создает палитру из различных красок, смешивая их. Полученный набор красок используется для рисования.

В GDI встроены средства для работы с 256-цветными палитрами. Если видеоконтроллер способен работать с палитрами, создается одна системная палитра , которая содержит отображаемые на экране цвета. Вы можете думать об этой палитре как о таблице цветов, хранящейся в памяти видеоконтроллера.

Часть системной палитры (20 элементов) зарезервированы для использования операционной системой. В зарезервированных элементах хранятся статические цвета, которые нужны для рисования таких объектов, как рамки окон, полосы просмотра и т. п., а также изображений, рисуемых приложением. Если видеоконтроллер работает в режиме низкого цветового разрешения или приложение не использует цветовые палитры (несмотря на наличие соответствующих возможностей аппаратуры), цветовая гамма приложения ограничена статическими цветами.

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

Схематически системная цветовая палитра изображена на рис. 3.1.

3.1. Системная цветовая палитра

Ниже мы перечислим все статические цвета , указав для каждого цвета комбинацию соответствующих RGB-компонент (в шестнадцатеричном представлении).

Индекс в системной палитреR (красный цвет) G (зеленый цвет)B (голубой цвет) Цвет в палитре
00000 00черный
18000 00темно-красный
20080 00темно-зеленый
38080 00темно-желтый
40000 80темно-голубой
58000 80темно-малиновый
60080 80темно-синий
7C0C0 C0светло-серый
8С0DC C0светло-зеленый
9A6CA F0светло-голубой
246FFFB F0кремовый
247A0A0 A4светло-серый
2488080 80серый
249FF0 0красный
2500FF 0зеленый
251FFFF 0желтый
25200 FFсиний
253FF0 FFмалиновый
2540FF FFголубой (циан)
255FFFF FFбелый

Странное на первый взгляд расположение статических цветов в системной палитре (десять цветов находятся в начале таблицы, десять - в конце) выбрано для обеспечения правильной работы часто используемой растровой операции "ИСКЛЮЧАЮЩЕЕ ИЛИ".

3.2. Выбор цвета без использования палитры

Приложения, которые не хотят ничего знать про палитры, могут указывать логический цвет изображений, составляя его из 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_SYSCOLORCHANGE посылается всем активным окнам верхнего уровня при изменении системных цветов. В ответ на это сообщение приложения, которые создают свои перья и кисти на базе системных цветов, должны удалить эти перья и кисти, а затем создать их заново.

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

Сообщение WM_SYSCOLORCHANGE не имеет параметров, поэтому значения, передаваемые через wParam и lParam следует проигнорировать.

Функция ChooseColor

В составе 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.

3.3. Приложение GETCOLOR

Приложение 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 заключается в том, чтобы выбрать в контекст отображения перо и кисть, нарисовать прямоугольник.
Современными видеоадаптерами в режиме 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_PALETTE0 100
SIZEPALETTE0256 00
NUMRESERVED020 00
PLANES41 11
BITSPIXEL18 1624
NUMCOLORS 1620 40964096
COLORRES018 00

Из этой таблицы видно, что драйвер 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 должен перерисовать окно.

Если обработчик сообщения WM_QUERYNEWPALETTE изменил системную палитру, он должен вернуть ненулевое значение, а если нет - нулевое.

Сообщение WM_PALETTECHANGED

Когда любое приложение изменяет системную палитру, все перекрывающиеся (overlapped) и временные (pop up) окна получают сообщение WM_PALETTECHANGED . Это сообщение посылается также в окно приложения, которое выполнило изменение системной палитры.

Параметр wParam сообщения WM_PALETTECHANGED содержит идентификатор окна, изменившего системную палитру.

Если приложение обрабатывает это сообщение, оно должно вернуть нулевое значение.

В ответ на сообщение WM_PALETTECHANGED приложение должно заново реализовать палитру и, если палитра изменилась, перерисовать окно. Вместо полной перерисовки окна можно обновить цвета в окне, вызвав функцию UpdateColors :

int WINAPI UpdateColors(HDC hdc);

Следует, однако, иметь в виду, что обновление цветов может привести к деградации качества изображения, поэтому при изменении палитры лучше перерисовать окно заново.

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

3.6. Приложение SYSPAL

В завершение третьей главы приведем исходные тексты приложения 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
Назад       Содержание       Вперёд