Рисование геометрических фигур
В этом разделе мы расскажем вам об использовании функций, предназначенных для рисования точек, линий, окружностей и других геометрических фигур.
Несмотря на то, что в программном интерфейсе GDI имеется функция SetPixel , позволяющая нарисовать один пиксел, не следует думать, что рисование линии или окружности сводится к многократному вызову этой функции. Если бы это было так, процесс рисования занимал бы очень много времени. На самом деле многие из функций рисования выполняются драйвером или даже аппаратурой видеоконтроллера, что значительно ускоряет вывод.
С помощью функции GetDeviceCaps приложение может определить, поддерживает ли драйвер ту или иную функцию рисования.
Первый параметр функции hdc задает контекст устройства, для которого необходимо получить информацию о его возможностях.
Второй параметр iCapability определяет параметр устройства, значение которого необходимо получить.
Приведем список значений для второго параметра функции GetDeviceCaps, с помощью которых можно определить, какие операции рисования выполняет драйвер устройства вывода.
Имя константы | Описание |
LINECAPS | Способности устройства рисовать линии. Возвращаемое значение представляет собой набор битовых масок, установленных в 1, если устройство может само рисовать линии различного типа:LC_INTERIORS устройство может закрашивать внутреннюю область;LC_MARKER маркеры;LC_NONE устройство не может рисовать линии;LC_POLYLINE ломаные линии;LC_POLYMARKER линии polymarker;LC_STYLED устройство может рисовать линии с использованием различных стилей (штриховые, пунктирные, штрих пунктирные и т.д.);LC_WIDE широкие линии;LC_WIDESTILED устройство может рисовать широкие линии с использованием различных стилей (штриховые, пунктирные, штрих-пунктирные и т. д.) |
CURVECAPS | Способность устройства рисовать различные кривые линии и геометрические фигуры. Возвращаемое значение представляет собой набор битовых масок, установленных в 1, если устройство может само рисовать различные фигуры:CC_CIRCLES окружности;CC_CHORD сегмент эллипса;CC_ELLIPSES эллипсы;CC_INTERIORS устройство может закрашивать внутреннюю область геометрических фигур;CC_NONE устройство не может рисовать кривые линии и геометрические фигуры;CC_PIE секторы эллипса;CC_ROUNDRECT прямоугольники со скругленными углами;CC_STYLED устройство может рисовать рамки с использованием различных стилей (штриховые, пунктирные, штрих-пунктирные и т.д.);CC_WIDE широкие рамки;CC_WIDESTYLED устройство может рисовать широкие рамки с использованием различных стилей (штриховые, пунктирные, штрих-пунктирные и т. д.) |
POLYGONALCAPS | Способности устройства рисовать многоугольники. Возвращаемое значение представляет собой набор битовых масок, установленных в 1, если устройство может само рисовать многоугольники различного типа:PC_INTERIORS устройство может закрашивать внутреннюю область;PC_NONE устройство не может рисовать многоугольники;PC_RECTANGLE прямоугольники;PC_SCANLINES устройство может выполнять сканирование линий растра;PC_STYLED устройство может рисовать рамки с использованием различных стилей (штриховые, пунктирные, штрих-пунктирные и т. д.);PC_WIDE широкие рамки;PC_WIDESTILED устройство может рисовать широкие рамки с использованием различных стилей (штриховые, пунктирные, штрих-пунктирные и т. д.)PC_WINDPOLYGON многоугольники с заполнением в режиме WINDING |
Для приложения не имеет особого значения, кто именно будет рисовать - видеоконтроллер, драйвер или GDI. Запрос на рисование, например, эллипса, будет выполнен, даже если соответствующая операция не поддерживается драйвером. В последнем случае эллипс будет нарисован самим GDI с использованием более примитивных операций, но процесс рисования займет больше времени.
Учитывая сказанное выше, не следует строить работу приложений таким образом, чтобы периодичность вывода или скорость работы приложения зависела от скорости рисования (подобная практика не приветствуется и при создании программ для MS-DOS, вспомните, как ведут себя старые игры, разработанные для процессора 8088, на компьютерах с процессорами i80386 или i486). Современные видеоадаптеры сконструированы таким образом, что большинство основных операций рисования, используемых в операционной системе Windows, выполняются аппаратно. Эти видеоадаптеры иногда называются ускорителями Windows. Скорость рисования для ускорителя Windows может превышать в десятки раз скорость рисования для обычного адаптера VGA или SVGA.
Результат рисования геометрических фигур зависит от установки таких атрибутов контекста, как ширина, цвет и стиль линии (определяются выбранным в контекст отображения пером), способ закраски замкнутых фигур (определяется выбранной в контекст отображения кистью), цвета фона, прозрачностью фона (прозрачный режим TRANSPARENT и непрозрачный режим OPAQUE ), режимом рисования, режимом закрашивания, областью ограничения, режимом отображения, т. е. практически от всех атрибутов контекста отображения. Поэтому при описании функций мы будем попутно описывать способы изменения атрибутов контекста отображения, влияющих на результат их выполнения.
Работа с цветовыми палитрами и битовыми изображениями будут рассмотрены позже в отдельных разделах, так как эти вопросы далеко не тривиальны и поэтому заслуживают отдельного обсуждения.
Итак, перейдем непосредственно к описанию функций рисования геометрических фигур.
Рисование изображений DIB
Процесс рисования изображений DIB включает в себя несколько этапов.
Сначала необходимо загрузить bmp-файл в оперативную память и убедиться в том, что этот файл действительно содержит изображение DIB. Ваше приложение может полностью проигнорировать bmp-файлы в формате Presentation Manager (как это делает, например, приложение Paintbrush в Windows версии 3.1) или выполнить их преобразование в формат Windows, что намного лучше. Следует также проверить формат заголовка BITMAPINFOHEADER.
Затем нужно определить размер таблицы цветов (если она есть). Если в DIB есть таблица цветов, ее следует преобразовать в палитру. Непосредственно перед рисованием изображения DIB созданная палитра должна быть выбрана в контекст отображения и реализована. Если bmp-файл содержит изображение с высоким цветовым разрешением, в файле нет таблицы цветов. В этом случае нет необходимости создавать палитру.
После создания палитры следует определить адрес битов изображения. Напомним, что смещение битов изображения находится в поле bfOffBits структуры BITMAPFILEHEADER. Если содержимое этого поля равно нулю, можно вычислить адрес битов изображения исходя из размера заголовков и размера таблицы цветов.
В заключение считанное и проверенное изображение DIB можно нарисовать, использовав один из двух способов.
Первый способ заключается в преобразовании изображения DIB в изображение DDB с помощью функции SetDIBits. Полученное таким образом изображение DDB может быть выбрано в контекст памяти и нарисовано обычным способом при помощи функции BitBlt или StretchBlt.
Второй способ основан на использовании функции StretchDIBits, которая сама выполняет необходимые преобразования, однако в некоторых случаях работает медленнее функции BitBlt.
Если изображение DIB содержит таблицу цветов и устройство вывода способно работать с цветовыми палитрами, ваше приложение должно обрабатывать сообщения WM_PALETTECHANGED и WM_QUERYNEWPALETTE. Для обработки этих сообщений можно использовать алгоритм, описанный в главе "Цвет и цветовые палитры".
Рисование изображения DDB
Итак, мы загрузили битовое изображение в память и определили его параметры. Теперь наша задача заключается в том, чтобы нарисовать загруженное изображение в окне приложения.
Как мы уже говорили, в программном интерфейсе Windows (а точнее, в программном интерфейсе GDI) нет функции, предназначенной для рисования битовых изображений. Как же быть?
Используется следующая последовательность действий.
Прежде всего надо создать контекст памяти, совместимый с контекстом отображения реального устройства вывода. Для этого следует воспользоваться функцией CreateCompatibleDC.
Далее необходимо выбрать предварительно загруженное битовое изображение в контекст памяти с помощью функции SelectObject, указав ей в качестве параметров идентификатор контекста памяти и идентификатор загруженного изображения.
Затем нужно скопировать биты изображения из контекста памяти в контекст отображения, вызвав функцию BitBlt или StretchBlt. При этом изображение будет нарисовано на устройстве вывода, которое соответствует контексту отображения.
Рассмотрим реализацию этой последовательности действий на примере функции DrawBitmap , которую мы использовали в приложениях, описанных в предыдущих томах "Библиотеки системного программиста":
// ====================================================== // Рисование изображения типа bitmap // ====================================================== #define STRICT #include <windows.h>
void DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap) { HBITMAP hbm, hOldbm; HDC hMemDC; BITMAP bm; POINT ptSize, ptOrg;
// Создаем контекст памяти, совместимый // с контекстом отображения hMemDC = CreateCompatibleDC(hDC);
// Выбираем изображение bitmap в контекст памяти hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap);
// Если не было ошибок, продолжаем работу if (hOldbm) { // Для контекста памяти устанавливаем тот же // режим отображения, что используется в // контексте отображения SetMapMode(hMemDC, GetMapMode(hDC));
// Определяем размеры изображения GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&bm);
ptSize.x = bm.bmWidth; // ширина ptSize.y = bm.bmHeight; // высота
// Преобразуем координаты устройства в логические // для устройства вывода DPtoLP(hDC, &ptSize, 1);
ptOrg.x = 0; ptOrg.y = 0;
// Преобразуем координаты устройства в логические // для контекста памяти DPtoLP(hMemDC, &ptOrg, 1);
// Рисуем изображение bitmap BitBlt(hDC, x, y, ptSize.x, ptSize.y, hMemDC, ptOrg.x, ptOrg.y, SRCCOPY);
// Восстанавливаем контекст памяти SelectObject(hMemDC, hOldbm); }
// Удаляем контекст памяти DeleteDC(hMemDC); }
В качестве параметров этой функции передается идентификатор контекста отображения hDC, в котором необходимо нарисовать изображение, координаты x и y верхнего левого угла прямоугольной области, в которой будет нарисовано изображение, а также идентификатор самого изображения hBitmap.
Прежде всего функция DrawBitmap создает контекст памяти, совместимый с контекстом отображения, передаваемого через параметр hDC:
hMemDC = CreateCompatibleDC(hDC);
В качестве параметра для этой функции можно использовать значение NULL. В этом случае будет создан контекст памяти, совместимый с экраном видеомонитора. Функция возвращает идентификатор созданного контекста или NULL при ошибке.
Далее функция DrawBitmap выбирает изображение в созданный контекст памяти:
hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap);
Функция SelectObject возвращает идентификатор битового изображения, которое было выбрано в контекст памяти раньше. Так как мы только что создали контекст памяти, мы получим идентификатор битового изображения, выбранного в контекст памяти по умолчанию. Это монохромное изображение, состоящее из одного пиксела. Контекст памяти необходимо удалить после использования. Перед удалением мы должны выбрать в него изображение, которое было выбрано при создании, т. е. изображение с идентификатором hOldbm.
Теперь мы выбрали наше изображение в контекст памяти и готовы выполнить копирование в контекст отображения. Однако перед этим необходимы некоторые подготовительные действия.
Прежде всего нужно сделать так, чтобы в контексте памяти использовался тот же режим отображения, что и в контексте отображения. По умолчанию при создании контекста памяти (как и любого другого контекста) устанавливается режим отображения MM_TEXT. Однако в контексте отображения, идентификатор которого передается функции DrawBitmap, может быть установлен любой режим отображения, например, метрический. Для обеспечения соответствия режимов отображения удобно использовать функции GetMapMode и SetMapMode :
SetMapMode(hMemDC, GetMapMode(hDC));
Функция GetMapMode возвращает код режима отображения, установленного в контексте отображения. Этот код передается в качестве второго параметра функции SetMapMode, которая устанавливает такой же режим отображения в контексте памяти.
Так как функции BitBlt , копирующей биты изображения, необходимо указать координаты прямоугольной области, занимаемой изображением в контексте памяти, следует определить размеры изображения. Это можно сделать с помощью функции GetObject, которую мы только что описали:
GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm);
В данном случае нас интересуют ширина и высота изображения в пикселах:
ptSize.x = bm.bmWidth; // ширина ptSize.y = bm.bmHeight; // высота
Так как в контексте отображения может быть выбран любой режим отображения, размеры изображения необходимо преобразовать в логические, вызвав функцию DPtoLP:
DPtoLP(hDC, &ptSize, 1);
Об этой функции мы рассказывали в разделе, посвященной режимам отображения.
Для копирования битов изображения из контекста памяти в контекст отображения функция DrawBitmap использует функцию BitBlt (читается как "бит-блит"):
BOOL WINAPI BitBlt( HDC hdcDest, // контекст для рисования int nXDest, // x-координата верхнего левого угла // области рисования int nYDest, // y-координата верхнего левого угла // области рисования int nWidth, // ширина изображения int nHeight, // высота изображения HDC hdcSrc, // идентификатор исходного контекста int nXSrc, // x-координата верхнего левого угла // исходной области int nYSrc, // y-координата верхнего левого угла // исходной области DWORD dwRop); // код растровой операции
Функция копирует битовое изображение из исходного контекста hdcSrc в контекст отображения hdcDest. Возвращаемое значение равно TRUE при успешном завершении или FALSE при ошибке.
Размеры копируемого изображения задаются парамерами nWidth и nHeight. Координаты левого верхнего угла изображения в исходном контексте определяются параметрами nXSrc и nYSrc, а в контексте, куда копируется изображение, параметрами nXDest и nYDest.
Последний параметр dwRop определяет растровую операцию, используемую для копирования.
Отметим, что размеры и координаты необходимо задавать в логических единицах, соответствующих выбранному режиму отображения.
В нашем случае изображение копируется из точки (0,0) в физической системе координат в точку (x,y) в логической системе координат. Поэтому перед копированием изображения необходимо выполнить преобразование физических координат (0,0) в логические, вызвав функцию DPtoLP:
ptOrg.x = 0; ptOrg.y = 0; DPtoLP(hMemDC, &ptOrg, 1);
После этого можно вызывать функцию BitBlt:
BitBlt(hDC, x, y, ptSize.x, ptSize.y, hMemDC, ptOrg.x, ptOrg.y, SRCCOPY);
Эта функция копирует битовое изображение, имеющее размеры ptSize, из контекста памяти hMemDC в контекст отображения hDC. При этом логические координаты верхнего левого угла изображения в контексте памяти находятся в структуре ptOrg. Координаты верхнего левого угла прямоугольной области в контексте отображения, куда будет копироваться изображение, передаются через параметры x и y.
После рисования битового изображения функция DrawBitmap выбирает в контекст памяти первоначально выбранное при его создании изображение, состоящее из одного монохромного пиксела, и затем удаляет контекст памяти:
SelectObject(hMemDC, hOldbm); DeleteDC(hMemDC);
В качестве кода растровой операции используется константа SRCCOPY. При этом цвет пикселов копируемого изображения полностью замещает цвет соответствующих пикселов контекста отображения.
Мы уже говорили об использовании растровых операций для рисования линий и закрашивания областей.
При рисовании битовых изображений вы также можете использовать растровые операции, причем цвет полученного изображения в зависимости от выбранной растровой операции может определяться цветом исходного изображения, цветом поверхности, на которой выполняется рисование, и цветом кисти, выбранной в контекст отображения.
Чаще всего используется код растровой операции SRCCOPY. В этом случае цвет кисти, выбранной в контекст отображения, не имеет значения, так как ни цвет кисти, ни цвет фона не влияют на цвет нарисованного изображения.
Однако вы можете использовать и другие коды растровых операций (всего их 256). В этом случае для вычисления цвета полученного после рисования пиксела можно выбрать практически любое логическое выражение, учитывающее цвет фона, цвет кисти и цвет пиксела изображения.
В файле windows.h описаны константы для наиболее полезных кодов растровых операций. Мы опишем эти константы вместе с соответствующими логическими выражениями. При этом символом S мы будем обозначать цвет исходного изображения, символом D - цвет фона на котором выполняется рисование, и P - цвет кисти, выбранной в контекст отображения.
Код растровой операции | Логическое выражение | Описание |
SRCCOPY | S | Исходное изображение копируется в контекст отображения |
SRCPAINT | S | D | Цвет полученного изображения определяется при помощи логической операции ИЛИ над цветом изображения и цветом фона |
SRCAND | S & D | Цвет полученного изображения определяется при помощи логической операции И над цветом изображения и цветом фона |
SRCINVERT | S ^ D | Цвет полученного изображения определяется при помощи логической операции ИСКЛЮЧАЮЩЕЕ ИЛИ над цветом изображения и цветом фона |
SRCERASE | S & ~D | Цвет фона инвертируется, затем выполняется операция И над результатом и цветом исходного изображения |
NOTSRCCOPY | ~S | После рисования цвет изображения получается инвертированием цвета исходного изображения |
NOTSRCERASE | ~(S | D) | Цвет полученного изображения получается инвертированием результата логической операции ИЛИ над цветом изображения и цветом фона |
MERGECOPY | P & S | Выполняется логическая операции И над цветом исходного изображения и цветом кисти |
MERGEPAINT | ~S | D | Выполняется логическая операции ИЛИ над инвертированным цветом исходного изображения и цветом фона |
PATCOPY | P | Выполняется копирование цвета кисти |
PATPAINT | P | ~S | D | Цвет кисти комбинируется с инвертированным цветом исходного изображения, при этом используется логическая операция ИЛИ. Полученный результат комбинируется с цветом фона, также с помощью логической операции ИЛИ |
PATINVERT | P ^ D | Цвет полученного изображения определяется при помощи логической операции ИСКЛЮЧАЮЩЕЕ ИЛИ над цветом кисти и цветом фона |
DSTINVERT | ~D | Инвертируется цвет фона |
BLACKNESS | 0 | Область закрашивается черным цветом |
WHITENESS | 1 | Область закрашивается белым цветом |
Остальные коды приведены в документации, которая поставляется вместе с SDK. В более удобном виде все коды растровых операций приведены в приложении к книге "Developing Windows 3.1 Application whit Microsoft C/C++" (автором которой является Brent Rector). Мы не будем воспроизводить полную таблицу для кодов растровых операций, так как во-первых, эти операции редко используются, а во-вторых, таблица занимает много места.
Для рисования битовых изображений можно использовать вместо функции BitBlt функцию StretchBlt , с помощью которой можно выполнить масштабирование (сжатие или растяжение) битовых изображений:
BOOL WINAPI StretchBlt( HDC hdcDest, // контекст для рисования int nXDest, // x-координата верхнего левого угла // области рисования int nYDest, // y-координата верхнего левого угла // области рисования int nWidthDest, // новая ширина изображения int nHeightDest, // новая высота изображения HDC hdcSrc, // идентификатор исходного контекста int nXSrc, // x-координата верхнего левого угла // исходной области int nYSrc, // y-координата верхнего левого угла // исходной области int nWidthSrc, // ширина исходного изображения int nHeightSrc, // высота исходного изображения DWORD dwRop); // код растровой операции
Параметры этой функции аналогичны параметрам функции BitBlt, за исключением того, что ширина и высота исходного и полученного изображения должна определяться отдельно. Размеры исходного изображения (логические) задаются параметрами nWidthSrc и nHeightSrc, размеры нарисованного изображения задаются параметрами nWidthDest и nHeightDest.
Возвращаемое значение равно TRUE при успешном завершении или FALSE при ошибке.
Следует упомянуть также еще одну функцию, которая сама по себе не может рисовать битовые изображения, но часто используется для закраски прямоугольных областей экрана. Эта функция имеет имя PatBlt :
BOOL WINAPI PatBlt( HDC hdc, // контекст для рисования int nX, // x-координата верхнего левого угла // закрашиваемой области int nY, // y-координата верхнего левого угла // закрашиваемой области int nWidth, // ширина области int nHeight, // высота области DWORD dwRop); // код растровой операции
При использовании этой функции вы можете закрашивать области экрана с использованием следующих кодов растровых операций: PATCOPY, PATINVERT, PATPAINT, DSTINVERT, BLACKNESS, WHITENESS.
Возвращаемое функцией PatBlt значение равно TRUE при успешном завершении или FALSE при ошибке.
Рисование эллипса
Для рисования эллипса вы можете использовать функцию Ellipse :
BOOL WINAPI Ellipse( HDC hdc, // идентификатор контекста отображения int nxTL, // координата x верхнего левого угла int nyTL, // координата y верхнего левого угла int nxBR, // координата x правого нижнего угла int nyBR); // координата y правого нижнего угла
Первый параметр этой функции указывает идентификатор контекста отображения, остальные - координаты верхнего левого и правого нижнего углов прямоугольника, в который должен быть вписан эллипс (рис. 2.19).
Рис. 2.19. Рисование эллипса
Рисование линий
Приложения Windows могут рисовать прямые и ломаные линии, а также дуги эллипса (и окружности, как частного случая эллипса). Параметры этих линий определяются несколькими атрибутами контекста отображения. Это режим отображения, влияющий на используемую систему координат, цвет фона, режим фона (прозрачный или непрозрачный), режим рисования, цветовая палитра (в режимах, использующих цветовую палитру), перо (может иметь различный цвет, толщину и стиль).
Рисование линий произвольного стиля
Как мы уже говорили, вы не можете создать перо для рисования пунктирных, штрих-пунктирных или штриховых линий толщиной больше одного пиксела. Однако в некоторых случаях у вас может возникнуть необходимость в рисовании таких линий.
В программном интерфейсе GDI есть функция с именем LineDDA, которая позволяет рисовать любые линии (правда, основная работа по рисованию линий при этом будет возложена на программиста).
Функция LineDDA имеет следующий прототип:
void WINAPI LineDDA( int nxStart, int nyStart, // начальная точка int nxEnd, int nyEnd, // конечная точка LINEDDAPROC lnddaprc, // адрес функции для рисования LPARAM lParam); // дополнительные параметры
Первые четыре параметра этой функции определяют координаты начальной и конечной точки, между которыми надо нарисовать линию.
Через параметр lnddaprc передается указатель на функцию рисования, которая является функцией обратного вызова, определяемой программистом. Эта функция получает управление много раз, она вызывается для каждой точки рисуемой линии.
Для режима STRICT тип LINEDDAPROC определен в файле windows.h следующим образом:
typedef void (CALLBACK* LINEDDAPROC)(int, int, LPARAM);
Последний параметр предназначен для передачи дополнительных данных в функцию рисования.
Приведем прототип функции рисования (для функции можно использовать любое имя):
void CALLBACK _export LineProc(int xPos, int yPos, LPARAM lParam);
Первые два параметра представляют собой координаты точки, для рисования которых вызвана функция. Последний параметр соответствует последнему параметру функции LineDDA и содержит передаваемое этой функции значение.
Пример использования функции LineDDA вы можете найти ниже в разделе "Приложение DASHLINE".
Рисование ломаной линии
Функции Polyline , предназначенной для рисования ломаных линий, следует передать идентификатор контекста отображения hdc, указатель lppt на массив структур POINT, в котором должны находится координаты начала ломаной линии, координаты точек излома и координаты конца ломаной линии, а также размер этого массива cPoints:
BOOL WINAPI Polyline( HDC hdc, // идентификатор контекста отображения const POINT FAR* lppt,// указатель на массив структур POINT int cPoints); // размер массива
Функция Polyline возвращает TRUE при нормальном завершении или FALSE при ошибке. Она не использует текущую позицию пера и не изменяет ее.
Если ломаная линия не замкнута, ее последняя точка не рисуется (рис. 2.12).
Рис. 2.12. Рисование ломаной линии
Рисование многоугольников
Рисование многоугольников (рис. 2.22) выполняется функцией Polygon , аналогичной по своим параметрам функции Polyline, с помощью которой рисуются ломаные линии:
BOOL WINAPI Polygon( HDC hdc, // идентификатор контекста отображения const POINT FAR* lppt,// указатель на массив структур POINT int cPoints); // размер массива
Через параметр hdc передается идентификатор контекста отображения.
Параметр lppt указывает на массив структур POINT, в котором должны находится координаты вершин многоугольника. Параметр cPoints определяет размер этого массива.
Функция Polygon возвращает TRUE при нормальном завершении или FALSE при ошибке. Она не использует текущую позицию пера и не изменяет ее.
Рис. 2.22. Рисование многоугольника
В массиве структур POINT, определяющих вершины многоугольника, каждая вершина должна быть указана один раз. Функция Polygon автоматически замыкает ломаную линию, образующую многоугольник.
С помощью функции PolyPolygon можно нарисовать одновременно несколько многоугольников:
BOOL WINAPI PolyPolygon( HDC hdc, // идентификатор контекста отображения const POINT FAR*lppt, // указатель на массив структур POINT int FAR* lpnPolyCounts, // адрес массива количества точек // в многоугольниках int cPolygons); // количество многоугольников
Первый параметр hdc, как обычно, задает контекст отображения.
Параметр cPolygons определяет количество многоугольников, которые нужно нарисовать.
Параметр lppt должен содержать указатель на массив структур типа POINT, содержащий координаты вершин всех многоугольников.
И, наконец, через параметр lpnPolyCounts передается указатель на массив целых чисел. Каждое число в этом массиве определяет количество точек в соответствующем многоугольнике.
В отличие от функции Polygon, функция PolyPolygon не замыкает автоматически ломаную линию, образующую многоугольник.
В контексте отображения имеется атрибут, влияющий на способ закрашивания для самопересекающихся многоугольников. По умолчанию выбран режим ALTERNATE , в котором эти области не закрашиваются (закрашиваются только те области, которые расположены между нечетными и четными сторонами многоугольника).
С помощью функции SetPolyFillMode вы можете изменить значение этого атрибута на WINDING . В этом режиме для того чтобы определить, надо ли закрашивать область многоугольника, учитывается направление, в котором был нарисован этот многоугольник. Каждая сторона многоугольника может быть нарисована в направлении либо по часовой стрелке, либо против часовой стрелки. Если воображаемая линия, нарисованная в направлении из внутренней области многоугольника в наружную, пересекает сегмент, нарисованный в направлении по часовой стрелке, содержимое некоторого внутреннего счетчика увеличивается на единицу. Если же эта линия пересекает сегмент, нарисованный против часовой стрелки, содержимое счетчика уменьшается на единицу. Область закрашивается только в том случае, если содержимое счетчика не равно нулю.
Немного позже вы сможете изучить этот алгоритм с помощью приложения LINER.
Приведем прототип функции SetPolyFillMode:
int WINAPI SetPolyFillMode(HDC hdc, int fnMode);
Параметр fnMode, определяющий режим закрашивания многоугольников, может принимать значения ALTERNATE или WINDING. Функция возвращает код старого режима закрашивания.
Вы можете определить используемый в данный момент режим закрашивания многоугольников с помощью функции GetPolyFillMode :
int WINAPI GetPolyFillMode(HDC hdc);
Рисование прямой линии
Для того чтобы нарисовать прямую линию, приложение должно воспользоваться функцией LineTo :
BOOL WINAPI LineTo(HDC hdc, int xEnd, int yEnd);
Эта функция рисует линию из текущей позиции пера, установленной ранее функцией MoveTo или MoveToEx, в точку с координатами (xEnd,yEnd). После того как линия будет нарисована, текущая позиция пера станет равной (xEnd,yEnd).
Функция LineTo возвращает TRUE при нормальном завершении или FALSE при ошибке.
Таким образом, для того чтобы нарисовать прямую линию, приложение должно сначала с помощью функции MoveToEx установить текущую позицию пера в точку, которая будет началом линии, а затем вызвать функцию LineTo, передав ей через параметры xEnd и yEnd координаты конца линии.
Особенностью функции LineTo является то, что она немного не дорисовывает линию - эта функция рисует всю линию, не включая ее конец, т. е. точку (xEnd,yEnd). Это иллюстрируется на рис. 2.11.
Рис. 2.11. Рисование прямой линии
Если вас не устраивает необходимость пользоваться двумя функциями для рисования линии, вы можете создать свою собственную, например такую:
BOOL DrawLine(HDC hdc, int x1, int y1, int x2, int y1) { POINT pt; MoveToEx(hdc, x1, y1, &pt); return LineTo(hdc, x2, y2); }
Преимущества использования отдельных функций для установки текущей позиции и для рисования линии из текущей позиции в заданную точку с последующим изменением текущей позиции проявляются при рисовании ломаных линий. В этом случае вы можете только один раз установить текущую позицию пера на начало ломаной линии и в дальнейшем вызывать только функцию LineTo, передавая ей координаты точек излома линии. Однако для рисования ломаных линий (если известны координаты всех точек излома) больше подходит функция Polyline, которую мы рассмотрим в следующем разделе.
Рисование прямоугольника
Простейшая функция, с помощью которой можно нарисовать прямоугольник, называется Rectangle :
BOOL WINAPI Rectangle( HDC hdc, // идентификатор контекста отображения int nxTL, // координата x верхнего левого угла int nyTL, // координата y верхнего левого угла int nxBR, // координата x правого нижнего угла int nyBR); // координата y правого нижнего угла
Функция Rectangle рисует прямоугольник для контекста отображения hdc, возвращая значение TRUE в случае успеха или FALSE при ошибке.
Назначение остальных параметров иллюстрируется рис. 2.17.
Рис. 2.17. Рисование прямоугольника
Как видно из этого рисунка, последние четыре параметра функции задают координаты верхнего левого и нижнего правого угла прямоугольника.
В зависимости от стиля пера граница фигуры может находится полностью внутри прямоугольника, заданного координатами (nxTL, nyTL), (nxBR,nyBR) или выходить за его пределы (см. рис. 2.14). Если выбрать стиль пера PS_NULL, граница фигуры станет невидимой.
В зависимости от кисти, выбранной в контекст отображения, внутренность прямоугольника может быть закрашенной в тот или иной цвет, заштрихована одним из нескольких способов (как показано на рис. 2.16) или закрашена с помощью любого битового изображения размером 8х8 пикселов.
С помощью функции RoundRect можно нарисовать прямоугольник со скругленными углами (рис. 2.18).
Рис. 2.18. Прямоугольник со скругленными углами
По сравнению с функцией Rectangle функция RoundRect имеет два дополнительных параметра nxEllipse и nyEllipse, определяющих форму и радиус закругления:
BOOL WINAPI RoundRect( HDC hdc, // идентификатор контекста отображения int nxTL, // координата x верхнего левого угла int nyTL, // координата y верхнего левого угла int nxBR, // координата x правого нижнего угла int nyBR, // координата y правого нижнего угла int nxEllipse, // ширина эллипса int nyEllipse); // высота эллипса
Есть и другие функции, которые можно использовать для рисования прямоугольников.
Функция FillRect закрашивает прямоугольную область окна заданной кистью:
int WINAPI FillRect( HDC hdc, // идентификатор контекста отображения const RECT FAR* lprc, // указатель на структуру RECT HBRUSH hbrush); // идентификатор кисти для закрашивания
Параметр lprc должен указывать на структуру типа RECT, в которую следует записать координаты закрашиваемой прямоугольной области. Правая и нижняя граница указанной области не закрашивается.
Независимо от того, какая кисть выбрана в контекст отображения, функция FillRect будет использовать для закраски кисть, указанную параметром hbrush.
Учтите, что правильная работа функции FillRect гарантируется только в том случае, когда значение поля bottom структуры RECT больше значения поля top, а значение поля right больше значения поля left.
Для закрашивания границы прямоугольной области (т. е. для рисования прямоугольной рамки) можно использовать функцию FrameRect :
int WINAPI FrameRect( HDC hdc, // идентификатор контекста отображения const RECT FAR* lprc, // указатель на структуру RECT HBRUSH hbrush); // идентификатор кисти для закрашивания
Параметры этой функции аналогичны параметрам функции FillRect.
Ширина пера, используемого для рисования рамки, всегда равна одной логической единице. Структура RECT должна быть подготовлена таким же образом, что и для функции FillRect, т. е. значение поля bottom структуры RECT должно быть больше значения поля top, а значение поля right - больше значения поля left.
Значение, возвращаемое функциями FillRect и FrameRect не используется, приложения должны его игнорировать.
Используя функцию InvertRect , вы можете инвертировать содержимое прямоугольной области, заданной параметром lprc:
void WINAPI InvertRect(HDC hdc, const RECT FAR* lprc);
Есть еще одна интересная функция, предназначенная для рисования прямоугольников. Она имеет имя DrawFocusRect :
void WINAPI DrawFocusRect(HDC hdc, const RECT FAR* lprc);
Эта функция рисует прямоугольную рамку, предназначенную для выделения окна, имеющего фокус ввода.
Функция DrawFocusRect имеет три интересные особенности.
Во-первых, для рисования используется растровая операция "ИСКЛЮЧАЮЩЕЕ ИЛИ". Это приводит к тому, что для удаления нарисованной таким образом рамки ее достаточно нарисовать еще раз на том же месте.
Вторая особенность заключается в том, что для использования этой функции не нужно выбирать перо, рисующее пунктирную линию. Функция DrawFocusRect рисует пунктирную линию с нестандартным, очень близким расположением точек.
Третья особенность заключается в том, что перед использованием этой функции необходимо установить режим отображения MM_TEXT.
Первые две особенности позволяют использовать ее для рисования рамки выделения произвольных участков изображения на экране монитора (при помощи мыши).
В заключение отметим, что в программном интерфейсе Windows нет функции для рисования квадрата и круга. Эти фигуры являются частными случаями, соответственно, прямоугольника и эллипса, поэтому для рисования, например, квадрата, вы должны использовать одну из только что описанных функций. Для сохранения пропорций проще всего использовать одну из метрических систем координат.
Рисование с использованием палитры
Итак, вы создали палитру, выбрали ее в контекст отображения и реализовали. Теперь приложение может пользоваться цветами из созданной палитры. Но как?
Если приложению нужно создать перо или кисть, определить цвет текста функцией 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, так как для режимов высокого цветового разрешения эта макрокоманда обеспечит более точное цветовое соответствие.
Рисование сегмента эллипса
Сегмент эллипса (рис. 2.20) можно нарисовать при помощи функции Chord :
BOOL WINAPI Chord( HDC hdc, // идентификатор контекста отображения int nxLeft, int nyTop, // верхий левый угол int nxRight, int nyBottom, // правый нижний угол int nxStart, int nyStart, // начало дуги int nxEnd, int nyEnd); // конец дуги
Параметры этой функции аналогичны параметрам рассмотренной нами ранее функции Arc.
Рис. 2.20. Рисование сегмента эллипса
Рисование сектора эллипса
Для рисования сектора эллипса (рис. 2.21) следует использовать функцию Pie , аналогичную по своим параметрам функциям Arc и Chord:
BOOL WINAPI Pie( HDC hdc, // идентификатор контекста отображения int nxLeft, int nyTop, // верхний левый угол int nxRight, int nyBottom, // правый нижний угол int nxStart, int nyStart, // начало дуги int nxEnd, int nyEnd); // конец дуги
Рис. 2.21. Рисование сектора эллипса
Рисование точки
Функция рисования точки SetPixel устанавливает цвет точки с заданными координатами:
COLORREF WINAPI SetPixel( HDC hdc, // контекст отображения int nXPos, // x-координата точки int nYPos, // y-координата точки COLORREF clrref); // цвет точки
Установка первых трех параметров этой функции не должна вызывать у вас никаких затруднений. Параметр hdc определяет контекст отображения, для которого необходимо изменить цвет точки. Параметры nXPos и nYPos определяют координаты точки в системе координат, которая зависит от установленного для контекста hdc режима отображения.
Последний параметр clrref определяет новый цвет точки. О том, как "раскрасить" окно приложения, вы узнаете из третьей главы нашей книги. Тем не менее мы опишем самый простой способ для функции SetPixel.
В файле windows.h есть описание макрокоманды RGB , позволяющей сконструировать цвет в формате COLORREF из отдельных компонент красного (r), зеленого (g) и голубого (b) цвета:
#define RGB(r,g,b) \ ((COLORREF)(((BYTE)(r)|((WORD)(g)<<8)) | \ (((DWORD)(BYTE)(b))<<16)))
Вы можете использовать эту макрокоманду совместно с функцией SetPixel для установки, например, красного цвета точки, расположенной в начале системы координат (0,0), следующим образом:
SetPixel(hdc, 0, 0, RGB(0xff, 0, 0));
Три параметра макрокоманды RGB позволяют задать любой из примерно 16 млн. цветов и оттенков, однако это не означает, что вы получите на экране точно такой цвет, какой был задан при помощи этой макрокоманды. Скорее всего вы сможете воспользоваться одним из 20 системных цветов. Причины этого вы узнаете в третьей главе. Там же мы расскажем о том, как в некоторых случаях можно расширить используемую гамму цветов.
Функция SetPixel возвращает цвет, который фактически был использован для рисования точки. Как мы только что заметили, возвращенное значение может отличаться от заданного параметром clrref. В случае ошибки оно будет равно -1.
Функция GetPixel позволяет узнать цвет точки, заданной идентификатором контекста отображения и координатами:
COLORREF WINAPI GetPixel(HDC hdc, int nXPos, int nYPos);
С помощью следующих трех макрокоманд, определенных в файле windows.h, вы можете определить отдельные цветовые компоненты для значения, возвращаемого функциями SetPixel и GetPixel:
#define GetRValue (rgb) ((BYTE)(rgb)) #define GetGValue (rgb) ((BYTE)(((WORD)(rgb)) >> 8)) #define GetBValue (rgb) ((BYTE)((rgb)>>16))
Функции SetPixel и GetPixel используются достаточно редко, так как для построения графических изображений есть более мощные функции.
Рисование замкнутых фигур
Помимо линий, приложения Windows могут использовать функции GDI для рисования замкнутых закрашенных или незакрашенных фигур, таких как прямоугольники, эллипсы, многоугольники с прямыми и скругленными углами и т. д.
Для закрашивания внутренней области замкнутых фигур используется кисть, задаваемая как атрибут контекста отображения. Внешний контур фигуры обводится пером, которое также выбирается в контекст отображения. Учитываются и остальные атрибуты, установку которых мы рассмотрели для функций рисования линий, такие, как режим отображения, режим фона, код растровой операции.
Мы начнем изучение функций GDI, предназначенных для рисования замкнутых фигур, с функций рисования прямоугольников.
Родительский контекст отображения
Родительский контекст отображения используется для дочерних окон. Он позволяет дочерним окнам "унаследовать" атрибуты контекста отображения у родительского окна, что во многих случаях упрощает процедуру настройки этих атрибутов. Например, дочернее окно может использовать для вывода текста тот же шрифт и цвета, что и родительское окно.
Для использования родительского контекста отображения в классе, на базе которого создается дочернее окно, перед регистрацией необходимо указать стиль CS_PARENTDC :
wc.style = CS_PARENTDC;
Шрифт
Контекст отображения содержит информацию о том, какой шрифт (font ) используется для вывода текста. По умолчанию текст выводится системным шрифтом с переменной шириной букв в кодировке ANSI.
С помощью функций CreateFont , CreateFontIndirect и SelectObject приложение может выбрать для вывода текста любой другой шрифт, установленный (зарегистрированный) в операционной системе. Для установки шрифта, как вы знаете, следует использовать приложение Control Panel.
Шрифты
Для того чтобы рисовать текст, используются шрифты . Как мы уже говорили в предыдущих томах "Библиотеки системного программиста", операционная система Windows версии 3.1 может работать с растровыми, векторными и масштабируемыми шрифтами. Кроме этого, приложения Windows могут использовать шрифты, встроенные в устройство вывода (обычно это принтерные шрифты).
Растровые шрифты содержат битовые образы всех символов. Для каждого размера шрифта необходимо иметь свой набор символов. Кроме того, различные устройства вывода имеют разное соотношение горизонтальных и вертикальных размеров пиксела, что приводит к необходимости хранить отдельные наборы образов символов не только для разных размеров шрифта, но и для разного соотношения размеров пиксела физического устройства отображения.
Растровые шрифты плохо поддаются масштабированию, так как при этом наклонные линии контура символа принимают зазубренный вид.
Векторные шрифты хранятся в виде набора векторов, описывающих отдельные сегменты и линии контура символа, поэтому они легко масштабируются. Однако их внешний вид далек от идеального. Как правило, векторные шрифты используются для вывода текста на векторные устройства, такие, как плоттер.
Масштабируемые шрифты TrueType впервые появились в Windows версии 3.1 и сильно повлияли на рост популярности этой операционной системы. Шрифты True Type поддаются масштабированию без существенных искажений внешнего вида.
Рис. 1.5 иллюстрирует ухудшение внешнего вида растрового и векторного шрифтов при увеличении размера букв до величины 40 пунктов. Внешний вид масштабируемого шрифта не ухудшился.
Рис. 1.5. Растровый, векторный и масштабируемый шрифты
В состав операционной системы Windows входит не очень большое количество шрифтов, однако при необходимости вы можете приобрести дополнительные шрифты как отдельно, так и в составе различного программного обеспечения. Например, вместе с графическим редактором Corel Draw версии 3.0 и 4.0 поставляются сотни различных шрифтов.
Помимо обычных шрифтов существуют символьные или декоративные шрифты , содержащие вместо букв различные пиктограммы.
Прежде чем использовать шрифт, его надо выбрать в контекст отображения. Эта процедура будет описана нами в главе, посвященной использованию шрифтов.
Поэтому они приобретают дополнительные шрифты у независимых разработчиков. Однако использование нестандартных шрифтов может привести к проблемам при необходимости переноса документа из одного компьютера в другие, так как там нужного шрифта может не оказаться. Вы, конечно, можете просто скопировать нужный шрифт и перенести его вместе с документом, однако такая процедура может быть запрещена по условию лицензионного соглашения с разработчиками шрифта.
Проблему переноса документа на другой компьютер с сохранением прав разработчиков шрифта можно решить, используя шрифты, встроенные в документ. Пользователь может, например, подготовить документ в текстовом процессоре Microsoft Word for Windows версии 6.0 и встроить в него все использованные шрифты. При переносе такого документа на другой компьютер эти шрифты можно будет использовать для просмотра и, возможно, редактирования этого (и только этого) документа. Возможность редактирования с использованием встроенного шрифта определяется разработчиком шрифта.
В этой главе вы узнаете о классификации шрифтов, принятой в операционной системе Windows, научитесь выбирать шрифт для вывода текста и определять параметры выбранного шрифта. Мы приведем пример приложения, которое выводит строку текста под заданным углом.
Системная цветовая палитра
Что такое цветовая палитра ?
Если вы видели художника за работой, вы уже знаете ответ на этот вопрос. Цветовая палитра - это не более чем набор цветов. Художник создает палитру из различных красок, смешивая их. Полученный набор красок используется для рисования.
В 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-компонент. Очевидный недостаток этого способа заключается в том, что пользователь не сможет изменить эти цвета. Даже если у вас идеальный вкус и вы сможете подобрать превосходную цветовую гамму, следует учитывать, что ваше приложение может быть запущено на монохромном мониторе, где отдельные цвета преобразуются в градации серого цвета. В результате некоторые элементы изображения могут стать плохо различимыми или пропадут вовсе.
Более удачный способ заключается в использовании так называемых системных цветов. Системные цвета - это цвета, с помощью которых операционная система 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_PAINT непосредственно перед началом рисования. Процесс настройки может оказаться достаточно длительным, кроме того, может потребоваться восстановление исходного состояния атрибутов контекста отображения.
В программном интерфейсе GDI имеются две функции, которые позволяют сохранить сразу все атрибуты контекста отображения и затем быстро восстановить их.
Для сохранения атрибутов контекста отображения следует использовать функцию SaveDC :
int WINAPI SaveDC(HDC hdc);
Значение, возвращаемое этой функцией, необходимо использовать в качестве параметра nSavedDC для функции RestoreDC , восстанавливающей атрибуты контекста отображения:
BOOL WINAPI RestoreDC(HDC hdc, int nSavedDC);
Функция RestoreDC возвращает значение TRUE при успешном завершении или FALSE при ошибке.
В качестве значения параметра nSavedDC можно использовать -1. В этом случае будет восстановлен контекст, сохраненный при последнем вызове функции SaveDC.
Сообщение WM_PALETTECHANGED
Когда любое приложение изменяет системную палитру, все перекрывающиеся (overlapped) и временные (pop up) окна получают сообщение WM_PALETTECHANGED . Это сообщение посылается также в окно приложения, которое выполнило изменение системной палитры.
Параметр wParam сообщения WM_PALETTECHANGED содержит идентификатор окна, изменившего системную палитру.
Если приложение обрабатывает это сообщение, оно должно вернуть нулевое значение.
В ответ на сообщение WM_PALETTECHANGED приложение должно заново реализовать палитру и, если палитра изменилась, перерисовать окно. Вместо полной перерисовки окна можно обновить цвета в окне, вызвав функцию UpdateColors :
int WINAPI UpdateColors(HDC hdc);
Следует, однако, иметь в виду, что обновление цветов может привести к деградации качества изображения, поэтому при изменении палитры лучше перерисовать окно заново.
Сообщение WM_QUERYNEWPALETTE
Сообщение WM_QUERYNEWPALETTE посылается окну, которое становится активным. Это сообщение не имеет параметров.
Главное окно приложения может менять свой статус с активного на фоновое несколько раз. Каждый раз, когда оно становится активным, ему посылается сообщение WM_QUERYNEWPALETTE. В ответ на это сообщение приложение должно заново реализовать свою логическую палитру, так как пока его главное окно было неактивно, другое приложение могло изменить системную палитру. Если палитра изменилась, обработчик сообщения WM_QUERYNEWPALETTE должен перерисовать окно.
Если обработчик сообщения WM_QUERYNEWPALETTE изменил системную палитру, он должен вернуть ненулевое значение, а если нет - нулевое.
Сообщение WM_SYSCOLORCHANGE
Как мы только что сказали, сообщение WM_SYSCOLORCHANGE посылается всем активным окнам верхнего уровня при изменении системных цветов. В ответ на это сообщение приложения, которые создают свои перья и кисти на базе системных цветов, должны удалить эти перья и кисти, а затем создать их заново.
Так как после изменения системных цветов все активные окна получают сообщение WM_PAINT, обработчик сообщения WM_SYSCOLORCHANGE не должен ничего перерисовывать в окне.
Сообщение WM_SYSCOLORCHANGE не имеет параметров, поэтому значения, передаваемые через wParam и lParam следует проигнорировать.
Сообщения об изменении палитры
Если ваше приложение активно работает с палитрами, оно должно иметь в виду, что одновременно вместе с ним могут работать и другие приложения, претендующие на изменение системной палитры. Для того чтобы при передаче фокуса ввода другому приложению изображение, построенное с использованием палитры, не оказалось испорчено в результате изменения системной палитры другим приложением, ваше приложение должно обрабатывать сообщения WM_QUERYNEWPALETTE и WM_PALETTECHANGED.
Создание цветовой палитры
Процесс создания цветовой палитры несложен. Вначале надо убедиться в том, что bmp-файл содержит таблицу цветов. Если размер таблицы цветов не равен нулю, следует заказать память для структуры LOGPALETTE , заполнить соответствующим образом заголовок и переписать в палитру цвета из таблицы цветов:
lpPal->palVersion = 0x300; lpPal->palNumEntries = wNumColors; for (i = 0; i < wNumColors; i++) { lpPal->palPalEntry[i].peRed =lpbmi->bmiColors[i].rgbRed; lpPal->palPalEntry[i].peGreen=lpbmi->bmiColors[i].rgbGreen; lpPal->palPalEntry[i].peBlue =lpbmi->bmiColors[i].rgbBlue; lpPal->palPalEntry[i].peFlags = 0; }
Палитра создается с помощью функции CreatePalette:
hPal = CreatePalette(lpPal);
Создание изображений в памяти
Другой способ работы с изображениями в формате DDB заключается в создании их непосредственно в оперативной памяти.
Вы должны подготовить массив, содержащий биты изображения, заполнить структуру типа BITMAP, которая описывает изображение, и затем вызвать функцию CreateBitmapIndirect , указав ей в качестве единственного параметра указатель lpbm на заполненную структуру типа BITMAP:
HBITMAP CreateBitmapIndirect(BITMAP FAR* lpbm);
Функция вернет идентификатор битового изображения, который вы можете использовать обычным способом.
Как правило, в памяти создаются монохромные изображения небольших размеров. В этом случае структура битов изображения является достаточно простой.
Например, пусть нам надо нарисовать битовое изображение, показанное в увеличенном виде на рис. 4.2.
Рис. 4.2. Битовое изображение
Подготовим в памяти массив, описывающий это изображение. Каждая строка массива соответствует одной строке сканирования битового изображения:
BYTE bBytes[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
При этом нам необходимо принимать во внимание, что размер одной строки сканирования изображения должен быть кратен 16 битам, т. е. двум байтам.
Однако сам по себе приведенный выше массив бит не содержит информации о размере изображения или о количестве бит, обозначающих цвет одного пиксела. Для формирования битового изображения необходимо подготовить структуру типа BITMAP, содержащую все необходимые сведения:
BITMAP bmp = { 0, 64, 9, 8, 1, 1, NULL };
В этом массиве указаны размеры изображения (ширина - 64 пиксела, высота - 9 пикселов), размер памяти для одной строки сканирования в байтах (равен 8), количество цветовых плоскостей (одна) и количество бит, используемых для представления цвета одного пиксела (один бит).
Указатель на массив бит будет проинициализирован непосредственно перед созданием изображения, так как сегмент данных может переместиться.
После того как массив данных и структура подготовлены, можно вызывать функцию CreateBitmapIndirect, передав ей в качестве параметра указатель на структуру:
bmp.bmBits = (LPSTR)bBytes; bmLogo2 = CreateBitmapIndirect(&bmp);
Непосредственно перед вызовом функции CreateBitmapIndirect следует установить в структуре типа BITMAP указатель на массив бит изображения.
Есть еще одна возможность. Вы можете создать битовое изображение, вызвав функцию CreateBitmap :
HBITMAP WINAPI CreateBitmap( int nWidth, // ширина изображения int nHeight, // высота изображения UINT cbPlanes, // количество цветовых плоскостей UINT cbBits, // количество бит на один пиксел const void FAR* lpvBits); // указатель на массив бит
Через параметры этой функции передаются значения, которые необходимо записать в структуру типа BITMAP перед вызовом функции CreateBitmapIndirect.
Функции CreateBitmap и CreateBitmapIndirect возвращают идентификатор созданного в памяти изображения, который можно использовать для выбора изображения в контекст памяти, или NULL при ошибке.
Создание кисти
Если вам нужна цветная кисть, ее следует создать с помощью функции CreateSolidBrush :
HBRUSH WINAPI CreateSolidBrush(COLORREF clrref);
В качестве параметра для этой функции необходимо указать цвет кисти. Для выбора цвета вы можете воспользоваться, например, макрокомандой RGB, позволяющей указать содержание отдельных цветовых компонент.
Windows может выбрать для кисти чистые или смешанные цвета, что зависит от текущего цветового разрешения. Подробности вы сможете узнать позже из главы, посвященной работе с цветами и цветовыми палитрами.
После использования созданной вами кисти ее следует удалить, не забыв перед этим выбрать в контекст отображения старую кисть. Для удаления кисти следует использовать макрокоманду DeleteBrush :
#define DeleteBrush(hbr) DeleteObject((HGDIOBJ)(HBRUSH)(hbr))
Приложение может заштриховать внутреннюю область замкнутой фигуры, создав одну из шести кистей штриховки функцией CreateHatchBrush :
HBRUSH WINAPI CreateHatchBrush(int fnStyle, COLORREF clrref);
С помощью параметра clrref вы можете определить цвет линий штриховки.
Параметр fnStyle задает стиль штриховки:
Стиль штриховки | Внешний вид |
HS_BDIAGONAL | |
HS_CROSS | |
HS_DIAGCROSS | |
HS_FDIAGONAL | |
HS_HORIZONTAL | |
HS_VERTICAL |
Вы можете использовать свой собственный стиль штриховки, создав кисть из битового изображения размером 8х8 пикселов (можно использовать только такой размер).
Если битовое изображение кисти определено в ресурсах приложения, его следует загрузить при помощи функции LoadBitmap . Эта функция возвратит идентификатор битового изображения. Затем для создания кисти этот идентификатор следует передать в качестве параметра функции CreatePatternBrush :
HBRUSH WINAPI CreatePatternBrush(HBITMAP hBitmap);
Забегая вперед, скажем, что битовые изображения делятся на те, которые хранятся в формате, зависящем от аппаратных особенностей устройства отображения, и на те, которые хранятся в аппаратно-независимом формате. Последние более универсальны, однако труднее в использовании. С помощью функции CreateDIBPatternBrush вы можете использовать для кисти битовое изображение в аппаратно-независимом формате:
HBRUSH WINAPI CreateDIBPatternBrush( HGLOBAL hglbDibPacked, UINT fnColorSpec);
Первый параметр указывает на область глобальной памяти, в которой содержится аппаратно-независимое битовое изображение в упакованном формате. Второй параметр определяет содержимое таблицы цветов, используемое этим битовым изображением, и может принимать два значения: DIB_PAL_COLORS (таблица цветов содержит ссылки на цветовую палитру) DIB_RGB_COLORS (таблица цветов содержит отдельные компоненты цвета).
Более подробное обсуждение таких понятий, как аппаратно-независимые битовые изображения и таблица цветов мы отложим до главы, посвященной битовым изображениям.
Создание логической палитры
Для того чтобы создать палитру, ваше приложение должно заполнить структуру 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 при ошибке.
Создание области
Приложение может создать область прямоугольной формы, область в виде многоугольника, область эллиптической формы. Можно комбинировать область из двух других, выполняя при этом над областями логические операции объединения, пересечения и т. д.
Структура PRINTDLG
В качестве параметра функции PrintDlg необходимо передать адрес предварительно подготовленной структуры типа PRINTDLG , описанной в файле commdlg.h:
typedef struct tagPD { DWORD lStructSize; HWND hwndOwner; HGLOBAL hDevMode; HGLOBAL hDevNames; HDC hDC; DWORD Flags; UINT nFromPage; UINT nToPage; UINT nMinPage; UINT nMaxPage; UINT nCopies; HINSTANCE hInstance; LPARAM lCustData; UINT (CALLBACK* lpfnPrintHook)(HWND, UINT,WPARAM,LPARAM); UINT (CALLBACK* lpfnSetupHook)(HWND, UINT,WPARAM,LPARAM); LPCSTR lpPrintTemplateName; LPCSTR lpSetupTemplateName; HGLOBAL hPrintTemplate; HGLOBAL hSetupTemplate; } PRINTDLG; typedef PRINTDLG FAR* LPPRINTDLG;
Рассмотрим назначение отдельных полей этой структуры.
Текущая позиция пера
Для рисования линий в интерфейсе GDI предназначена функция LineTo, которая использует понятие текущей позиции пера (current pen position ). Функция LineTo рисует линию из точки, соответствующей текущей позиции пера в точку, указанную при помощи параметров. Для установки текущей позиции пера предназначена функция MoveTo .
По умолчанию текущая позиция пера равна значению (0,0), что в системе координат, выбранной по умолчанию, соответствует верхнему левому углу внутренней области окна.
Заметим, что текущая позиция используется интерфейсом GDI только для рисования линий.
Для рисования прямых линий (и только для этого) в контексте отображения хранятся координаты текущей позиции пера . Для изменения текущей позиции пера в Windows версии 3.1 есть две функции с именами MoveTo и MoveToEx . Для совместимости с 32-разрядными версиями Windows, такими, как Windows NT, в новых приложениях следует использовать функцию MoveToEx:
BOOL WINAPI MoveToEx( HDC hdc, // идентификатор контекста отображения int x, // x-координата int y, // y-координата POINT FAR* lppt); // указатель на структуру POINT
Для контекста отображения hdc эта функция устанавливает текущую позицию пера, равную (x,y). В структуру типа POINT, на которую указывает параметр lppt, после возврата из функции будут записаны старые координаты пера.
Функция MoveToEx возвращает TRUE при нормальном завершении или FALSE при ошибке.
Чтобы узнать текущую позицию пера, приложение может использовать функцию GetCurrentPositionEx :
BOOL WINAPI GetCurrentPositionEx(HDC hdc, POINT FAR* lppt);
После вызова этой функции текущая позиция пера будет записана в структуру типа POINT, на которую указывает параметр lppt. Функция GetCurrentPositionEx возвращает TRUE при нормальном завершении или FALSE при ошибке.
Удаление палитры
Так как палитра является объектом, принадлежащим GDI, а не создавшему ее приложению, после использования палитры приложение должно обязательно ее удалить. Для удаления логической палитры лучше всего воспользоваться макрокомандой DeletePalette , определенной в файле windowsx.h:
#define DeletePalette(hpal) \ DeleteObject((HGDIOBJ)(HPALETTE)(hpal))
В качестве параметра этой макрокоманде следует передать идентификатор удаляемой палитры.
Учтите, что как и любой другой объект GDI, нельзя удалять палитру, выбранную в контекст отображения. Перед удалением следует выбрать старую палитру, вызвав функцию SelectPalette.
Установка начальных координат кисти
Начальные координаты кисти (brush origin ) - это атрибут контекста отображения. Он используются для определения координат точки внутри кисти, которая будет служить начальной при закраске внутренней области фигуры или окна. По умолчанию используются координаты (0,0), соответствующие верхнему левому углу кисти (в системе координат, выбранной в контекст отображения по умолчанию).
Если кисть используется для закраски внутренней области окна, верхний левый угол изображения кисти совмещается с верхним левым углом этой области. Затем изображение кисти многократно повторяется с шагом 8 пикселов.
При закраске фигур начальное расположение кисти привязывается не к фигуре, а по-прежнему к верхнему левому углу внутренней области окна. Поэтому при закраске, например, прямоугольника, верхний левый угол кисти может не совпадать с верхним левым углом прямоугольника.
Приложение может изменить начальные координаты кисти (сдвинуть кисть) при помощи функций SetBrushOrg и UnrealizeObject.
Прежде всего нужно вызвать функцию UnrealizeObject , передав ей в качестве параметра идентификатор сдвигаемой кисти (только если это не встроенная кисть):
BOOL WINAPI UnrealizeObject(HGDIOBJ hbrush);
В этом случае система сбросит координаты кисти после выбора ее в контекст отображения. После сброса надо установить новые значения координат кисти, вызвав функцию SetBrushOrg :
DWORD WINAPI SetBrushOrg(HDC hdc, int nx, int ny);
Параметры nx и ny определяют новые значения для начальных координат кисти пикселах (от 0 до 7).
В завершении следует снова выбрать кисть в контекст отображения при помощи макрокоманды SelectBrush.
Библиотеки системного программиста" представляли собой
Предыдущие три тома " Библиотеки системного программиста" представляли собой краткое (!) введение в программирование для операционной системы Microsoft Windows. В них мы рассказали вам только о некоторых возможностях, не углубляясь в тонкости графического интерфейса и других подсистем. Мы также отложили рассказ о таких важных понятиях, как многооконный интерфейс MDI, протокол обмена данными между приложениями DDE, системе привязки и вставки объектов OLE.
В книге, которую вы сейчас держите в руках, мы расскажем о графическом интерфейсе GDI (Graphic Device Interface - интерфейс графических устройств), посредством которого графическая операционная система Windows выводит графику и текст на экран, принтер, плоттер и другие аналогичные устройства. В предыдущих томах мы уже упоминали о GDI, однако были рассмотрены только основные моменты, без понимания которых невозможно сделать ни одно приложение (разве что такое, которое не создает окон и ничего не выводит на экран или принтер).
Интерфейс GDI избавляет приложения Windows от необходимости учитывать многие (но не все) аппаратные особенности графических устройств вывода. Например, адресация видеопамяти выполняется по-разному в видеоадаптерах CGA, EGA, VGA, SVGA и, к тому же, структура видеопамяти сильно зависит от используемого режима (подробности вы можете найти третьем томе "Библиотеки системного программиста", который называется "Программирование видеоадаптеров CGA, EGA и VGA"). Однако приложения не работают непосредственно с видеопамятью. Для того чтобы нарисовать линию или написать строку текста, приложения вызывают ту или иную функцию интерфейса GDI, реализованного в виде DLL-библиотеки. Функции GDI также не работают с аппаратурой. Для выполнения нужной графической операции GDI вызывает драйвер устройства вывода, который "знает" о всех особенностях аппаратуры.
Таким образом, с помощью GDI приложения могут организовать вывод текста и графических изображений на некоторое логическое устройство вывода.
Функции GDI и драйверы обеспечивают независимость приложений от аппаратуры, поэтому правильно созданное приложение Windows будет корректно работать с любыми видеоадаптерами и принтерами, как существующими на момент разработки приложения, так и с теми, которые появятся в будущем. В этом приложения Windows имеют большое преимущество над программами MS-DOS, вынужденными для повышения производительности работать непосредственно с регистрами видеоконтроллера и видеопамятью.
В первой главе вы познакомитесь с основными понятиями GDI, такими, как контекст отображения и его атрибуты, узнаете об инструментах, которые приложение Windows может использовать для рисования.
Вторая глава посвящена контексту отображения. Вы узнаете о типах контекста, научитесь получать и освобождать контекст отображения и контекст устройства. Мы расскажем вам об использовании режимов отображения, позволяющих работать с различными логическими системами координат. Отдельные разделы этой главы посвящены рисованию геометрических фигур и использованию областей.
В третьей главе мы расскажем о цвете и цветовых палитрах. Вы познакомитесь со статическими и системными цветами, научитесь создавать логическую палитру цветов и следить за изменениями системной палитры цветов. Все это позволит вам рисовать многоцветные изображения. Мы также расскажем об особенностях работы в режимах с высоким цветовым разрешением, таких как True Color.
Четвертая глава - об использовании битовых изображений. Вы узнаете о существовании различных типов битовых изображений и форматов bmp-файлов, содержащих изображения, научитесь рисовать битовые изображения, загруженные из bmp-файлов, а также создавать и использовать логическую палитру на базе таблицы цветов, расположенной в bmp-файле.
Пятая глава посвящена использованию шрифтов, в том числе масштабируемых шрифтов True Type. Будет рассмотрен механизм отображения шрифтов, который используется в процессе выделения шрифтов по запросу приложения. Мы расскажем о том, как выбрать нужный шрифт из числа установленных в системе, как получить различную информацию о шрифте, выбранном в контекст отображения.
Несмотря на то что мы не привели полного описания функций GDI, предназначенных для работы с шрифтами True Type (оно занимает слишком много места), пользуясь нашей книгой вы сможете выполнять все основные операции, связанные с использованием шрифтов True Type. Если перед вами стоит задача разработки такого приложения, как редактор шрифтов True Type, вы сможете получить недостающую информацию из документации, которая поставляется вместе с Microsoft SDK for Windows 3.1.
В шестой главе рассмотрены средства, предназначенные для работы с принтером. Теперь ваши приложения смогут печатать текст и графику на любом принтере, установленном в системе. Мы научим вас пользоваться стандартными диалоговыми панелями, предназначенными для печати, выбора принтера и установки параметров принтера, опишем функции GDI, предназначенные для печати.
Мы привели исходные тексты приложений, демонстрирующих использование описанных средств GDI. Вы можете купить дискету, содержащую исходные тексты всех этих приложений.
Для трансляции исходных текстов приложений, приведенных в книге, мы пользовались системой разработки Borland Turbo C++ for Windows версии 3.1. Вы можете также использовать Borland C++ версий 3.1 или 4.0.
Чтобы вы смогли изучить приемы работы с палитрой и использовать режимы с высоким цветовым разрешением, в вашем компьютере должен быть установлен подходящий видеоадаптер и видеомонитор, способный работать в режимах SVGA. Обычный адаптер VGA в Windows предоставляет возможность работы всего лишь с 16 цветами, что недостаточно для многих графических приложений. Механизм цветовых палитр операционной системы Windows используется только в том случае, если в компьютере есть видеоадаптер SVGA, способный отображать 256 цветов. Лучше всего приобрести недорогой видеоадаптер с ускорителем для Windows, который может работать в режиме True Color (примерно 16 млн. цветов). Для отладки приложений, приведенных в этой книге, авторы использовали следующие видеоадаптеры: AVGA3 с видеопроцессором фирмы Cirrus Logic и объемом видеопамяти 1 Мбайт (акселератор для шины ISA стоимостью примерно 100 долларов), Orchid Fahrenheit VA-VLB (акселератор для шины VL-BUS), OAK VGA (адаптер SVGA с объемом видеопамяти 1 Мбайт без ускорителя).
Наш адрес E-mail:
frolov@glas.apc.org
Вы можете присылать нам по этому адресу свои замечания и предложения по содержанию книг серий "Библиотека системного программиста" и "Персональный компьютер. Шаг за шагом". Мы их обязательно учтем при подготовке следующих изданий.
Авторы выражают благодарность сотрудникам АО "Диалог-МИФИ" Елене Виноградовой, Олегу Александровичу Голубеву, Наталье Дмитриевой, Оксане Кузьминовой, корректору Виктору Кустову. Появление этой книги стало возможным только благодаря напряженному труду всех этих людей. Мы также благодарим за помощь в работе над книгой Максима Синева и Сергея Ноженко.
Выбор цвета без использования палитры
Приложения, которые не хотят ничего знать про палитры, могут указывать логический цвет изображений, составляя его из RGB-компонент, указывая их количественный состав. Однако, если видеоконтроллер не работает в режиме True Color, для вывода на экран будут использованы только статические цвета или смешанные цвета, состоящие из статических цветов. В результате полученный на экране физический цвет может не соответствовать запрошенному логическому цвету.
Выбор кисти
Для закрашивания внутренней области замкнутых фигур вы можете использовать встроенные кисти, или кисти, созданные вашим приложением. Последние необходимо удалять после использования.
Выбор палитры в контекст отображения
Созданная палитра перед использованием должна быть выбрана в контекст отображения. Выбор палитры выполняется функцией SelectPalette :
HPALETTE WINAPI SelectPalette( HDC hdc, HPALETTE hpal, BOOL fPalBack);
Функция выбирает палитру hpal в контекст отображения hdc, возвращая в случае успеха идентификатор палитры, которая была выбрана в контекст отображения раньше. При ошибке возвращается значение NULL.
Указав для параметра fPalBack значение TRUE, вы можете заставить GDI в процессе реализации палитры использовать алгоритм, соответствующий фоновому окну. Если же этот параметр равен FALSE, используется алгоритм для активного окна (т. е. все ячейки системной палитры, кроме зарезервированных для статических цветов, отмечаются как свободные и используются для реализации палитры).
Выбор пера
Для рисования линий приложения Windows могут выбрать одно из трех встроенных перьев, либо создать собственное перо.
Для выбора встроенного пера лучше всего воспользоваться макрокомандами GetStockPen и SelectPen , определенными в файле windowsx.h:
#define GetStockPen(i) ((HPEN)GetStockObject(i)) #define SelectPen(hdc, hpen) \ ((HPEN)SelectObject((hdc), (HGDIOBJ)(HPEN)(hpen)))
Макрокоманда GetStockPen возвращает идентификатор встроенного пера, заданного параметром i. Вы можете выбрать для этого параметра одно из следующих значений:
Значение | Описание |
BLACK_PEN | Перо, рисующее черную линию толщиной в один пиксел (для любого режима отображения). Это перо выбрано в контекст отображения по умолчанию |
WHITE_PEN | Перо белого цвета. Толщина пера также равна одному пикселу и не зависит от режима отображения |
NULL_PEN | Невидимое перо толщиной в один пиксел. Используется для рисования замкнутых закрашенных фигур (таких, как прямоугольник или эллипс) в тех случаях, когда контур фигуры должен быть невидимым |
После получения идентификатора пера его необходимо выбрать в контекст отображения при помощи макрокоманды SelectPen. Первый параметр этой макрокоманды используется для указания идентификатора контекста отображения, в который нужно выбрать перо, второй - для передачи идентификатора пера.
Макрокоманда SelectPen возвращает идентификатор пера, который был выбран в контекст отображения раньше. Вы можете сохранить этот идентификатор и использовать его для восстановления старого пера.
Однако при помощи встроенных перьев вы не можете нарисовать цветные, широкие, штриховые и штрих-пунктирные линии.
Если вас не устраивают встроенные перья, вы можете легко создать собственные. Для этого нужно воспользоваться функциями CreatePen или CreatePenIndirect.
Функция CreatePen позволяет определить стиль, ширину и цвет пера:
HPEN WINAPI CreatePen( int fnPenStyle, // стиль пера int nWidth, // ширина пера COLORREF clrref); // цвет пера
Параметр fnPenStyle определяет стиль линии и может принимать одно из следующих значений, определенных в файле windows.h:
Стиль линии | Внешний вид | Описание |
PS_SOLID | Сплошная | |
PS_DASH | Штриховая | |
PS_DOT | Пунктирная | |
PS_DASHDOT | Штрих-пунктирная, одна точка на одну линию | |
PS_DASHDOTDOT | Штрих-пунктирная, две точки на одну линию | |
PS_NULL | Невидимая | |
PS_INSIDEFRAME | Линия, предназначенная для обводки замкнутых фигур |
Параметр clrref задает цвет пера.
На первый взгляд линии PS_SOLID и PS_INSIDEFRAME похожи, однако между ними имеются различия, особенно заметные для широких линий. Широкая линия, имеющая стиль PS_SOLID, располагается по обе стороны оси, заданной координатами линии. Линии, имеющие стиль PS_INSIDEFRAME, располагаются внутри контура, определяющего размеры замкнутой фигуры (рис. 2.14).
Рис. 2.14. Использование стилей PS_SOLID и PS_INSIDEFRAME
Еще одно отличие связано с использованием смешанных цветов (dithered color). Когда Windows не может в точности подобрать цвет, указанный для толстой линии стиля PS_INSIDEFRAME, он раскрашивает эту линию с использованием смешанного цвета, полученного из других цветов. В этом случае изображение линии формируется из отдельных точек разных цветов. Техника смешанных цветов может применяться и при закрашивании замкнутых фигур, о чем мы еще будем говорить.
При рисовании тонких линий, а также линий, имеющих другой стиль, используются только чистые цвета.
Небольшое замечание относительно концов толстых линий. Концы толстых линий закруглены (рис. 2.15).
Рис. 2.15. Закругленные концы толстой линии
Для изображения толстой линии с прямыми концами следует задать прямоугольную область ограничения (см.
ниже раздел, посвященный области ограничения). Можно также нарисовать толстую линию как закрашенный прямоугольник с использованием тонкого пера.
Другая возможность создать перо - вызвать функцию CreatePenIndirect :
HPEN WINAPI CreatePenIndirect(LOGPEN FAR* lplgpn);
Эта функция работает аналогично функции CreatePen, однако в качестве параметра ей необходимо передать указатель на структуру типа LOGPEN, в которой должны находиться характеристики создаваемого пера.
Структура LOGPEN и различные указатели на нее определены в файле windows.h:
typedef struct tagLOGPEN { UINT lopnStyle; // стиль пера POINT lopnWidth; // ширина пера COLORREF lopnColor; // цвет пера } LOGPEN; typedef LOGPEN* PLOGPEN; typedef LOGPEN NEAR* NPLOGPEN; typedef LOGPEN FAR* LPLOGPEN;
Заметим, что ширина пера в данном случае находится в поле x структуры POINT. Поле y не используется.
Если вы создали перо, его можно выбрать в контекст отображения при помощи макрокоманды SelectPen. После этого можно рисовать линии обычным образом, вызывая функции MoveToEx и LineTo.
Созданные вашим приложением перья принадлежат GDI, соответствующие структуры данных располагаются в его сегменте данных. Поэтому если перо больше не нужно, его нужно удалить для освобождения памяти.
Прежде чем удалять созданное вами перо, следует выбрать в контекст отображения одно из встроенных перьев (например то, которое использовалось раньше). После этого для удаления вашего пера нужно вызвать макрокоманду DeleletePen , определенную в файле windowsx.h:
#define DeletePen(hpen) DeleteObject((HGDIOBJ)(HPEN)(hpen))
В качестве параметра этой макрокоманде необходимо передать идентификатор удаляемого пера.
Нельзя удалять перо, если оно выбрано в контекст отображения. Нет никакого смысла в удалении встроенных перьев.
Выбор режима фона
Режим фона влияет на заполнение промежутков между штрихами и точками в штрих-пунктирных, штриховых и пунктирных линиях.
Напомним, что по умолчанию в контексте отображения установлен непрозрачный режим фона OPAQUE . В этом режиме промежутки закрашиваются цветом фона, определенным как атрибут контекста отображения. Приложение может установить прозрачный режим фона TRANSPARENT , в этом случае промежутки в линиях не будут закрашиваться (рис. 2.16).
Рис. 2.16. Режимы фона OPAQUE и TRANSPARENT
Для установки режима фона предназначена функция SetBkMode :
int WINAPI SetBkMode(HDC hdc, int fnBkMode);
Эта функция устанавливает новый режим фона fnBkMode для контекста отображения hdc, возвращая в случае успеха код старого режима фона.
Для параметра fnBkMode вы можете использовать значения OPAQUE или TRANSPARENT, определенные в файле windows.h.
Приложение может определить текущий режим фона, вызвав функцию GetBkMode :
int WINAPI GetBkMode(HDC hdc);
С помощью функций SetBkColor и GetBkColor вы можете, соответственно, установить и определить текущий цвет фона, который используется для закраски промежутков между штрихами и точками линий:
COLORREF WINAPI SetBkColor(HDC hdc, COLORREF clrref); COLORREF WINAPI GetBkColor(HDC hdc);
Выбор режима отображения
Напомним, что режим отображения - это атрибут контекста отображения, влияющий на используемую функциями GDI систему координат. Для обеспечения независимости приложений от аппаратного обеспечения приложения Windows работают с логическими координатами, которые отображаются в физические. Приложения Windows (в отличие от программ MS-DOS) могут не знать номер используемого видеорежима и соответствующее ему разрешение по вертикали и горизонтали в пикселах, определяя размеры элементов формируемого изображения в миллиметрах или дюймах. Хотя в качестве единицы измерения можно использовать и пиксел, если выбрать соответствующий режим отображения.
Из уроков геометрии в школе и институте вы знаете, что существуют различные системы координат, каждая из которых удобна в том или ином случае. Мы не будем описывать все возможные системы координат, ограничившись доступными в Windows версии 3.1 (в операционной системе Windows NT набор систем координат немного расширен).
Изучение режимов отображения Windows версии 3.1 мы начнем с определения основных понятий и терминов, имеющих отношение к системам координат и преобразованию логических координат в физические.
Выбор режима рисования
Возвращаясь к обычной бумаге и карандашу, отметим, что в процессе рисования графит (или иной материал) переносится с острия карандаша на поверхность бумаги. Цвет полученной линии полностью соответствует цвету карандаша и не зависит от цвета бумаги. По умолчанию в контексте отображения выбран именно такой режим рисования , т. е. цвет рисуемой линии не зависит от цвета изображения, поверх которого рисуется линия.
Однако это не единственная возможность. При выборе соответствующего режима рисования цвет линии (на растровых устройствах вывода, таких, как экран монитора) может зависеть от цвета подложки, причем зависимость может быть достаточно сложная.
Для выбора режима рисования предназначена функция SetROP2 :
int WINAPI SetROP2(HDC hdc, int fnDrawMode);
Параметр hdc предназначен для указания контекста отображения, в котором необходимо установить новый режим рисования, определяемый параметром fnDrawMode.
Функция SetROP2 возвращает код предыдущего режима рисования.
Процесс рисования на экране монитора заключается в выполнении логической операции над цветами точек экрана и цветами изображения. Ниже в таблице мы привели возможные значения для параметра fnDrawMode. Для каждого режима рисования в этой таблице есть формула, с использованием которой вычисляется результат, и краткое описание режима рисования. В формулах цвет пера обозначается буквой P, цвет подложки - D.
Режим рисования | Формула | Цвет пиксела |
R2_COPYPEN | P | Соответствует (равен) цвету пера |
R2_BLACK | 0 | Черный |
R2_WHITE | 1 | Белый |
R2_NOP | D | Не меняется, т. е. перо ничего не рисует |
R2_NOT | ~D | Получается инвертированием цвета подложки, т. е. цвета пиксела до рисования |
R2_NOTCOPYPEN | ~P | Получается инвертированием цвета пера |
R2_MASKPEN | P&D | Комбинация компонент цветов, имеющихся как в цвете подложки, так и в цвете пера |
R2_NOTMASKPEN | ~(P&D) | Инверсия предыдущего значения |
R2_MERGEPEN | P|D | Комбинация компонент цветов подложки и пера |
R2_NOTMERGEPEN | ~(P|D) | Инверсия предыдущего значения |
R2_XORPEN | P^D | При определении цвета пиксела выполняется операция "ИСКЛЮЧАЮЩЕЕ ИЛИ" между компонентами цвета подложки и пера |
R2_NOTXORPEN | ~(P^D) | Инверсия предыдущего значения |
R2_MASKNOTPEN | ~P & D | Комбинация цвета подложки и инверсии цвета пера |
R2_MASKPENNOT | P & ~D | Комбинация двух цветов: инверсии цвета подложки и цвета пера |
R2_MERGENOTPEN | ~P | D | Комбинация компонент цветов подложки и инверсии цвета пера |
R2_MERGEPENNOT | P | ~D | Комбинация инверсии цвета подложки и цвета пера |
Если изображение и перо черно-белые, результат выполнения описанных выше операций (которые, кстати, называются растровыми операциями ) можно легко предсказать.
В режиме R2_COPYPEN, который установлен в контексте отображения по умолчанию, цвет нарисованной линии будет такой же, как и цвет пера. Для режимов R2_BLACK и R2_WHITE цвет линии будет, соответственно, черный и белый. В режиме R2_NOP вы не увидите нарисованную линию, так как цвет вдоль нее вообще не изменится. Более интересен режим R2_NOT, при использовании которого на черном фоне будет нарисована белая линия, а на белом фоне - черная.
Для цветных изображений перечисленные выше формулы применяются по отдельности к каждой компоненте цвета (всего в Windows используется три компоненты цвета - красная, зеленая и голубая), поэтому для некоторых режимов рисования цвет линии предсказать достаточно трудно. Использование цветовых палитр, которые мы рассмотрим в третьей главе нашей книги, дополнительно усложняет эту задачу.
С помощью функции GetROP2 приложение может определить режим рисования, установленный для контекста отображения hdc:
int WINAPI GetROP2(HDC hdc);
Выбор шрифта в контекст отображения
Для того чтобы написать строку текста заданным шрифтом, этот шрифт следует, подобно остальным объектам GDI, выбрать в контекст отображения. После этого функции TextOut, DrawText и аналогичные будут использовать для вывода текста нужный вам шрифт.
Приложения Windows могут использовать либо один из встроенных шрифтов, либо создать свой, описав требуемые характеристики шрифта. В любом случае в распоряжение пользователя будет предоставлен один из шрифтов, зарегистрированных при установке Windows или позже (с помощью Control Panel). Для выбора шрифта, соответствующего описанию, используется достаточно сложный алгоритм, учитывающий степень важности обеспечения соответствия параметров предоставленного шрифта запрошенным параметрам.
Обратим ваше внимание на одно важное обстоятельство.
Приложение заказывает шрифт, описывая его параметры. GDI анализирует запрошенные параметры и подбирает наиболее подходящий шрифт. При этом приложение не может "заставить" GDI выделить ему какой-то конкретный шрифт, указав его название или путь к файлу. Однако приложение может определить параметры шрифта, выбранного пользователем из состава установленных шрифтов, и запросить у GDI шрифт с этими параметрами. В последнем случае будет выделен шрифт, выбранный пользователем.
Выбор созданного шрифта в контекст отображения
Если вы заполнили все нужные поля в структуре LOGFONT и затем передали адрес структуры функции CreateFontIndirect, эта функция вернет идентификатор шрифта. Вы должны выбрать шрифт с этим идентификатором в контекст отображения с помощью макрокоманды SelectFont (точно так же, как для встроенных шрифтов):
hfontOldFont = SelectFont(hdc, hfont);
Как только в созданном шрифте отпадет необходимость, его следует удалить при помощи макрокоманды DeleteFont , предварительно выбрав в контекст отображения тот шрифт, который был выбран в него раньше:
#define DeleteFont(hfont) \ DeleteObject((HGDIOBJ)(HFONT)(hfont))
Процесс отображения логического шрифта достаточно сложен. GDI сравнивает заданные в структуре LOGFONT параметры с параметрами различных шрифтов, которые можно использовать для данного устройства отображения, выбирая наиболее подходящий шрифт. Для сравнения используются пенальти (штрафные очки), которые имеют разные весовые коэффициенты. Выбирается тот шрифт, для которого сумма пенальти наименьшая.
Наиболее важное поле в структуре LOGFONT - поле lfCharSet. Если в этом поле будет установлено нулевое значение, будет выбран шрифт ANSI_CHARACTER, так как значение соответствующей ему константы равно нулю. Понятно, почему это поле самое важное - если приложение запрашивает шрифт OEM_CHARSET, оно предполагает использовать для вывода кодировку OEM. Если бы GDI предоставил приложению шрифт в кодировке ANSI, скорее всего, строку было бы невозможно прочесть. Если же в Windows нет ни одного шрифта с кодировкой OEM, приложение все равно получит какой-нибудь шрифт, однако результат вывода текста может оказаться неудовлетворительным.
Учтите, что растровые шрифты семейств Modern, Roman и Script, которые пришли из Windows версии 3.0, отмечены как имеющие кодировку OEM, хотя в действительности для этих шрифтов используется кодировка ANSI. Это сделано для того, чтобы в процессе выбора GDI вначале использовал масштабируемые шрифты перечисленных семейств, и только в крайнем случае остановил свой выбор на растровых шрифтах.
Следующее по важности поле в структуре LOGFONT - это поле lfPitchAndFamily. Оно имеет большое значение потому, что приложение, запрашивающее шрифт с фиксированной шириной букв, может работать неправильно, если ему будет выделен шрифт с переменной шириной букв.
Далее следует поле lfFaceName, а после него - поле lfFamily.
После сравнения всех описанных полей GDI сравнивает высоту букв шрифта (поле lfHeight), затем в сравнении принимают участие поля lfWidth, lfItalic, lfUnderline, lfStrikeOut.
Выбор встроенного шрифта
По умолчанию в контекст отображения при его создании выбирается системный шрифт, основным (и почти единственным) преимуществом которого является то, что он всегда доступен. Системный шрифт не является масштабируемым, содержит буквы переменной ширины, не имеющие засечек, для него используется кодировка ANSI.
Однако в некоторых случаях вам может понадобиться шрифт с фиксированной шириной букв, или шрифт в кодировке OEM. Вы можете получить идентификатор одного из встроенных шрифтов при помощи макрокоманды GetStockFont , описанной в файле windowsx.h:
#define GetStockFont(i) ((HFONT)GetStockObject(i))
В качестве единственного параметра этой макрокоманде следует передать идентификатор одного из встроенных шрифтов:
Идентификатор | Описание |
SYSTEM_FONT | Системный шрифт в кодировке ANSI с переменной шириной букв, используется операционной системой Windows для отображения текста в меню, заголовках окон и диалоговых панелях |
SYSTEM_FIXED_FONT | Шрифт в кодировке ANSI с фиксированной шириной букв. Использовался в старых версиях операционной системой Windows (до версии 3.0) как системный шрифт |
ANSI_VAR_FONT | Шрифт в кодировке ANSI с переменной шириной букв |
ANSI_FIXED_FONT | Шрифт в кодировке ANSI с фиксированной шириной букв |
OEM_FIXED_FONT | Шрифт в кодировке OEM с фиксированной шириной букв |
DEVICE_DEFAULT_FONT | Шрифт, который используется для данного устройства по умолчанию. Если устройство не имеет своих шрифтов, используется системный шрифт SYSTEM_FONT |
После того как вы получили идентификатор шрифта, этот шрифт можно выбрать в контекст отображения макрокомандой SelectFont :
#define SelectFont(hdc, hfont) \ ((HFONT)SelectObject((hdc), (HGDIOBJ)(HFONT)(hfont)))
Первый параметр этой макрокоманды определяет идентификатор контекста отображения, в который выбирается шрифт с идентификатором hfont. Она возвращает идентификатор шрифта, который был выбран в контекст отображения раньше, до вызова SelectFont.
Вам не нужно удалять встроенные шрифты, так же как не нужно удалять встроенные кисти и перья.
Загрузка bmp-файла и проверка заголовков
Вы можете загрузить в оперативную память весь bmp-файл сразу или вначале только заголовки, а затем таблицу цветов и биты изображений. В приложении BMPINFO, рисующем изображения DIB в своем окне, мы использовали первый способ, отведя для загрузки bmp-файла один сплошной блок глобальной памяти.
Составляя программу чтения bmp-файла в память, не следует забывать о том, что размер файла, а следовательно и размер нужного для его загрузки блока памяти, практически всегда превышает 64 Кбайт. Поэтому для чтения такого файла лучше всего использовать функцию _hread , позволяющую прочитать сразу весь файл в один блок памяти любого (теоретически) размера.:
_hread(hfDIBFile, lpBuf, *dwFileSize);
Мы уже пользовались этой функцией для перекодировки файла из OEM в ANSI.
Прочитав файл в память, следует убедиться, что его первые два байта содержат значение 0x4d42 ("BM"). Если это так, нужно определить формат bmp-файла. Для этого следует проанализировать содержимое поля biSize, расположенное сразу после заголовка BITMAPFILEHEADER. Для файлов в формате Windows в этом поле должно быть значение 40, что соответствует размеру структуры BITMAPINFOHEADER. Для файлов в формате Presentation Manager в этом поле должно находиться значение 12 (размер структуры BITMAPCOREHEADER).
Ваше приложение может отвергнуть файл в формате Presentation Manager, и это не будет большим недостатком для приложения Windows. В случае необходимости bmp-файлы Presentation Manager могут быть преобразованы в формат Windows, например, с помощью приложения Paintbrush.
Убедившись в том, что вы загрузили bmp-файл в формате Windows, следует проверить содержимое полей структуры BITMAPINFOHEADER.
Следует проверить поля biPlanes, biBitCount и biCompression. Вы можете использовать для проверки следующие критерии:
Поле | Критерии проверки |
biPlanes | Должно содержать значение 1 |
biBitCount | Может быть равно 1, 4, 8 или 24.Вы можете столкнуться с новыми 16- и 32-битовыми форматами файлов DIB, используемых в Windows NT. Для них в этом поле могут находиться также значения 16 и 32. Если ваше приложение не умеет обрабатывать такие файлы, данную ситуацию следует рассматривать как ошибочную |
biCompression | Может принимать одно из следующих значений: BI_RGB, BI_RLE4, BI_RLE8.При использовании метода компрессии BI_RLE4 содержимое поля biBitCount должно быть равно 4. При использовании метода компрессии BI_RLE8 содержимое поля biBitCount должно быть равно 8.Ваше приложение может ограничиться обработкой bmp-файлов в формате BI_RGB, как это делает, например, приложение Paintbrush |
Можно было бы проверить содержимое и других полей структуры BITMAPINFOHEADER, однако это необязательно, так как они не содержат критической информации. Проверка "с пристрастием" может привести к тому, что пользователи будут думать, будто ваше приложение не умеет читать такие файлы, с которыми легко справляются другие приложения.
Итак, подводя итоги, можно выдать следующие рекомендации:
смело игнорируйте bmp-файлы в формате Presentation Manager, а если вы не можете так поступить, преобразуйте их в формат Windows;
в структуре BITMAPINFOHEADER проверяйте только поля biPlanes, biBitCount и biCompression;
так как метод компрессии RLE4 и RLE8 используются редко и не приводит к значительной экономии памяти, ваше приложение может не поддерживать компрессованные bmp-файлы.
Загрузка изображений из ресурсов приложения
Самый простой способ использования битовых изображений в приложениях Windows заключается в том, что изображение создается графическим редактором в виде bmp-файла и описывается в файле определения ресурсов приложения при помощи оператора BITMAP:
LOGO BITMAP mylogo.bmp
Созданное таким образом битовое изображение можно загрузить в память при помощи функции LoadBitmap :
HBITMAP WINAPI LoadBitmap(HINSTANCE hinst, LPCSTR lpszBitmap);
Параметр hinst определяет идентификатор копии приложения, из ресурсов которого нужно загрузить изображение. Идентификатор ресурса изображения задан параметром lpszBitmap. Функция LoadBitmap возвращает идентификатор загруженного изображения или NULL при ошибке.
После использования приложение должно удалить битовое изображение. Для этого лучше всего воспользоваться макрокомандой DeleteBitmap , описанной в файле windowsx.h следующим образом:
#define DeleteBitmap(hbm) \ DeleteObject((HGDIOBJ)(HBITMAP)(hbm))
В качестве параметра этой макрокоманде нужно передать идентификатор удаляемого изображения.
Приложение может определить параметры загруженного изображения, вызвав функцию GetObject :
int WINAPI GetObject( HGDIOBJ hgdiobj, // идентификатор объекта int cbBuffer, // размер буфера void FAR* lpvObject); // адрес буфера
С помощью этой функции можно получить разнообразную информацию об объектах GDI, таких, как логические перья, кисти, шрифты или битовые изображения.
Для нас интересно использование этой функции с целью получения параметров изображения. Идентификатор изображения должен передаваться через параметр hgdiobj. Параметр lpvObject должен указывать на структуру типа BITMAP, в которую будут записаны сведения об изображении. Через параметр cbBuffer следует передать размер структуры BITMAP:
BITMAP bm; HBITMAP hBitmap; GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm);
Структура BITMAP и указатели на нее описаны в файле windows.h:
typedef struct tagBITMAP { int bmType; int bmWidth; int bmHeight; int bmWidthBytes; BYTE bmPlanes; BYTE bmBitsPixel; void FAR* bmBits; } BITMAP; typedef BITMAP* PBITMAP; typedef BITMAP NEAR* NPBITMAP; typedef BITMAP FAR* LPBITMAP;
Опишем назначение отдельных полей этой структуры.
Поле | Описание |
bmType | Тип битового изображения. Должен быть равен 0 |
bmWidth | Ширина битового изображения в пикселах, должна быть больше 0 |
bmHeight | Высота битового изображения в пикселах, должна быть больше 0 |
bmWidthBytes | Размер памяти, занимаемый одной строкой растра битового изображения. Это значение должно быть четным, так как массив изображения состоит из целых чисел размером 16 бит. Таким образом, произведение bmWidthBytes*8 должно быть кратно 16. Кроме того, это произведение должно быть больше или равно произведению bmWidth*bmBitsPixel |
bmPlanes | Количество плоскостей в битовом изображении. В зависимости от типа видеоадаптера и его режима работы для представления цвета одного пиксела может использоваться несколько бит, расположенных в одной или нескольких плоскостях видеопамяти (подробное описание структуры видеопамяти в различных режимах вы можете найти в 3 томе "Библиотеки системного программиста") |
bmBitsPixel | Количество битов, используемых для представления цвета пиксела. Если используется несколько плоскостей, то это поле содержит количество бит одной плоскости, используемых для представления цвета пиксела |
bmBits | Дальний указатель на массив, содержащий биты изображения |
Для монохромных битовых изображений используется одна плоскость. Для определения цвета пиксела (черный или белый) используется один бит памяти. Размер памяти, занимаемый одной строкой растра битового изображения, кратен величине 16 бит.
Пусть, например, вы подготовили с помощью графического редактора, входящего в состав приложения Resource Workshop, битовое изображение, показанное на рис. 4.1. Для наглядности каждая строка растра этого изображения пронумерована.
Рис. 4.1. Черно-белое битовое изображение
Этому представлению соответствует дамп памяти, представленный ниже:
00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20: 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF 00 30: 00 00 00 FF 00 00 00 00 FF 00 00 00 00 FF 00 00 40: 00 00 00 00 FF 00 00 00 FF 00 00 00 FF 00 00 00 50: 00 00 00 00 00 FF 00 00 FF 00 00 FF 00 00 00 00 60: 00 00 00 00 00 00 FF 00 FF 00 FF 00 00 00 00 00 70: 00 00 00 00 00 00 00 FF 00 FF 00 00 00 00 00 00 80: 00 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 00 90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Так как буфер, в котором хранится строка, должен иметь длину, кратную длине слова (два байта), буфер каждой строки дополняется нулем.
Обратите внимание, что для изображений DDB используется система координат, соответствующая режиму отображения MM_TEXT, т. е. система координат, принятая для устройства отображения.
Закрашивание области
Для закрашивания области кистью, выбранной в контекст отображения, предназначена функция PaintRgn :
BOOL WINAPI PaintRgn(HDC hdc, HRGN hrgn);
Функция FillRgn также закрашивает область, но, в отличие от функции PaintRgn, эта функция использует кисть, идентификатор которой передается ей в качестве параметра hbrush:
BOOL WINAPI FillRgn(HDC hdc, HRGN hrgn, HBRUSH hbrush);
С помощью функции FrameRgn вы можете обвести заданную область (закрасить ее границу), используя кисть hbrush:
BOOL WINAPI FrameRgn(HDC hdc, HRGN hrgn, HBRUSH hbrush, int nWidth, int nHeight);
Параметры nWidth и nHeight определяют, соответственно, ширину и высоту кисти в пикселах, используемой для рисования границы.
Функция InvertRgn инвертирует цвета в указанной области:
BOOL WINAPI InvertRgn(HDC hdc, HRGN hrgn);
Все эти функции возвращают TRUE при успешном завершении или FALSE при ошибке.
Графический редактор битовых изображений Paint Brush, который поставляется вместе с операционной системой Windows, умеет закрашивать внутренние области замкнутых фигур. Для закраски вам достаточно, находясь в соответствующем режиме редактора, указать мышью любую точку внутри фигуры.
Для реализации описанной выше операции в программном интерфейсе GDI предусмотрены функции FloodFill и ExtFloodFill.
Для функции FloodFill необходимо указать идентификатор контекста отображения hdc, координаты точки nX и nY, а также цвет контура clrref, ограничивающего область:
BOOL WINAPI FloodFill(HDC hdc, int nX, int nY, COLORREF clrref);
Функция возвращает TRUE при успешном завершении или FALSE при ошибке (ошибка возникает в том случае, когда указанная точка имеет цвет clrref или она лежит вне области ограничения данного контекста отображения).
Для закраски используется кисть, выбранная в контекст отображения.
Функция ExtFloodFill аналогична функции FloodFill:
BOOL WINAPI ExtFloodFill(HDC hdc, int nX, int nY, COLORREF clrref, UINT fuFillType);
Эта функция имеет дополнительный параметр fuFillType, определяющий способ закраски области. Параметр может принимать значения FLOODFILLBORDER или FLOODFILLSURFACE . В первом случае закрашивается внутренняя область фигуры, ограниченная контуром, имеющим цвет clrref (как и при использовании функции FloodFill). Во втором случае закрашивается вся область, имеющая цвет clrref.
Функция ExtFloodFill возвращает TRUE при успешном завершении или FALSE при ошибке. Если значение параметра fuFillType равно FLOODFILLBORDER, ошибка может возникнуть из-за тех же причин, что и при выполнении функции FloodFill. Если же значение параметра fuFillType равно FLOODFILLSURFACE, ошибка может возникнуть из-за того, что цвет точки (nX,nY) не равен clrref.
Закрашивание внутренней области окна
Напомним, что кисть можно использовать еще и для закрашивания внутренней области окна . Для этого идентификатор кисти следует записать в поле hbrBackground структуры типа WNDCLASS перед регистрацией класса окна:
wc.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);