Графический интерфейс GDI в Microsoft Windows
Атрибуты контекста отображения
В документации, которая поставляется в составе Microsoft SDK, описаны 20 атрибутов контекста отображения . Несколько атрибутов описывают систему координат, используемую для рисования графических изображений. Есть атрибуты, влияющие на цвет графических объектов и цвет фона. Для отображения можно выбрать цветовую палитру (набор цветов). Можно выбрать инструмент для рисования линий и закрашивания внутренней области замкнутых геометрических фигур, таких как многоугольники и эллипсы. Приложение может рисовать внутри области произвольной формы (или вне этой области), причем область также задается в контексте отображения. Есть атрибуты, специально предназначенные для вывода текста. Это шрифт и расстояние между символами, а также цвет текста.Когда приложение получает контекст отображения или создает контекст устройства, все атрибуты контекста имеют значения, установленные по умолчанию. Для того чтобы вы получили представление о назначении атрибутов контекста отображения, приведем их краткое описание.
Битовые изображения в формате DDB
Как мы уже говорили, битовые изображения в формате DDB являются аппаратно-зависимыми. Поэтому структура изображения в оперативной памяти зависит от особенностей аппаратуры.В предыдущих томах "Библиотеки системного программиста" мы использовали изображения DDB и даже приводили исходный текст функции DrawBitmap, с помощью которой можно нарисовать такое изображение на экране.
Как правило, изображения DDB либо загружаются из ресурсов приложения, либо создаются непосредственно в оперативной памяти. Для вывода изображений DDB на экран используются такие функции, как BitBlt и StretchBlt.
Изображения DIB, в отличие от изображений DDB, являются аппаратно-независимыми, поэтому без дополнительного преобразования их нельзя отображать на экране с помощью функций BitBlt и StretchBlt. В операционной системе Windows версий 3.х битовые изображения хранятся в файлах с расширением имени bmp, при этом используется аппаратно-независимый формат DIB.
Битовые изображения в формате DIB
Как мы уже говорили, битовые изображения DDB имеют один существенный недостаток - они "привязаны" к конкретному типу устройства вывода. В графической оболочке Presentation Manager операционной системы OS/2 впервые был использован аппаратно-независимый формат для хранения изображений, который называется DIB.В операционной системе Windows версии 3.0 этот формат получил свое дальнейшее развитие. В частности, была добавлена возможность хранения изображения в компрессованном виде. К сожалению, использованный алгоритм компрессии дает хорошие результаты только для таких изображений, которые содержат большие одинаково закрашенные области. Операционная система Windows NT позволяет использовать новые форматы изображений DIB и произвольные методы компрессии, такие как, например, JPEG (очень эффективный метод компрессии графических изображений, при котором можно ценой потери качества получить практически любую степень сжатия).
В программном интерфейсе Windows нет функции, специально предназначенной для рисования битовых изображений DIB. Поэтому если вы создаете приложение, которое должно отображать bmp-файлы на экране или печатать их на принтере, вам придется иметь дело с внутренней структурой этих файлов. Мы познакомим вас с форматом всех необходимых структур данных, имеющих отношение к bmp-файлам.
Битовые изображения
Если вам нужно вывести на экран сложное изображение, полученное при помощи сканера и содержащее множество мелких деталей, едва ли имеет смысл рисовать его при помощи перьев и кистей. Хотя последнее и возможно, такая процедура будет выполняться слишком долго.В битовом изображении bitmap каждый пиксел представлен своим цветом. Есть черно-белые и цветные изображения, изображения, рассчитанные на определенный тип устройства вывода (использовались в ранних версиях Windows) и не зависящие от типа устройства вывода (впервые появились в Windows версии 3.0).
Рисование битовых изображений выполняется путем копирования соответствующего массива графических данных в видеопамять. Разумеется, приложение не имеет непосредственного доступа к видеопамяти. Для выполнения операции копирования приложение обращается к GDI, вызывая соответствующую функцию. Как правило, копирование битовых изображений выполняется не GDI, а драйвером или даже аппаратурой видеоконтроллера, что благоприятно сказывается на скорости выполнения операции вывода.
Работа с битовыми изображениями - не самое простое из того, что может быть в программировании для Windows. Особенно это касается использования битовых изображений в формате, не зависящем от типа устройства вывода или содержащих цветовые палитры. Ситуация дополнительно усложняется отсутствием единого формата bmp-файлов, содержащих изображения и необходимостью (в профессиональных приложениях) распознавать bmp-файлы, подготовленные в операционной системе OS/2, а также контролировать формат bmp-файлов. Однако только битовые изображения дают возможность получить на экране компьютера красивые рисунки, приближающиеся (на хороших мониторах) по качеству к слайдам, а также работать с движущимися изображениями.
Создавая приложение, которое должно работать с bmp-файлами, следует учитывать, что существует несколько форматов таких файлов. Файлы битовых изображений могут содержать таблицу цветов или цветовую палитру, могут быть сжаты с использованием алгоритмов RLE4 или RLE8 . Кроме того, коммерческая версия приложения должна уметь работать с битовыми изображениями в формате оболочки Presentation Manager операционной системы OS/2. Было бы также неплохо, если бы приложение могло анализировать структуру bmp-файла с целью обнаружения возможных ошибок, так как отображение содержимого неправильного файла может создать полный хаос на экране.
Кроме битовых изображений используются так называемые векторные изображения. Если битовые изображения содержат описание цвета каждого пиксела, векторные изображения состоят из описаний отдельных графических объектов, из которых состоит изображение. Это могут быть линии, окружности и т.п. Некоторые графические редакторы, например, Corel Draw, Microsoft Draw, Micrografx Designer, для внешнего представления изображения используют векторный формат.
Сравнивая эти форматы, отметим, что каждый из них имеет свои преимущества и свои недостатки.
Битовые изображения, как правило, выводятся на экран быстрее, так как их внутренняя структура аналогична (до некоторой степени) структуре видеопамяти. Изображения, получаемые при помощи сканеров и цифровых видеокамер (таких, как PhotoMan фирмы Logitec) получаются именно как битовые изображения.
К недостаткам битовых изображений можно отнести большой объем памяти, требующийся для их хранения (около 1 Мбайт в режиме True Color), невозможность масштабирования без потери качества изображения, а также сложность выделения и изменения отдельных объектов изображения.
Векторные изображения состоят из описаний отдельных элементов, поэтому они легко масштабируются. С помощью такого графического редактора, как, например, Micrografx Designer, вы без особого труда сможете выделить отдельный элемент изображения и изменить его внешний вид.Однако вывод векторных изображений выполняется, как правило, медленнее, чем битовых.
Следует отметить, что некоторые устройства вывода, такие как плоттер (графопостроитель), способен работать только с векторными изображениями, так как с помощью пера можно рисовать только линии.
Существует множество форматов файлов, предназначенных для хранения битовых и векторных изображений. В этой главе мы будем подробно рассматривать только формат файлов с расширением имени bmp (мы будем называть их bmp-файлами). Эти файлы хранят битовые изображения и используются в различных версиях операционной системы Microsoft Windows, Microsoft Windows NT и в графической оболочке Presentation Manager операционной системы OS/2. Векторные изображения можно хранить в виде метафайлов.
Биты изображения
Формат области битов изображения одинаковый для некомпрессованых bmp-файлов Windows и bmp-файлов Presentation Manager (оболочка Presentation Manager операционной системы OS/2 версии 1.х не работает с компрессованными файлами).Каждая строка битового изображения (scan line) хранится в буфере, длина которого кратна двойному слову DWORD. Для черно/белых изображений каждый бит буфера используется для представления цвета одного пиксела (если не используется компрессия).
Формат области битов изображения для bmp-файлов не зависит от аппаратных особенностей какого-либо устройства отображения. Всегда используется одна цветовая плоскость, при этом цвет пиксела представляется разным количеством бит памяти, в зависимости от цветового разрешения изображения.
Последнее обстоятельство может оказаться полезным, например, при печати изображения на принтере, способным работать с оттенками серого цвета. В этом случае драйвер принтера сможет использовать избыточную цветовую информацию, содержащуюся в bmp-файле, для формирования полутонового изображения.
В памяти изображение хранится в перевернутом виде. Поэтому, например, изображение, показанное на рис. 4.1 после вывода будет иметь вид, показанный на рис. 4.6.
Рис. 4.6. Изображение в окне Windows
В отличие от изображений DDB, при работе с изображениями DIB используется система координат, начало которой расположено в левом нижнем углу, а координатные оси направлены вверх и вправо.
Если ваше приложение загружает изображение в оперативную память, необходимо предварительно определить размер буфера. Если изображение не компрессовано (т. е. в поле biCompression структуры BITMAPINFOHEADER находится значение BI_RGB), для вычисления размера буфера можно воспользоваться следующей формулой:
dwSizeImage = ((nBits + 31)/32 * 4) * biHeight
где
nBits = biBitCount * biWidth
В этих формулах переменные biHeight, biBitCount и biWidth являются полями структуры BITMAPINFOHEADER.
Если же изображение хранится в компрессованом формате (в поле biCompression структуры BITMAPINFOHEADER находятся значения BI_RLE4 или BI_RLE8), нужный размер буфера можно получить из поля biSizeImage структуры BITMAPINFOHEADER.
Формат области битов изображения для цветных файлов сложнее, однако размер области определяется аналогично.
Как правило, приложения не формируют сложные изображения непосредственно в оперативной памяти, а загружают их из bmp-файлов. Однако иногда требуется создать простое черно/белое изображение непосредственно в памяти. В этом случае требуется знать точный формат области битов изображения.
Но в большинстве случаев для вывода готового изображения от вас требуется только умение определить размер области битов изображения для получения соответствующего буфера, куда эта область загружается из bmp-файла перед выводом изображения на экран. Поэтому мы не будем рассматривать формат области битов изображения для цветных bmp-файлов. Отметим только, что для bmp-файлов в формате DIB для представления цвета одного пиксела используются несколько бит памяти. Например, в 16-цветных файлах цвет пиксела представляется при помощи 4 бит памяти, в 256-цветных файлах для этой цели используется 8 бит, файлы True Color используют 24 бита.
Цвет фона
Продолжая аналогию между контекстом отображения и листом бумаги, на котором рисуются графические изображения и пишется текст, можно сказать, что атрибут цвета фона (background color ) в контексте отображения соответствует цвету бумаги. Чаще всего для рисования и письма используется белая бумага, вероятно поэтому по умолчанию в контексте отображения выбран фон белого цвета.Приложение может изменить цвет фона, воспользовавшись функцией SetBkColor . Мы опишем эту, а также другие функции, связанные с цветом, в отдельной главе.
Цвет и цветовые палитры
3.1.3.2.
3.3.
3.4.
3.5.
3.6.
До сих пор мы в наших приложениях практически не использовали цвет. Теперь настало время рассказать о том, как с помощью средств GDI можно раскрасить окна ваших приложений.
Не секрет, что цветовые возможности приложений ограничиваются в основном цветовым разрешением аппаратуры. В настоящее время чаще всего используются монохромные видеомониторы VGA (как правило, в портативных компьютерах типа Notebook), цветные видеомониторы VGA и SVGA. Все реже встречаются видеомониторы EGA и CGA, которые мало подходят для работы в Windows.
При использовании монохромных мониторов VGA вместо цветов используются градации серого, поэтому приложения не ограничены в выборе черным и белым цветами. Для мониторов EGA и VGA в распоряжении приложений Windows имеется всего лишь 16 цветов.
Цветовое разрешение для мониторов SVGA зависит от типа видеоконтроллера и от объема установленной в нем видеопамяти. Обычные видеоконтроллеры SVGA могут отображать одновременно только 256 цветов. Самые хорошие видеоконтроллеры SVGA, способные работать в режиме True Color , могут отображать 16,777,216 цветов. Это достаточно много, хотя цветовое разрешение человеческого глаза еще выше.
Для использования режимов с высоким разрешением необходимо использовать драйверы, которые обычно поставляются вместе с видеоконтроллерами. Приведем возможные режимы работы для драйверов Cirrus 542x версии 1.0, которые поставляются с видеоконтроллерами на базе видеопроцессоров фирмы Cirrus Logic при объеме видеопамяти 1 Мбайт:
| Разрешение в пикселах | Цветовое разрешение |
| 1024x768 | 16 |
| 1024x768 | 256 |
| 800x600 | 16 |
| 800x600 | 256 |
| 800x600 | 65,536 |
| 640x480 | 16 |
| 640x480 | 256 |
| 640x480 | 65,536 |
| 640x480 | 16,777,216 |
В нашей книге разрешение 16 цветов мы будем называть низким цветовым разрешением, 256 цветов - средним, 65,536 и 16,777,216 цветов - высоким.
В операционной системе Windows приложения определяют цвет, задавая интенсивности трех его RGB-компонент: красной (R), зеленой (G) и голубой (B).
Интенсивность каждой компоненты задается числом в диапазоне от 0 (минимальная интенсивность) до 255 (максимальная интенсивность). Такая система позволяет приложению указать любой из 16,777,216 цветов (256*256*256 = 16,777,216).
Раскрашивая изображение, приложение Windows может использовать любые цвета. Однако это не означает, что цвет изображения, полученного на экране (или принтере) будет в точности такой, какой был указан при выводе.
Windows учитывает цветовое разрешение устройств вывода, ограничивая соответствующим образом цветовую гамму изображения или работая со смешанными цвета (смешанный цвет образуется из чистых цветов, при этом изображение состоит из точек, имеющих чистые цвета). Соответствующий механизм достаточно сложен и зависит от текущего цветового разрешения.
В режиме с низким цветовым разрешением используются 16 различных цветов (режим совместимости с VGA). Определяя цвет пера для рисования линий, вы можете указать любой цвет, однако в результате будет выбран один из 16 цветов, максимально соответствующий заказанному. Цвет кисти может быть либо чистым (в этом случае используется один из 16 цветов), либо смешанным.
В режиме среднего цветового разрешения может использоваться механизм цветовых палитр. Приложению доступны сотни тысяч цветов, однако одновременно оно может использовать не более 256 цветов, составляющих палитру. К сожалению, механизм палитр не является прозрачным для приложений и сложен в использовании.
В режиме True Color палитры не используются, а полученный на экране цвет изображения в точности соответствует заказанному.
Несмотря на то что стоимость видеоконтроллеров True Color постоянно снижается, такие контроллеры все же дороже обычных видеоконтроллеров SVGA на несколько десятков долларов (есть еще одна проблема, связанная с увеличением объема памяти, необходимого для хранения битовых изображений с высоким цветовым разрешением).
Поэтому когда пользователю не нужна высокая скорость работы в Windows, но нужна возможность работы с разрешением 256 цветов, он может остановить свой выбор на более дешевом видеоконтроллере.Кроме того, даже если в компьютере установлен видеоадаптер True Color, он может использоваться в режимах со средним или низким цветовым разрешением. Для программиста это означает необходимость использования цветовых палитр, так как только в этом случае будут реализованы все цветовые возможности такого видеоконтроллера.
Цвет текста
По умолчанию в контексте отображения для вывода текста выбран черный цвет. Поэтому, если вы не изменили ни одного атрибута контекста отображения, связанного с текстом и цветом, такие функции, как TextOut и DrawText будут выводить черный текст на белом фоне в непрозрачном режиме.Для выбора цвета текста приложение должно использовать функцию SetTextColor .
Цветовая палитра
Цветовая палитра (color palette ) - это таблица цветов. Некоторые устройства отображения способны работать с десятками тысяч цветов, однако из-за ограничений аппаратуры в них можно использовать одновременно только некоторые из них. Типичный пример - видеоконтроллеры SVGA в режиме отображения 256 цветов. Приложения Windows могут составить для себя палитру из 236 цветов (20 цветов используются системой) и использовать ее для вывода таких изображений, как, например, цветные фотографии, преобразованные в двоичные данные при помощи сканера.Использование цветовых палитр мы рассмотрим позже. Отметим только, что работа с палитрами - сложная задача, требующая учета многочисленных тонкостей. Современные адаптеры True Color способны отображать более 16 млн. цветов одновременно, поэтому для них механизм цветовых палитр не используется.
Для работы с палитрами в составе программного интерфейса определены такие функции, как CreatePalette , SelectPalette , RealizePalette , UnrealizeObject .
DIBCreatePalette
Функция создает логическую палитру на базе таблицы цветов изображения DIB, возвращая идентификатор созданной палитры или NULL при ошибке. Если DIB не содержит таблицу цветов, палитра не создается.Прототипы всех описанных выше функций, а также необходимые константы и типы данных описаны в файле dib.hpp (листинг 4.9).
Листинг 4.9. Файл bmpinfo/dib.hpp
#define WINRGB_DIB 1 #define WINRLE4_DIB 2 #define WINRLE8_DIB 3 #define PM_DIB 10
typedef HGLOBAL HDIB; typedef unsigned char huge* LPDIB;
HFILE DIBSelectFile(void); HDIB DIBReadFile(HFILE hfDIBFile, DWORD *dwFileSize); BOOL DIBInfo(HDIB hDib, DWORD dwFileSize); int DIBType(HDIB hDib); WORD DIBNumColors(LPDIB lpDib); WORD DIBHeight(LPDIB lpDib); WORD DIBWidth(LPDIB lpDib); HPALETTE DIBCreatePalette(HDIB hDIB); BOOL DIBPaint(HDC hDC, int x, int y, HDIB hDIB);
Обратите внимание на тип LPDIB, который описан как указатель типа huge:
typedef unsigned char huge* LPDIB;
Это необходимо для правильной адресации внутри массива данных, размер которого превосходит 64 Кбайт.
Файл определения ресурсов приложения BMPINFO приведен в листинге 4.10.
Листинг 4.10. Файл bmpinfo/bmpinfo.rc
#include "bmpinfo.hpp"
APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "&Open", CM_FILEOPEN MENUITEM "&Info...", CM_FILEINFO MENUITEM SEPARATOR MENUITEM "E&xit", CM_FILEEXIT END
POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END
Файл определения модуля приложения BMPINFO вы сможете найти в листинге 4.11.
Листинг 4.11. Файл bmpinfo/bmpinfo.def
; ============================= ; Файл определения модуля ; ============================= NAME BMPINFO DESCRIPTION 'Приложение BMPINFO, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
DIBFindBits
Эта функция возвращает адрес массива бит изображения. В качестве параметра ей необходимо передать адрес зафиксированного блока памяти, содержащего загруженный файл изображения.DIBHeight
Функция возвращает высоту изображения DIB в пикселах как значение поля biHeight структуры BITMAPINFOHEADER.DIBInfo
Функция выводит информацию о загруженном изображении DIB. Идентификатор блока памяти, содержащего файл изображения, передается этой функции в качестве первого параметра. Через второй параметр передается размер файла, определенный функцией чтения файла DIBReadFile.DIBNumColors
Эта функция определяет размер таблицы цветов. Если изображение DIB содержит таблицу цветов уменьшенного размера, возвращается размер таблицы из поля biClrUsed структуры BITMAPINFOHEADER. В противном случае размер таблицы цветов определяется исходя из содержимого поля biBitCount структуры BITMAPINFOHEADER (количество бит памяти, определяющих цвет пиксела).DIBPaint
Эта функция рисует изображение DIB. Первый параметр определяет контекст отображения. Второй и третий - координаты верхнего левого угла прямоугольной области, в которой будет нарисовано изображение. Через последний параметр передается идентификатор блока памяти, содержащего загруженное изображение DIB.Рисование выполняется при помощи функции StretchDIBits.
DIBPaintBlt
Функция аналогична предыдущей, однако перед рисованием выполняется преобразование DIB в DDB. Далее преобразованное изображение выводится через контекст памяти в контекст отображения при помощи функции BitBlt.DIBReadFile
С помощью этой функции открытый файл читается в глобальный блок памяти. Этот блок заказывается непосредственно перед чтением, причем его размер соответствует размеру файла.В случае успеха функция возвращает идентификатор блока памяти, содержащий файл, причем этот блок является перемещаемым.
Во время чтения курсор мыши принимает форму песочных часов, так как процедура чтения может быть достаточно длительной.
Так как размер bmp-файлов обычно больше 64 Кбайт, для чтения используется функция _hread.
DIBSelectFile
Эта функция выводит на экран стандартную диалоговую панель "Open", с помощью которой можно выбрать bmp-файл.Функция возвращает идентификатор открытого файла или NULL, если пользователь отказался от выбора.
DIBType
Функция получает в качестве параметра идентификатор блока памяти, содержащего загруженный файл изображения DIB. Она определяет его формат и выполняет проверку полей структур заголовка.Если в качестве параметра этой функции было передано значение NULL, она возвращает код ошибки-2. Если не удалось зафиксировать блок памяти, возвращается код ошибки -1.
Функция проверяет первые два байта bmp-файла. Если они не содержат значения 0x4d42, возвращается код ошибки 0.
Далее функция определяет формат заголовка. Для изображения в стандарте Windows проверяются поля biPlanes, biBitCount и biCompression структуры BITMAPINFOHEADER, а для изображения в стандарте Presentation Manager - поля biPlanes и biBitCount структуры BITMAPCOREHEADER.
DIBWidth
Функция возвращает ширину изображения DIB в пикселах как значение поля biWidth структуры BITMAPINFOHEADER.Другие функции для работы с изображениями DDB
Если вы создали изображение DDB, то пользуясь его идентификатором, нетрудно скопировать в него новый массив бит, соответствующий новому изображению. Для этой цели можно воспользоваться функцией SetBitmapBits :LONG WINAPI SetBitmapBits( HBITMAP hbmp, DWORD cBits, const void FAR* lpvBits);
Первый параметр этой функции предназначен для передачи идентификатора битового изображения. Параметр lpvBits является указателем на массив бит, размер этого массива задается при помощи параметра cBits.
При успешном выполнении функция возвращает количество байт, использованных для изменения изображения. Если произошла ошибка, возвращается нулевое значение.
Обратную операцию (чтение массива памяти изображения) можно выполнить при помощи функции GetBitmapBits :
LONG WINAPI GetBitmapBits( HBITMAP hbmp, LONG cbBuffer, void FAR* lpvBits);
Эта функция копирует байты изображения hbmp в массив lpvBits, причем размер массива указывается через параметр cbBuffer.
Функция возвращает количество скопированных в буфер байт в случае нормального завершения или нулевое значение при ошибке.
Если вам нужно создать цветное битовое изображение DDB, можно воспользоваться функцией CreateCompatibleBitmap :
HBITMAP WINAPI CreateCompatibleBitmap( HDC hdc, // контекст отображения int nWidth, // ширина изображения int nHeight); // высота изображения
Эта функция создает неинициализированное изображение шириной nWidth пикселов и высотой nHeight пикселов, причем формат изображения соответствует контексту отображения hdc.
Ваше приложение может выбрать неинициализированное изображение, созданное при помощи функции CreateCompatibleBitmap, в контекст памяти (совместимый с тем же контекстом отображения). Затем оно может нарисовать в контексте памяти все что угодно, используя обычные функции GDI, такие как LineTo (передавая им в качестве первого параметра идентификатор контекста памяти). После этого приложение может вызвать функцию BitBlt для отображения результата в окне приложения.
Если желательно создать удаляемое (discardable) битовое изображение, вместо предыдущей функции вы можете вызвать функцию CreateDiscardableBitmap :
HBITMAP WINAPI CreateDiscardableBitmap( HDC hdc, // контекст отображения int nWidth, // ширина изображения int nHeight); // высота изображения
Она имеет параметры, аналогичные параметрам функции CreateCompatibleBitmap. Если изображение, созданное с использованием этой функции, не выбрано в контекст памяти, оно может быть удалено. При попытке выбрать в контекст удаленное изображение функция SelectBitmap вернет нулевое значение. В этом случае необходимо удалить изображение макрокомандой DeleteBitmap, после чего создать его заново.
Другие операции
Для изменения прямоугольной области можно воспользоваться функцией SetRectRgn , изменяющей координаты существующей прямоугольной области:void WINAPI SetRectRgn( HRGN hrgn, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
Изменение области выполняется быстрее ее удаления с последующим повторный созданием новой.
Для перемещения области предназначена функция OffsetRgn :
int WINAPI OffsetRgn(HRGN hrgn, int nX, int nY);
Эта функция сдвигает область на nX логических единиц по горизонтали и на nY логических единиц по вертикали, возвращая такие же значения, что и функция CombineRegion.
Вы можете сравнить две области при помощи функции EqualRgn :
BOOL WINAPI EqualRgn(HRGN hrgn1, HRGN hrgn2);
Эта функция возвращает TRUE, если области совпадают, и FALSE - если нет.
С помощью функции GetRgnBox можно узнать координаты воображаемого прямоугольника, в который вписана область:
int WINAPI GetRgnBox(HRGN hrgn, RECT FAR* lprc);
Эта функция возвращает такие же значения, что и функция CombineRegion
Иногда требуется определить, находится ли заданная точка или прямоугольная область внутри другой области. Это можно сделать при помощи функций PtInRegion и RectInRegion .
Функция PtInRegion возвращает TRUE, если точка (nX,nY) находится внутри области hrgn:
BOOL WINAPI PtInRegion(HRGN hrgn, int nX, int nY);
Аналогично для прямоугольной области:
BOOL WINAPI RectInRegion(HRGN hrgn, const RECT FAR* lprc);
Физическая система координат
На рис. 2.1 показана физическая система координат для экрана видеомонитора.Рис. 2.1. Физическая система координат для экрана видеомонитора
Начало этой системы координат располагается в левом верхнем углу экрана. Ось X направлена слева направо, ось Y - сверху вниз. В качестве единицы длины в данной системе координат используется пиксел.
Для того чтобы определить физическое разрешение устройства вывода (например, размер экрана в пикселах по вертикали и горизонтали), следует использовать функцию GetDeviceCaps , которую мы рассмотрели в 11 томе "Библиотеки системного программиста". Если передать в качестве второго параметра этой функции значения VERTRES и HORZRES , она в любом режиме отображения вернет, соответственно, размер экрана в пикселах по вертикали и по горизонтали. Параметр hdc должен указывать информационный контекст или контекст отображения, связанный с экраном монитора:
HDC hdc; int iVertRes, iHorzRes; hdc = CreateDC("DISPLAY", NULL, NULL, NULL); iVertRes = GetDeviceCaps(hdc, VERTRES); iHorzRes = GetDeviceCaps(hdc, HORZRES); DeleteDC(hdc);
Физическая система координат "привязана" к физическому устройству вывода, поэтому при ее использовании для вывода изображения следует учитывать особенности видеоконтроллера. В 11 томе "Библиотеки системного программиста" в разделе, посвященном метрикам операционной системы Windows, мы подробно рассказали об использовании функции GetDeviceCaps для исследования пространственных характеристик монитора. Для удобства мы воспроизведем приведенную в этом томе таблицу некоторых метрик для видеомониторов различных типов.
| Параметр функции GetDeviceCaps | CGA | EGA | VGA | SVGA 800x 600 | 8514/A | SVGA 1024 x 768 |
| HORZRES | 640 | 640 | 640 | 800 | 1024 | 1024 |
| VERTRES | 200 | 350 | 480 | 600 | 760 | 768 |
| HORZSIZE | 240 | 240 | 208 | 208 | 280 | 208 |
| VERTSIZE | 180 | 175 | 156 | 152 | 210 | 152 |
| ASPECTX | 5 | 38 | 36 | 36 | 10 | 36 |
| ASPECTY | 12 | 48 | 36 | 36 | 14 | 36 |
| ASPECTXY | 13 | 61 | 51 | 51 | 14 | 51 |
| LOGPIXELSX | 96 | 96 | 96 | 96 | 120 | 96 |
| LOGPIXELSY | 48 | 72 | 96 | 96 | 120 | 96 |
Из этой таблицы видны недостатки физической системы координат.
Во-первых, вертикальное (VERTRES) и горизонтальное (HORZRES) разрешение зависит от типа видеоконтроллера.
Во-вторых, физические размеры пикселов (ASPECTX и ASPECTY ), и, что самое главное, отношение высоты и ширины пиксела также зависят от типа видеоконтроллера.
Если приложению требуется нарисовать, например, окружность или квадрат, при использовании физической системы координат придется учитывать форму пикселов, выполняя масштабирование изображения по одной из осей координат. В противном случае вместо окружности и квадрата на экране появятся эллипс и прямоугольник.
Flags
Это поле должно содержать флаги инициализации:PD_ALLPAGES
Переключатель "All" в диалоговой панели "Print" должен находиться во включенном состоянии, при этом предполагается что необходимо распечатать весь текст, а не отдельные страницы или выделенный фрагмент текста.
PD_SELECTION
Переключатель "Selection" в диалоговой панели "Print" должен находиться во включенном состоянии, при этом предполагается что необходимо распечатать выделенный фрагмент текста, но не весь текст или отдельные страницы.
PD_PAGENUMS
Переключатель "Page" в диалоговой панели "Print" должен находиться во включенном состоянии, при этом предполагается что необходимо распечатать отдельные страницы текста, но не выделенный фрагмент текста или весь текст.
PD_NOSELECTION
Переключатель "Selection" должен находиться в заблокированном состоянии.
PD_NOPAGENUMS
Переключатель "Pages" и связанные с ним органы управления должны находиться в заблокированном состоянии.
PD_COLLATE
Переключатель "Collate" должен находиться во включенном состоянии.
PD_PRINTTOFILE
Переключатель "Print to File" должен находиться во включенном состоянии.
PD_PRINTSETUP
При вызове функции PrintDlg вместо диалоговой панели "Print" отображается диалоговая панель "Print Setup".
PD_NOWARNING
Отмена вывода сообщения о том, что в системе не установлен принтер по умолчанию.
PD_RETURNDC
Функция PrintDlg должна вернуть в поле hDC идентификатор контекста устройства, который можно использовать для печати.
PD_RETURNIC
Функция PrintDlg должна вернуть в поле hDC идентификатор информационного контекста, который можно использовать для получения информации о принтере.
PD_RETURNDEFAULT
После возвращения из функции PrintDlg поля hDevMode и hDevNames будут содержать идентификаторы блоков памяти структур DEVMODE и DEVNAMES, заполненных параметрами принтера, выбранного по умолчанию. Если указан флаг PD_RETURNDEFAULT, перед вызовом функции PrintDlg поля hDevMode и hDevNames должны содержать значения NULL, в противном случае функция вернет признак ошибки.
PD_SHOWHELP
В диалоговой панели необходимо отобразить кнопку "Help".
PD_ENABLEPRINTHOOK
Разрешается использовать функцию фильтра для диалоговой панели "Print".
PD_ENABLESETUPHOOK
Разрешается использовать функцию фильтра для диалоговой панели "Print Setup".
PD_ENABLEPRINTTEMPLATE
Разрешается использовать шаблон диалоговой панели "Print", определяемой полями hInstance и lpPrintTemplateName.
PD_ENABLESETUPTEMPLATE
Разрешается использовать шаблон диалоговой панели "Print Setup", определяемой полями hInstance и lpSetupTemplateName.
PD_ENABLEPRINTTEMPLATEHANDLE
Поле hPrintTemplate содержит идентификатор блока памяти с загруженным шаблоном диалоговой панели "Print". Содержимое поля hInstance игнорируется.
PD_ENABLESETUPTEMPLATEHANDLE
Поле hSetupTemplate содержит идентификатор блока памяти с загруженным шаблоном диалоговой панели "Print Setup". Содержимое поля hInstance игнорируется.
PD_USEDEVMODECOPIES
Орган управления "Copies" блокируется, если принтерный драйвер не способен печатать несколько копий.
PD_DISABLEPRINTTOFILE
Блокируется переключатель "Print to File".
PD_HIDEPRINTTOFILE
Переключатель "Print to File" блокируется и удаляется из диалоговой панели.
Формат bmp-файлов Presentation Manager
Оболочка Presentation Manager операционной системы OS/2 использует другой формат bmp-файла (рис. 4.5).Рис. 4.5. Формат bmp-файла для Presentaton Manager операционной системы OS/2 версии 1.х
В самом начале файла расположена структура BITMAPFILEHEADER. Ее формат полностью соответствует формату аналогичной структуры bmp-файлов операционной системы Windows. В этой структуре, в частности, есть информация о смещении области битов изображения относительно начала файла.
После структуры BITMAPFILEHEADER в файле располагается структура BITMAPCOREINFO, содержащая структуру BITMAPCOREHEADER и таблицу цветов в виде массива структур RGBTRIPLE :
typedef struct tagBITMAPCOREINFO { BITMAPCOREHEADER bmciHeader; RGBTRIPLE bmciColors[1]; } BITMAPCOREINFO; typedef BITMAPCOREINFO* PBITMAPCOREINFO; typedef BITMAPCOREINFO FAR* LPBITMAPCOREINFO;
Формат структуры BITMAPCOREHEADER приведен ниже:
typedef struct tagBITMAPCOREHEADER { DWORD bcSize; short bcWidth; short bcHeight; WORD bcPlanes; WORD bcBitCount; } BITMAPCOREHEADER; typedef BITMAPCOREHEADER* PBITMAPCOREHEADER; typedef BITMAPCOREHEADER FAR* LPBITMAPCOREHEADER;
Приведем описание отдельных полей этой структуры.
| Поле | Описание |
| bcSize | Размер структуры BITMAPCOREHEADER в байтах |
| bcWidth | Ширина битового изображения в пикселах |
| bcHeight | Высота битового изображения в пикселах |
| bcPlanes | Количество плоскостей в битовом изображении. Содержимое этого поля должно быть равно 1 |
| bcBitCount | Количество битов на один пиксел. Может быть равно 1, 4, 8 или 24 |
Таблица цветов в bmp-файле в формате Presentation Manager расположена после структуры BITMAPCOREHEADER и представляет собой массив структур RGBTRIPLE , содержащих RGB-компоненты цвета:
typedef struct tagRGBTRIPLE { BYTE rgbtBlue; BYTE rgbtGreen; BYTE rgbtRed; } RGBTRIPLE; typedef RGBTRIPLE FAR* LPRGBTRIPLE;
Зная количество битов, используемых для представления одного пиксела изображения, нетрудно определить количество элементов в таблице цветов:
wClrUsed = 1 << bcBitCount;
Формат bmp-файлов Windows
Формат bmp-файлов для операционной системы Windows версий 3.0 и 3.1 представлен на рис. 4.4 (в более старых версиях bmp-файлы содержали битовые изображения в формате DDB).Рис. 4.4. Формат bmp-файла для Windows версии 3.х
Файл, содержащий битовое изображение, начинается со структуры BITMAPFILEHEADER. Эта структура описывает тип файла и его размер, а также смещение области битов изображения.
Сразу после структуры BITMAPFILEHEADER в файле следует структура BITMAPINFO, которая содержит описание изображения и таблицу цветов. Описание изображения (размеры изображения, метод компрессии, размер таблицы цветов и т.д.) находится в структуре BITMAPINFOHEADER. В некоторых случаях (не всегда) в файле может присутствовать таблица цветов (как массив структур RGBQUAD), присутствующих в изображении.
Биты изображения обычно располагаются сразу после таблицы цветов. Точное значение смещения битов изображения находится в структуре BITMAPFILEHEADER.
Структура BITMAPFILEHEADER , а также указатели на нее, описаны в файле windows.h:
typedef struct tagBITMAPFILEHEADER { UINT bfType; DWORD bfSize; UINT bfReserved1; UINT bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER; typedef BITMAPFILEHEADER* PBITMAPFILEHEADER; typedef BITMAPFILEHEADER FAR* LPBITMAPFILEHEADER;
Структура BITMAPFILEHEADER одинакова как для bmp-файлов Windows, так и для bmp-файлов оболочки Presentation Manager (рис. 4.5). И в том, и в другом случае она расположена в начале файла и обычно используется для идентификации типа файла.
Приведем описание полей этой структуры.
| Поле | Описание |
| bfType | Тип файла. Поле содержит значение 0x4D42 (текстовая строка "BM"). Анализируя содержимое этого поля, приложение может идентифицировать файл как содержащий битовое изображение |
| bfSize | Размер файла в байтах. Это поле может содержать неправильное значение, так как в SDK для Windows версии 3.0 поле bfSize было описано неправильно (утверждалось, что это поле содержит размер файла в двойных словах). Обычно содержимое этого поля игнорируется, так как из-за ошибки в документации старые приложения устанавливали в этом поле неправильное значение |
| bfReserved1 | Зарезервировано, должно быть равно 0 |
| bfReserved2 | Зарезервировано, должно быть равно 0 |
| bfOffBits | Смещение битов изображения от начала файла в байтах. Область изображения не обязательно должна быть расположена сразу вслед за заголовками файла или таблицей цветов (если она есть) |
В структуре BITMAPFILEHEADER для нас важны два поля - поле bfType, определяющее тип файла, и поле bfOffBits, определяющее смещение битов, из которых формируется изображение. Остальные поля можно проигнорировать. В частности, размер файла нетрудно определить средствами файловой системы MS-DOS.
Сразу после структуры BITMAPFILEHEADER в bmp-файле расположена структура BITMAPINFO (для изображений Windows) или BITMAPCOREINFO (для изображений Presentation Manager).
Структура BITMAPINFO и указатели на нее описаны в файле windows.h следующим образом:
typedef struct tagBITMAPINFO { BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[1]; } BITMAPINFO; typedef BITMAPINFO* PBITMAPINFO; typedef BITMAPINFO FAR* LPBITMAPINFO;
Структура BITMAPINFOHEADER описывает размеры и способ представления цвета в битовом изображении:
typedef struct tagBITMAPINFOHEADER { DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER; typedef BITMAPINFOHEADER* PBITMAPINFOHEADER; typedef BITMAPINFOHEADER FAR* LPBITMAPINFOHEADER;
Опишем назначение отдельных полей этой структуры.
| Поле | Описание |
| biSize | Размер структуры BITMAPINFOHEADER в байтах |
| biWidth | Ширина битового изображения в пикселах |
| biHeight | Высота битового изображения в пикселах |
| biPlanes | Количество плоскостей в битовом изображении. Содержимое этого поля должно быть равно 1 |
| biBitCount | Количество битов на один пиксел. Может быть равно 1, 4, 8 или 24. Для новых 16- и 32-битовых форматов файлов DIB, используемых в Windows NT, в этом поле могут находиться также значения 16 и 32 |
| biCompression | Метод компрессии. Может принимать одно из следующих значений:BI_RGB - компрессия не используетсяBI_RLE4 - компрессия изображений, в которых для представления пиксела используется 4 бита. При использовании этого метода компрессии содержимое поля biBitCount должно быть равно 4BI_RLE8 - компрессия изображений, в которых для представления пиксела используется 8 бит. При использовании этого метода компрессии содержимое поля biBitCount должно быть равно 8BI_BITFIELDS - другой формат компрессии. Это значение используется для Windows NT. Соответствующая константа описана в файле windows.h, который поставляется вместе со средствами разработки приложений Windows NT |
| biSizeImage | Размер изображения в байтах. Это поле содержит размер, необходимый для хранения разжатого изображения. Если компрессия не используется (в поле biCompression находится значение BI_RGB), содержимое поля biSizeImage может быть равно 0 |
| biXPelsPerMeter | Разрешение устройства вывода по горизонтали в пикселах на метр, необходимое для вывода битового изображения без искажений. Это поле используется не всегда. Если оно не используется, в нем следует установить нулевое значение. |
| biYPelsPerMeter | Разрешение устройства вывода по вертикали в пикселах на метр, необходимое для вывода битового изображения без искажений. Это поле, как и предыдущее, используется не всегда. Если оно не используется, в нем следует установить нулевое значение |
| biClrUsed | Размер таблицы цветов. Это поле определяет размер массива структур RGBQUAD (рис. 4.4), расположенного в файле сразу после структуры BITMAPINFOHEADER. Если в этом поле находится нулевое значение, размер таблицы цветов зависит от количества бит, используемых для представления цвета одного пиксела (поле biBitCount) |
| biClrImportant | Количество цветов, необходимое для отображения файла без искажений. Обычно в этом поле находится нулевое значение, в этом случае важны все цвета |
Сразу после структуры BITMAPINFOHEADER в фале может находиться таблица цветов. Эта таблица содержит массив структур RGBQUAD :
typedef struct tagRGBQUAD { BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD; typedef RGBQUAD FAR* LPRGBQUAD;
Поля rgbBlue, rgbGreen и rgbRed содержат RGB-компоненты цветов, поле rgbReserved зарезервировано и должно содержать нулевое значение.
Как мы уже говорили, файл битового изображения может содержать таблицу цветов, а может и не содержать ее. Приведем зависимость размера таблицы цветов в зависимости от значения поля biBitCount (количество бит, используемых для представления одного пиксела):
| Значение biBitCount | Размер таблицы цветов |
| 1 | 2 |
| 4 | 16 |
| 8 | 256 |
| 24 | не используется |
Форматы файлов и структур данных
Если рассматривать структуру bmp-файлов в общем, для нас интересны два формата. Первый формат используется в Windows версий 3.х, второй - в графической оболочке Presentation Manager операционной системы OS/2 версий 1.х. Первый из этих форматов является естественным для приложений Windows. Что же касается второго формата, такие приложения, как Paint Brush, способны конвертировать его в стандартный формат Windows.Ваше приложение может либо полностью проигнорировать формат bmp-файлов оболочки Presentation Manager, либо (что лучше) автоматически преобразовывать его к формату Windows, как это делают стандартные приложения Windows.
Функции для печати
В операционной системе Windows версии 3.0 и более ранних версий для печати использовалась одна функция Escape, которая имела множество подфункций (63 подфункции). Начиная с версии 3.1 вместо этой функции рекомендуется использовать несколько других.Чаще всего используются семь функций.
StartDoc
Эта функция начинает печать нового документа (формирует задание на печать). Она должна вызываться один раз перед началом печати нового документа
StartPage
Эта функция подготавливает устройство вывода к печати новой страницы документа. После вызова этой функции приложение может начинать печать, используя контекст принтера.
EndPage
Функция EndPage завершает процесс печати страницы. Метафайл, созданный в процессе печати одной страницы, проигрывается на принтере.
EndDoc
Функция завершает процесс печати документа. Она вызывается один раз после завершения печати документа.
AbortDoc
Эта функция предназначена для аварийного завершения процесса печати документа.
SetAbortProc
Функция SetAbortProc используется для обеспечения возможности аварийного завершения процесса печати, а также для того чтобы другие приложения могли работать во время печати.
ResetDC
С помощью этой функции можно изменить параметры печати для отдельных листов документа.
Как пользоваться этими функциями?
После получения контекста устройства для принтера ваше приложения должно установить адрес функции отмены печати, вызвав функцию SetAbortProc, и вывести на экран диалоговую панель, позволяющую отменить печать. Диалоговая панель отмены печати должна быть немодальной.
Функция диалоговой панели обычно используется для установки глобального флага отмены печати, который периодически проверяется функцией, выполняющей печать.
Приведем прототип функции SetAbortProc :
int SetAbortProc(HDC hdc, ABORTPROC abrtprc);
Первый параметр определяет контекст устройства, для которого устанавливается функция отмены печати, второй - адрес функции отмены печати, полученный при помощи функции MakeProcInstance .
Функция отмены печати может выглядеть, например, следующим образом:
BOOL CALLBACK _export AbortFunc(HDC hdc, int nCode) { MSG msg; while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { if(!hdlgAbort !IsDialogMessage(hdlgAbort, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return(!fAbort); }
В качестве первого параметра функции передается контекст устройства, в качестве второго - код ошибки. Однако обычно оба эти параметра игнорируются.
GDI в процессе печати периодически вызывает функцию отмены печати, которая действует аналогично обычному циклу обработки сообщений, выполняя выборку сообщений из очереди и их диспетчеризацию. Если в очереди больше нет сообщений, функция PeekMessage возвращает FALSE, при этом цикл обработки сообщений завершается.
После этого функция возвращает инвертированное значение флага отмены печати. Если печать должна быть продолжена, функция отмены должна вернуть значение FALSE, если отменена - TRUE.
В качестве функции диалога, предназначенного для отмены печати, можно использовать, например, такую функцию:
BOOL CALLBACK _export AbortDlgFunc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_INITDIALOG: { fAbort = FALSE; hdlgAbort = hdlg; return TRUE; } case WM_COMMAND: { if (wParam == IDOK wParam == IDCANCEL) { fAbort = TRUE; return TRUE; } return FALSE; } case WM_DESTROY: { hdlgAbort = 0; return FALSE; } } return FALSE; }
Основной задачей данной функции является установка глобального флага отмены печати fAbort:
fAbort = TRUE;
Итак, мы получили контекст принтера, установили для этого контекста процедуру отмены печати, вывели на экран диалоговую панель с кнопкой "Cancel", позволяющую отменить печать. Теперь можно приступать к печати документа.
Прежде всего необходимо вызвать функцию StartDoc :
int StartDoc( HDC hdc, // контекст принтера DOCINFO FAR* lpdi); // адрес структуры DOCINFO
Перед вызовом этой функции нужно подготовить структуру DOCINFO , адрес которой передается через параметр lpdi:
typedef struct { int cbSize; LPCSTR lpszDocName; LPCSTR lpszOutput; } DOCINFO;
Поле cbSize должно содержать размер структуры в байтах.
Поле lpszDocName должно содержать указатель на текстовую строку, закрытую двоичным нулем, в которой записано название документа.
Через поле lpszOutput можно передать адрес строки, содержащей имя файла (если печать должна быть переназначена в файл). Поле может содержать NULL, в этом случае выполняется печать на принтере.
Далее можно начинать цикл печати страниц документа.
Печать страницы документа начинается с вызова функции StartPage :
int StartPage(HDC hdc);
Эта функция подготавливает принтер для приема данных. При успешном завершении функция возвращает положительное значение, отличное от нуля, при ошибке - нуль или значение, меньшее нуля.
После вызова функции StartPage приложение может начинать печать страницы, вызывая обычные функции GDI, такие как TextOut, BitBlt, StretchBlt, Rectangle и т. п.
Завершив печать одной страницы, приложение должно вызвать функцию EndPage :
int EndPage(HDC hdc);
Эта функция не только загружает в принтер новую страницу, и даже не столько загружает новую страницу, сколько проигрывает созданный на этапе рисования метафайл, т. е. выполняет процесс печати страницы.
При успешном завершении функция возвращает положительное значение, отличное от нуля, при ошибке - нуль или значение, меньшее нуля
В цикле печати необходимо проверять значение, возвращаемое функцией EndPage, а также глобальный флаг отмены печати, который устанавливается функцией диалоговой панели отмены печати.
После успешной печати всех страниц документа нужно вызвать функцию EndDoc :
int EndDoc(HDC hdc);
Если печать была отменена, или произошла ошибка, вместо этой функции следует вызвать функцию AbortDoc :
int AbortDoc(HDC hdc);
Если в процессе печати нужно изменить параметры принтера для отдельных страниц документа, вы можете вызвать до функции StartPage функцию ResetDC :
HDC ResetDC( HDC hdc, // контекст печати const DEVMODE FAR* lpdm); // адрес структуры DEVMODE
Эта функция изменяет контекст печати на основе информации, представленной в структуре DEVMODE.
Приложение обычно вызывает эту функцию также в ответ на сообщение WM_DEVMODECHANGE, которое посылается приложению при изменении параметров принтера.
Учтите, что функция StartPage запрещает изменение контекста печати, поэтому вы не сможете изменить параметры печати в процессе печати страницы.
Функция ChooseColor
В составе DLL-библиотеки commdlg.dll есть функция ChooseColor , которая предназначена для выбора цвета . Эта функция выводит на экран диалоговую панель (рис. 3.3), с помощью которой пользователь может выбрать цвет из основного набора цветов "Basic Color" и дополнительного "Custom Colors". Возможность использования цветовых палитр при выборе не обеспечивается, так что с помощью этой функции можно выбирать только чистые статические или смешанные цвета.Рис. 3.3. Диалоговая панель для выбора цвета
Если нажать на кнопку "Define Custom Colors...", внешний вид диалоговой панели изменится (рис. 3.4).
Рис. 3.4. Расширенная диалоговая панель
Пользуясь правой половиной диалоговой панели, пользователь сможет добавить новый цвет в набор "Custom Colors" и затем выбрать его из этого набора.
Функция ChooseColor описана в файле commdlg.h:
BOOL WINAPI ChooseColor(CHOOSECOLOR FAR* lpcc);
Перед вызовом функции следует заполнить структуру CHOOSECOLOR, передав функции ее адрес через параметр lpcc.
В случае успешного выбора цвета функция возвращает TRUE. Если пользователь отказался от выбора или, если произошла ошибка, возвращается значение FALSE.
Структура CHOOSECOLOR и указатель на нее описаны в файле commdlg.h:
typedef struct tagCHOOSECOLOR { DWORD lStructSize; HWND hwndOwner; HWND hInstance; COLORREF rgbResult; COLORREF FAR* lpCustColors; DWORD Flags; LPARAM lCustData; UINT (CALLBACK* lpfnHook)(HWND, UINT, WPARAM, LPARAM); LPCSTR lpTemplateName; } CHOOSECOLOR; typedef CHOOSECOLOR FAR *LPCHOOSECOLOR;
Опишем назначение и правила использования отдельных полей этой структуры.
Поле lStructSize заполняется перед вызовом функции. Оно должно содержать размер структуры в байтах.
Поле Flags также заполняется до вызова функции. В него следует записать флаги инициализации, влияющие на использование других полей этой структуры.
| Флаг | Описание |
| CC_RGBINIT | Для цвета, выбранного по умолчанию, используется цвет, указанный в поле rgbResult |
| CC_FULLOPEN | Если указан этот флаг, на экране появляется полный вариант диалоговой панели, обеспечивающий возможность определения произвольного цвета. Если этот флаг не указан, для определения произвольного цвета пользователь должен нажать на кнопку "Define Custom Color" |
| CC_PREVENTFULLOPEN | Кнопка "Define Custom Color" блокируется, таким образом, при использовании этого флага пользователь не может определить произвольный цвет |
| CC_SHOWHELP | Флаг разрешает отображение кнопки "Help". Если указан этот флаг, в поле нельзя указывать значение hwndOwner |
| CC_ENABLEHOOK | Если указан этот флаг, используется функция фильтра, адрес которой указан в поле lpfnHook. С помощью этой функции можно организовать дополнительную обработку сообщений от диалоговой панели |
| CC_ENABLETEMPLATE | Используется шаблон диалоговой панели, определяемый содержимым поля hInstance. Адрес строки, содержащей имя шаблона, должен быть указан в поле lpTemplateName |
| CC_ENABLETEMPLATEHANDLE | Используется шаблон диалоговой панели, идентификатор которого записан в поле hInstance. Содержимое поля lpTemplateName игнорируется |
В поле hwndOwner перед вызовом функции следует записать идентификатор окна, создавшего диалоговую панель, или NULL. В последнем случае нельзя использовать флаг CC_SHOWHELP.
Поле hInstance заполняется в тех случаях, когда приложение использует свой собственный шаблон диалоговой панели (вместо стандартного шаблона, расположенного в ресурсах DLL-библиотеки). В этом случае перед вызовом функции в это поле следует записать идентификатор модуля, содержащего шаблон диалоговой панели. В поле Flags необходимо указать флаги CC_ENABLETEMPLATE или CC_ENABLETEMPLATEHANDLE.
В поле lpTemplateName следует записать адрес текстовой строки идентификатора ресурса шаблона диалоговой панели (если этот шаблон используется).
Поле rgbResult предназначено для передачи приложению цвета, выбранного пользователем. Если записать в это поле значение NULL, сразу после вывода диалоговой панели выбора цвета по умолчанию будет выбран черный цвет. Вы, однако, можете использовать для начального выбора любой другой цвет, записав его в это поле и указав флаг CC_RGBINIT.
Перед вызовом функции вы должны подготовить массив из 16 двойных слов, содержащих цвета для использования в меню "Custom Colors". Адрес этого массива следует передать через параметр lpCustColors.
Поле lCustData используется для передачи данных функции фильтра (если она определена).
Адрес функции фильтра передается через параметр lpfnHook. Для использования функции фильтра следует указать флаг CC_ENABLEHOOK.
Функция ChooseFont
Только что мы приближенно описали алгоритм, который используется GDI для отображения логического шрифта на физический. Нетрудно заметить, что приложение может получить от GDI совсем не тот шрифт, который ему нужен. Поэтому лучше всего предоставить пользователю возможность выбрать шрифт самостоятельно.DLL-библиотека commdlg.dll содержит функцию ChooseFont , специально предназначенную для выбора одного из зарегистрированных в системе шрифтов. Эта функция выводит на экран диалоговую панель "Font", с помощью которой пользователь может выбрать шрифт, стиль шрифта, размер шрифта, цвет букв, может выбрать подчеркнутый или перечеркнутый шрифт (рис. 5.2).
Рис. 5.2. Диалоговая панель "Font"
Из списка "Font", который расположен в левой верхней части этой диалоговой панели, пользователь может выбрать название шрифта. Список "Font Style" позволяет выбрать один из доступных стилей, например, наклонный или жирный шрифт. Список "Size" предназначен для выбора размера шрифта. С помощью переключателей "Strikeout" и "Underline", расположенных в поле "Effects", можно создать, соответственно, перечеркнутый и подчеркнутый шрифт. И, наконец, из меню "Color" можно выбрать цвет букв.
Образец выбранного шрифта отображается в поле "Sample".
Обратите внимание на то, что в списке "Font" некоторые шрифты отмечены двойной буквой "T". Это масштабируемые шрифты True Type.
Приведем прототип функции ChooseFont:
BOOL WINAPI ChooseColor(CHOOSEFONT FAR* lpcf);
Единственный параметр функции является указателем на структуру типа CHOOSEFONT. Эта структура, а также сама функция ChooseFont, определены в файле commdlg.h. Структура определена следующим образом:
typedef struct tagCHOOSEFONT { DWORD lStructSize; HWND hwndOwner; HDC hDC; LOGFONT FAR* lpLogFont; int iPointSize; DWORD Flags; COLORREF rgbColors; LPARAM lCustData; UINT (CALLBACK* lpfnHook)(HWND, UINT, WPARAM, LPARAM); LPCSTR lpTemplateName; HINSTANCE hInstance; LPSTR lpszStyle; UINT nFontType; int nSizeMin; int nSizeMax; } CHOOSEFONT; typedef CHOOSEFONT FAR *LPCHOOSEFONT;
Перед вызовом функции ChooseFont вы должны проинициализировать нужные поля структуры CHOOSEFONT, записав в остальные поля нулевые значения.
Опишем назначение отдельных полей структуры CHOOSEFONT:
| Поле | Описание |
| lStructSize | Размер структуры в байтах. Это поле необходимо заполнить перед вызовом функции ChooseFont |
| hwndOwner | Идентификатор окна, которому будет принадлежать диалоговая панель. Если в поле Flags не указан флаг CF_SHOWHELP, в это поле можно записать значение NULL. Поле заполняется до вызова функции ChooseFont |
| hDC | Идентификатор контекста отображения или информационного контекста для принтера. Если установлен флаг CF_PRINTERFONTS, в списке появятся шрифты, доступные в данном контексте |
| lpLogFont | Указатель на структуру LOGFONT. Приложение может заполнить нужные поля в этой структуре перед вызовом функции ChooseFont. Если при этом будет установлен флаг CF_INITTOLOGFONTSTRUCT, выбранные значения будут использоваться в качестве начальных. |
| iPointSize | Размер букв выбранного шрифта в десятых долях пункта. Содержимое этого поля устанавливается после возврата из функции ChooseFont |
| Flags | Флаги инициализации диалоговой панели. Можно использовать следующие значения:CF_APPLY - разрешается использование кнопки "Apply";CF_ANSIONLY - в списке выбора появляются только шрифты в кодировке ANSI;CF_BOTH - в списке шрифтов появляются экранные и принтерные шрифты;CF_TTONLY - можно выбирать только масштабируемые шрифты True Type;CF_EFFECTS - если указан этот флаг, с помощью диалоговой панели можно определять цвет букв создавать подчеркнутые и перечеркнутые шрифты. В этом случае необходимо перед вызовом функции проинициализировать содержимое полей lfStrikeOut, lfUnderline, rgbColors;CF_ENABLEHOOK - разрешается использовать функцию фильтра адрес которой указан в поле lpfnHook;CF_ENABLETEMPLATE - разрешается использование шаблона диалоговой панели, определяемого содержимым полей hInstance и lpTemplateName;CF_ENABLETEMPLATEHANDLE - флаг указывает, что поле hInstance содержит идентификатор загруженного шаблона диалоговой панели. Содержимое поля lpTemplateName игнорируется;CF_FIXEDPITCHONLY - можно выбрать только шрифты с фиксированной шириной символов:CF_FORCEFONTEXIST - выдается сообщение об ошибке, если пользователь пытается выбрать несуществующий шрифт;CF_INITTOLOGFONTSTRUCT - для инициализации диалоговой панели используется содержимое структуры LOGFONT, адрес которой передается через поле lpLogFont;CF_LIMITSIZE - при выборе шрифта учитывается содержимое полей nSizeMin и nSizeMax;CF_NOFACESEL - отменяется выбор в списке "Font";CF_NOOEMFONTS - нельзя выбирать векторные шрифты, этот флаг аналогичен флагу CF_NOVECTORFONTS;CF_NOSIMULATIONS - запрещается эмуляция шрифтов;CF_NOSIZESEL - отменяется выбор размера шрифта;CF_NOSTYLESEL - отменяется выбор стиля шрифта;CF_NOVECTORFONTS - нельзя выбирать векторные шрифты, этот флаг аналогичен флагу CF_NOOEMFONTS;CF_PRINTERFONTS - в списке появляются только такие шрифты, которые поддерживаются принтером, контекст отображения для которого задан в поле hDC;CF_SCALABLEONLY - можно выбирать только масштабируемые и векторные шрифты;CF_SCREENFONTS - можно выбирать только экранные шрифты;CF_SHOWHELP - в диалоговой панели отображается кнопка "Help";CF_USESTYLE - строка lpszStyle содержит указатель на буфер, который содержит строку описания стиля. Эта строка используется для инициализации списка "Font Style" диалоговой панели "Font";CF_WYSIWYG - можно выбирать только такие шрифты, которые доступны и для отображения на экране, и для печати на принтере. Если установлен этот флаг, следует также установить флаги CF_BOTH и CF_SCALABLEONLY |
| rgbColors | Цвет символов шрифта, который будет выбран в меню "Colors" диалоговой панели "Fonts" сразу после отображения диалоговой панели. Должен использоваться флаг CF_EFFECTS. Поле заполняется до вызова функции ChooseFont, после возврата из функции поле содержит значение выбранного цвета |
| lCustData | Произвольные данные, передаваемые функции фильтра, определенной содержимым поля lpfnHook |
| lpfnHook | Указатель на функцию фильтра, обрабатывающую сообщения, поступающие в диалоговую панель. Для работы с фильтром необходимо в поле Flags указать флаг CF_ENABLEHOOK |
| lpTemplateName | Строка, закрытая двоичным нулем, которая содержит идентификатор шаблона диалоговой панели. Для использования этого поля необходимо указать флаг CF_ENABLETEMPLATE |
| hInstance | Идентификатор модуля, который содержит шаблон диалоговой панели в качестве ресурса. Поле используется только в тех случаях, когда в поле Flags указаны значения CF_ENABLETEMPLATE или CF_ENABLETEMPLATEHANDLE. Поле заполняется до вызова функции ChooseFont |
| lpszStyle | Указатель на буфер, содержащий строку, описывающую шрифт. Если указан флаг CF_USESTYLE, эта строка используется для инициализации списка "Font Style". Размер буфера должен быть не меньше LF_FACESIZE байт |
| nFontType | Тип выбираемого шрифта. Можно использовать одно из следующих значений:SIMULATED_FONTTYPE - GDI может эмулировать этот шрифт;PRINTER_FONTTYPE - принтерный шрифт;SCREEN_FONTTYPE - экранный шрифт;BOLD_FONTTYPE - жирный шрифт, используется только для шрифтов True Type;ITALIC_FONTTYPE - наклонный шрифт, используется только для шрифтов True Type;REGULAR_FONTTYPE - не жирный и не наклонный шрифт, используется только для шрифтов True Type |
| nSizeMin | Минимальный размер шрифта, который можно выбрать. Для использования этого поля необходимо установить флаг CF_LIMITSIZE |
| nSizeMax | Максимальный размер шрифта, который можно выбрать. Для использования этого поля необходимо установить флаг CF_LIMITSIZE |
Функция EnumFontFamilies
В тех случаях, когда вас не устраивают возможности диалоговой панели "Font", создаваемой функцией ChooseFont, придется использовать для выбора шрифтов список, создаваемый с помощью функции EnumFontFamilies . Эту функцию можно также использовать для получения информации о шрифте.Приведем прототип функции EnumFontFamilies:
int EnumFontFamilies( HDC hdc, // идентификатор контекста отображения LPCSTR lpszFamily, // адрес имени семейства шрифта FONTENUMPROC fntenmprc, // адрес функции обратного вызова LPARAM lParam); // произвольные данные
Параметр hdc определяет контекст отображения устройства, для которого требуется получить список доступных шрифтов.
Через параметр lpszFamily передается адрес строки, закрытой двоичным нулем, содержащей название шрифта, для которого требуется получить информацию. Если этот параметр указан как NULL, выбирается один произвольный шрифт для каждого семейства шрифтов.
Параметр fntenmprc задает адрес функции обратного вызова, полученный с помощью функции MakeProcInstance.
Приложение может передать функции обратного вызова произвольные 32-разрядные данные через параметр lParam.
Функция обратного вызова должна быть определена следующим образом (для функции можно использовать любое имя):
int CALLBACK _export EnumFontFamProc( NEWLOGFONT FAR* lpnlf, // адрес структуры NEWLOGFONT NEWTEXTMETRIC FAR* lpntm, // адрес структуры метрики // физического шрифта int FontType, // тип шрифта LPARAM lParam); // адрес произвольных данных, переданных // функции EnumFontFamilies через // параметр lParam
Когда функция EnumFontFamProc получает управление, через параметр lpnlf передается адрес структуры NEWLOGFONT , которая по непонятной причине не описана в файле windows.h. Эта структура аналогична структуре LOGFONT, но имеет в конце два дополнительных поля, определенных только для шрифтов True Type:
typedef struct tagNEWLOGFONT { int lfHeight; int lfWidth; int lfEscapement; int lfOrientation; int lfWeight; BYTE lfItalic; BYTE lfUnderline; BYTE lfStrikeOut; BYTE lfCharSet; BYTE lfOutPrecision;
BYTE lfFullName[2 * LF_FACESIZE];// только для True Type
BYTE lfStyle[LF_FACESIZE]; // только для True Type } NEWLOGFONT;
Поле lfFullName представляет собой массив, содержащий полное имя шрифта, которое состоит из названия шрифта и названия стиля.
В поле lfStyle находится название стиля шрифта.
Параметр lpntm функции EnumFontFamProc содержит адрес структуры NEWTEXTMETRIC , описывающей метрику шрифта:
typedef struct tagNEWTEXTMETRIC { int tmHeight; int tmAscent; int tmDescent; int tmInternalLeading; int tmExternalLeading; int tmAveCharWidth; int tmMaxCharWidth; int tmWeight; BYTE tmItalic; BYTE tmUnderlined; BYTE tmStruckOut; BYTE tmFirstChar; BYTE tmLastChar; BYTE tmDefaultChar; BYTE tmBreakChar; BYTE tmPitchAndFamily; BYTE tmCharSet; int tmOverhang; int tmDigitizedAspectX; int tmDigitizedAspectY;
// Дополнительные поля DWORD ntmFlags; UINT ntmSizeEM; UINT ntmCellHeight; UINT ntmAvgWidth; } NEWTEXTMETRIC; typedef NEWTEXTMETRIC* PNEWTEXTMETRIC; typedef NEWTEXTMETRIC NEAR* NPNEWTEXTMETRIC; typedef NEWTEXTMETRIC FAR* LPNEWTEXTMETRIC;
Структура NEWTEXTMETRIC аналогична структуре TEXTMETRIC, за исключением четырех дополнительных полей, добавленных в конце. Эти поля описывают физические атрибуты шрифта True Type.
Поле ntmFlags может содержать значения NTM_REGULAR, NTM_BOLD или NTM_ITALIC, соответственно, для нормального, жирного или наклонного шрифта True Type.
Поле ntmSizeEM содержит ширину буквы "М", в единицах, использованных при разработке шрифта.
В поле ntmCellHeight находится высота шрифта в единицах, использованных при разработке шрифта.
Поле ntmAvgWidth содержит ширину шрифта в единицах, использованных при разработке шрифта.
Параметр FontType функции EnumFontFamProc определяет тип шрифта и может содержать одно из следующих значений:
| Значение | Описание |
| DEVICE_FONTTYPE | Шрифт устройства вывода |
| RASTER_FONTTYPE | Растровый шрифт |
| TRUETYPE_FONTTYPE | Шрифт True Type |
Если приложение собирается продолжить просмотр доступных шрифтов, функция EnumFontFamProc должна возвратить ненулевое значение. Если просмотр должен быть завершен, следует возвратить нуль.
Функция GetDCEx
В программном интерфейсе операционной системы Windows версии 3.1 появилась функция GetDCEx , предоставляющая расширенные возможности для получения контекста отображения:HDC WINAPI GetDCEx(register HWND hwnd, HRGN hrgnClip, DWORD flags);
Функция возвращает идентификатор полученного контекста отображения или NULL при ошибке.
Параметр hwnd задает идентификатор окна, для которого необходимо получить контекст отображения.
С помощью параметра hrgnClip можно определить область ограничения вывода. Эта область может иметь произвольную форму и являться комбинацией нескольких областей ограничения. Использование областей ограничения будет подробно описано дальше в этой главе.
Параметр flags определяет способ, которым будет образован контекст отображения. Этот параметр можно указывать как логическую комбинацию следующих значений:
| Константа | Описание |
| DCX_WINDOW | Функция возвращает контекст отображения, позволяющий рисовать во всем окне, а не только в его внутренней области |
| DCX_CACHE | Функция получает общий контекст отображения из кеша Windows, даже если окно создано на базе класса стиля CS_OWNDC или CS_CLASSDC |
| DCX_CLIPCHILDREN | Видимые области всех дочерних окон, расположенных ниже окна hwnd, исключаются из области отображения |
| DCX_CLIPSIBLINGS | Видимые области всех окон-братьев (окон, имеющих общих родителей), расположенных выше окна hwnd, исключаются из области отображения |
| DCX_PARENTCLIP | Для отображения используется вся видимая область родительского окна, даже если родительское окно создано с использованием стилей WS_CLIPCHILDREN и WS_PARENTDC. Начало координат устанавливается в левый верхний угол окна hwnd |
| DCX_EXCLUDERGN | Если указан этот флаг, при выводе будет использована область ограничения, заданная параметром hrgnClip |
| DCX_INTERSECTRGN | Используется пересечение области ограничения, заданной параметром hrgnClip, и видимой области полученного контекста отображения |
| DCX_LOCKWINDOWUPDATE | Этот флаг разрешает рисование в окне, заблокированном для рисования функцией LockWindowUpdate . Флаг можно использовать при необходимости рисовать, например, рамку, выделяющую произвольную область экрана |
Контекст отображения, полученный функцией GetDCEx, следует освободить после использования при помощи функции ReleaseDC.
Функция PrintDlg
С помощью функции PrintDlg приложение может вывести на экран одну из двух диалоговых панелей, представленных на рис. 6.1 и 6.2, с помощью которых пользователь может напечатать документ, выбрать нужный принтер или изменить его параметры.Рис. 6.1. Диалоговая панель "Print"
В верхней части диалоговой панели "Print" в поле "Printer" указано название принтера, который будет использован для печати. Вы можете выбрать другой принтер, если нажмете кнопку "Setup...".
С помощью группы органов управления "Print Range" вы можете выбрать диапазон страниц, которые должны быть распечатаны. Можно распечатать все или отдельные страницы, выделенный фрагмент текста или страницы в указанном диапазоне номеров страниц (поля "From" и "To").
Можно задать качество печати (поле "Print Quality"), указать количество копий (поле "Copies"), выполнить печать в файл (поле "Print to File").
С помощью переключателя "Collate Copies" можно выбрать порядок печати отдельных листов многостраничного документа, который должен быть напечатан в нескольких копиях. Если этот переключатель находится во включенном состоянии, вначале следует напечатать все страницы первой копии, затем - второй, и так далее. Если же этот переключатель выключен, вначале печатается несколько копий первой страницы, затем несколько копий второй страницы и так до конца документа. Последний режим печати удобен для лазерных принтеров, где время подготовки одной страницы для печати больше времени печати готовой страницы.
Если нажать на кнопку "Setup...", на экране появится диалоговая панель "Print Setup" (рис. 6.2).
Рис. 6.2. Диалоговая панель "Print Setup"
В группе органов управления "Printer" вы можете выбрать для печати либо принтер, принятый по умолчанию ("Default Printer"), либо выбрать другой принтер из списка "Specific Printer".
С помощью переключателей группы "Orientation" вы можете выбрать вертикальное ("Portrait") либо горизонтальное ("Landscape") расположение текста на листе бумаги.
Группа "Paper" содержит два списка, с помощью которых вы можете выбрать размер бумаги (список "Size") или устройство подачи бумаги ("Source").
Нажав в этой диалоговой панели кнопку "Options...", вы сможете выполнить настройку параметров принтера при помощи диалоговой панели "Options" (рис.6.3).
Рис. 6.3. Диалоговая панель "Options" для лазерного принтера HP LaserJet III
Внешний вид диалоговой панели "Options" зависит от драйвера принтера, так как эта панель формируется функцией DeviceMode или ExtDeviceMode, расположенными в соответствующем драйвере принтера. Для сравнения на рис. 6.4 представлен внешний вид диалоговой панели "Options" для матричного принтера Epson FX-850.
Рис. 6.4. Диалоговая панель "Options" для матричного принтера Epson FX-850
Если вас не устраивает внешний вид диалоговых панелей "Print" и "Print Options", вы можете использовать вместе с функцией PrintDlg свои собственные шаблоны диалоговых панелей. Можно также подключить функцию фильтра для обеспечения дополнительной обработки сообщений.
Приведем прототип функции PrintDlg, описанный в файле commdlg.h:
BOOL PrintDlg(PRINTDLG FAR* lppd);
При успешном завершении функция возвращает значение TRUE. В случае ошибки, отмены печати или отмены выбора принтера (если функция PrintDlg используется только для выбора принтера) функция возвращает значение FALSE.
HDC
Контекст устройства или информационный контекст.Это поле заполняется после возвращения из функции PrintDlg, если в поле Flags указано одно из значений: PD_RETURNDC или PD_RETURNIC. В первом случае возвращается контекст принтера, который можно использовать для печати, во втором - информационный контекст, который можно использовать для получения разнообразной информации о принтере.
HDevMode
Идентификатор глобального блока памяти, содержащего структуру типа DEVMODE, которая используется для инициализации параметров принтера.Если содержимое этого поля указать как NULL, после возвращения из функции PrintDlg поле будет содержать идентификатор глобального блока памяти, заказанного функцией. В этом блоке памяти будет расположена структура DEVMODE, заполненная выбранными параметрами принтера. Структура DEVMODE будет описана позже.
HDevNames
Идентификатор глобального блока памяти, содержащего структуру типа DEVNAMES, содержащей три текстовые строки. Первая строка определяет имя драйвера принтера, вторая - имя принтера, и третья - имя порта вывода, к которому подключен принтер.Если содержимое этого поля указать как NULL, после возвращения из функции PrintDlg поле будет содержать идентификатор глобального блока памяти, заказанного функцией для структуры DEVNAMES. В структуре будут находиться строки, соответствующие выбранному принтеру.
HInstance
Идентификатор модуля, который содержит шаблоны диалоговых панелей. Используется только в том случае, если указаны флаги PD_ENABLEPRINTTEMPLATE или PD_ENABLESETUPTEMPLATE.HPrintTemplate
Идентификатор блока памяти, содержащего предварительно загруженный шаблон диалоговой панели "Print". Для использования этого поля необходимо указать флаг PD_ENABLEPRINTTEMPLATEHANDLE.HSetupTemplate
Идентификатор блока памяти, содержащего предварительно загруженный шаблон диалоговой панели "Print Setup". Для использования этого поля необходимо указать флаг PD_ENABLESETUPTEMPLATEHANDLE.HwndOwner
Идентификатор окна, создавшего диалоговую панель. Если в поле Flags не указано значение PD_SHOWHELP, в поле hwndOwner можно указать NULL.Информационный контекст
Если приложению необходимо получить информацию об устройстве вывода (например, с помощью функции GetDeviceCaps, рассмотренной нами в 11 томе "Библиотеки системного программиста"), оно может создать вместо обычного информационный контекст . Информационный контекст нельзя использовать для рисования, однако он занимает меньше места в памяти.Информационный контекст создается при помощи функции CreateIC , аналогичной по своим параметрам функции CreateDC:
HDC WINAPI CreateIC( LPCSTR lpszDriver, // имя драйвера LPCSTR lpszDevice, // имя устройства LPCSTR lpszOutput, // имя файла или порта вывода const void FAR* lpvInitData); // данные для инициализации
После использования информационный контекст следует удалить, вызвав функцию DeleteDC.
Инструменты для рисования
Итак, теперь у вас есть представление о том, какая "бумага" используется для рисования. Вы смогли убедиться, что ее свойства намного богаче свойств обычной писчей бумаги или даже дорогой бумаги, предназначенной для печати документов или крупных денежных купюр. Теперь посмотрим, какие "карандаши" может использовать приложение Windows для рисования. Можно предположить, что при такой бумаге и карандаши тоже будут не простые...Для рисования в распоряжении приложений Windows есть перья, кисти, текстовые шрифты и битовые изображения. Разумеется, все эти инструменты могут быть черно-белые или цветные, причем цветовое разрешение определяется в основном только возможностями видеоконтроллера.
Расскажем кратко об основных особенностях и областях применения перечисленных выше инструментов.
Использование цветовых палитр
В большинстве случаев вам не потребуется использовать палитры Windows, так как далеко не все приложения работают с большим количеством цветов. Приложения для обычной презентационной графики (схемы, диаграммы и т. п.), обработки текстовых документов и другие аналогичные приложения выглядят вполне удовлетворительно при использовании 16 цветов, обеспечиваемых стандартным драйвером VGA. Более того, увеличение цветового разрешения, как правило, сказывается отрицательно на производительности Windows. И это понятно - чем больше используется цветов, тем больше объем данных, передаваемых из процессора в видеоконтроллер. Несмотря на то что акселераторы Windows значительно ускоряют работу, очень много пользователей имеют дешевые видеоконтроллеры, работающие в режиме низкого или, в крайнем случае, среднего цветового разрешения.Если же ваше приложение должно работать с многокрасочными реалистическими изображениями или выводить на экран битовые изображения, полученные с помощью цветного сканера, задача использования цветовых палитр выдвигается на первый план.
Оценивая отношение цветового разрешения к цене для разрешения 256 цветов, многие пользователи склоняются не к акселераторам с объемом видеопамяти 1-2 Мбайт (цена которых находится в диапазоне 100-400 долларов), а к обычным видеоадаптерам SVGA с объемом видеопамяти 512 Кбайт (цена порядка 40 долларов). Возможно, это неправильно, так как акселераторы и в самом деле заметно ускоряют работу Windows, однако едва ли вам понравится идея убеждать потенциальных покупателей вашего приложения обновить свой компьютер.
Поэтому приложение должно использовать все цветовые возможности обычных видеоадаптеров SVGA, которые реализуются с помощью механизма цветовых палитр.
Вы можете спросить: будет ли ваше приложение, рассчитанное на использование цветовых палитр, работать с современными видеоадаптерами в режиме True Color, для которых механизм палитр не предусмотрен (так как в этом случае он не нужен)? Будет, так как GDI все равно сможет (и даже с большим успехом) удовлетворить цветовые запросы приложения.
Использование встроенной кисти
Для выбора одной из встроенной кисти вы можете воспользоваться макрокомандой GetStockBrush , определенной в файле windowsx.h:#define GetStockBrush(i) ((HBRUSH)GetStockObject(i))
В качестве параметра для этой макрокоманды можно использовать следующие значения:
| Значение | Описание |
| BLACK_BRUSH | Кисть черного цвета |
| WHITE_BRUSH | Кисть белого цвета |
| GRAY_BRUSH | Серая кисть |
| LTGRAY_BRUSH | Светло-серая кисть |
| DKGRAY_BRUSH | Темно-серая кисть |
| NULL_BRUSH | Бесцветная кисть, которая ничего не закрашивает |
| HOLLOW_BRUSH | Синоним для NULL_BRUSH |
Как видно из только что приведенной таблицы, в Windows есть только монохромные встроенные кисти.
Макрокоманда GetStockBrush возвращает идентификатор встроенной кисти.
Прежде чем использовать полученную таким образом кисть, ее надо выбрать в контекст отображения (так же, как и перо). Для этого проще всего воспользоваться макрокомандой SelectBrush :
#define SelectBrush(hdc, hbr) \ ((HBRUSH)SelectObject((hdc), (HGDIOBJ)(HBRUSH)(hbr)))
Макрокоманда SelectBrush возвращает идентификатор старой кисти, выбранной в контекст отображения раньше.
Изображение bitmap
В контекст отображения можно выбрать изображение bitmap и затем отобразить его в окне или использовать в меню. Мы будем обсуждать изображения bitmap в отдельной главе этого тома. Сейчас отметим, что по умолчанию в контекст отображения не выбирается никакое изображение bitmap. Приложение должно выбрать его самостоятельно, используя функцию SelectObject . В программном интерфейсе GDI имеются и другие функции для работы с изображениями bitmap. Это функции CreateBitmap , CreateBitmapIndirect , CreateCompatibleBitmap .Приложения Windows широко пользуются изображениями bitmap, так как они очень удобны для отображения небольших пиктограмм или больших и сложных многоцветных рисунков, полученных при помощи специальных графических редакторов или сканеров. Заметим, что в предыдущих томах "Библиотеки системного программиста" мы использовали изображения bitmap для кнопок и органа управления Toolbar, отложив подробное их изучение на потом.
Как указать цвет
Многие функции программного интерфейса GDI (например, функции, создающие перья и кисти) требуют в качестве одного из своих параметров ссылку на используемый цвет. Цвет указывается при помощи переменной, имеющей тип COLORREF :HBRUSH WINAPI CreateSolidBrush(COLORREF clrref);
Тип COLORREF определен в файле windows.h следующим образом:
typedef DWORD COLORREF;
В простейшем случае цвет можно определить с помощью макрокоманды RGB , комбинирующей цвет из отдельных компонент:
#define RGB(r,g,b) \ ((COLORREF)(((BYTE)(r) | \ ((WORD)(g)<<8)) | \ (((DWORD)(BYTE)(b))<<16)))
Эта макрокоманда упаковывает отдельные цветовые компоненты в двойное слово, причем (что важно) старший байт этого слова должен быть равен нулю (рис. 3.2).
Рис. 3.2. Представление цвета, полученное с помощью макрокоманды RGB
В файле windows.h определены также макрокоманды, извлекающие из переменной типа COLORREF, упакованной с помощью макрокоманды RGB, отдельные цветовые компоненты:
#define GetRValue (rgb) ((BYTE)(rgb)) #define GetGValue (rgb) ((BYTE)(((WORD)(rgb)) >> 8)) #define GetBValue (rgb) ((BYTE)((rgb)>>16))
Как мы уже говорили, в зависимости от текущего цветового разрешения Windows может предоставить приложению приближенный цвет, который максимально соответствует запрошенному логическому цвету. Функция GetNearestColor возвращает для запрошенного логического цвета clrref физический цвет, составленный только из компонент чистого цвета:
COLORREF WINAPI GetNearestColor(HDC hdc, COLORREF clrref);
Через параметр hdc необходимо передать идентификатор контекста отображения.
Кисть
Для закрашивания внутренней области окна приложения или замкнутой геометрической фигуры можно использовать не только различные цвета, но и графические изображения небольшого (8х8 пикселов) размера - кисти (brush ).В предыдущих томах "Библиотеки системного программиста" мы использовали кисть для закрашивания внутренней области окна, указав ее при регистрации класса окна. Вы можете выбрать в контекст отображения предопределенную в Windows или свою кисть, использовав ее для закраски внутренних областей фигур или окна приложения. По умолчанию в контекст отображения выбрана кисть белого цвета.
Для выбора кисти предназначены функции CreateSolidBrush , CreateHatchBrush , CreatePatternBrush , CreateDIBPatternBrush , CreateBrushIndirect , SelectObject .
Кисти
Внутренняя область окна и замкнутых геометрических фигур может быть закрашена при помощи кисти. С помощью функции GetStockObject можно выбрать черную, белую, темно- или светло-серую кисть или нулевую кисть (null brush). Можно выбрать одну из кистей, предназначенных для штриховки (рис. 1.4), для чего следует использовать функцию CreateHatchBrush. Все эти кисти встроены в операционную систему.Рис. 1.4. Геометрические фигуры, закрашенные с использованием встроенных кистей
Если вас не устраивает ни одна из встроенных кистей, вы можете создать собственную, определив ее как изображение bitmap размером 8х8 пикселов. Такая кисть может иметь любой внешний вид и любой цвет.
Классификация шрифтов
Перед тем, как двигаться дальше, нам необходимо уточнить некоторые понятия и термины, связанные со шрифтами.Можно сказать, что шрифт состоит из изображений (рисунков) отдельных символов - глифов (glyph).
Для внутреннего представления глифа в файле шрифта True Type используются описания контуров, причем один глиф может содержать несколько контуров (рис. 5.1).
Рис. 5.1. Рисунки символов
Глифы могут иметь различный внешний вид (typeface). Операционная система Windows классифицирует шрифты на несколько типов, или семейств (font family). Эти типы называются Modern, Roman, Swiss, Script, Decorative.
Шрифты семейства Modern имеют одинаковую ширину букв. Таким шрифтом оформлены все листинги программ в нашей книге. Шрифты семейства Roman содержат буквы различной ширины, имеющие засечки. Семейство Swiss отличается тем, что при переменной ширине букв они не имеют засечек. Буквы в шрифтах семейства Script как бы написаны от руки. Семейство Decorative содержит глифы в виде небольших картинок (пиктограмм).
В следующей таблице мы привели примеры шрифтов различных семейств.
| Семейство | Название шрифта | Пример текста |
| Modern | Courier | Шрифт в стиле Modern |
| Roman | Times | Шрифт в стиле Roman |
| Swiss | Helvetica | Шрифт в стиле Swiss |
| Script | Script Cyrillic | Шрифт в стиле Script |
| Decorative | Wingdings | Dm13m,0=;rative |
Приложения Windows могут заказывать шрифт, ссылаясь на название соответствующего семейства, однако в зависимости от состава имеющихся шрифтов Windows может предоставить в ваше распоряжение не тот шрифт, какой бы вам хотелось.
Другая важная характеристика шрифта - это размер букв. Из 11 тома "Библиотеки системного программиста" вы знаете, что для описания вертикального размера букв шрифта используются несколько параметров. Не останавливаясь на тонкостях, отметим, что шрифты, содержащие буквы разного размера, являются разными шрифтами.
Растровые шрифты, которые относятся к одному семейству, но имеют разные размеры букв, хранятся в отдельных файлах.
В то же время благодаря возможности масштабирования шрифтов True Type для них нет необходимости в отдельном хранении глифов различных размеров.
GDI может выполнять масштабирование растровых шрифтов, увеличивая (но не уменьшая) размер букв. Результат такого масштабирования при большом размере букв обычно неудовлетворительный, так как на наклонных линиях контура букв образуются зазубрины (рис. 1.5 в первой главе).
Векторные шрифты легко поддаются масштабированию, поэтому для хранения шрифта одного семейства, но разного размера, можно использовать один файл.
Вы знаете, что шрифты могут иметь нормальное (normal), жирное (bold) или наклонное (italic) начертание:
| Начертание | Образец шрифта |
| Normal | AaBbCcDdEeFfGgHhIiJjKkLl АаБбВвГгДдЕеЖжЗзИиКкЛлМмНн |
| Bold | AaBbCcDdEeFfGgHhIiJjKkLl АаБбВвГгДдЕеЖжЗзИиКкЛлМмНн |
| Italic | AaBbCcDdEeFfGgHhIiJjKkLl АаБбВвГгДдЕеЖжЗзИиКкЛлМмНнОоПпРр |
Еще один часто используемый атрибут оформления строк текста - подчеркивание:
Текст с подчеркиванием
Иногда используется шрифт с перечеркнутыми буквами.
GDI выполняет подчеркивание самостоятельно, файлы шрифтов не содержат глифы с подчеркиванием.
Растровые и векторные шрифты хранятся в системном каталоге Windows в файлах с расширением имени fon.
Глифы масштабируемых шрифтов True Type находятся в файлах с расширением имени ttf, причем сами эти файлы могут располагаться в любом каталоге. В процессе регистрации масштабируемого шрифта Windows создает в своем системном каталоге файлы с расширением имени fot, которые содержат ссылки на соответствующие ttf-файлы.
С помощью приложения Control Panel вы можете добавлять или удалять любые шрифты.Следует, однако, учитывать ограничение: в системе можно одновременно использовать не более 253, к тому же для представления жирного и наклонного начертания используются отдельные масштабируемые шрифты. Чрезмерное количество установленных шрифтов может привести к снижению производительности системы.
Комбинирование областей
Функция CombineRegion позволяет вам изменить существующую область, скомбинировав ее из двух других:int WINAPI CombineRgn( HRGN hrgnDest, // новая область HRGN hrgnSrc1, // первая исходная область HRGN hrgnSrc2, // вторая исходная область int fnCombineMode); // режим комбинирования
Перед вызовом функции вам надо определить все три области. Функция объединит области hrgnSrc1 и hrgnSrc2, изменив соответствующим образом область hrgnDest.
Способ комбинирования областей зависит от значения параметра fnCombineMode:
| Значение параметра fnCombineMode | Способ образования области hrgnDest |
| RGN_AND | Пересечение областей hrgnSrc1 и hrgnSrc2 |
| RGN_OR | Объединение областей hrgnSrc1 и hrgnSrc2 |
| RGN_XOR | Объединение областей hrgnSrc1 и hrgnSrc2 с исключением перекрывающихся областей |
| RGN_DIFF | Область hrgnSrc1, которая не входит в область hrgnSrc2 |
| RGN_COPY | Область hrgnSrc1 |
В зависимости от результата выполнения операции функция CombineRegion может вернуть одно из следующих значений:
| Значение | Описание |
| ERROR | Ошибка |
| NULLREGION | Новая область пустая |
| SIMPLEREGION | Новая область не является самопересекающейся (т. е. граница созданной области не пересекает саму себя) |
| COMPLEXREGION | Создана самопересекающаяся область |
Для облегчения комбинирования областей в файле windowsx.h определены макрокоманды, предназначенные для копирования, пересечения, объединения и вычитания областей. Все они созданы на базе только что описанной функции CombineRegion :
#define CopyRgn (hrgnDst, hrgnSrc) \ CombineRgn(hrgnDst, hrgnSrc, 0, RGN_COPY)
#define IntersectRgn (hrgnResult, hrgnA, hrgnB) \ CombineRgn(hrgnResult, hrgnA, hrgnB, RGN_AND)
#define SubtractRgn (hrgnResult, hrgnA, hrgnB) \ CombineRgn(hrgnResult, hrgnA, hrgnB, RGN_DIFF)
#define UnionRgn (hrgnResult, hrgnA, hrgnB) \ CombineRgn(hrgnResult, hrgnA, hrgnB, RGN_OR)
#define XorRgn (hrgnResult, hrgnA, hrgnB) \ CombineRgn(hrgnResult, hrgnA, hrgnB, RGN_XOR)
Контекст для метафайла
Контекст для метафайла позволяет записывать команды GDI в файл и затем проигрывать такой файл на физическом устройстве вывода. Файл может находиться в памяти или на диске, в последнем случае его можно использовать для переноса графического изображения в другое приложение.Для создания контекста метефайла используется функция CreateMetaFile :
HDC WINAPI CreateMetaFile(LPCSTR lpszFileName);
Параметр lpszFileName должен указывать на строку, содержащую путь к имени файла, в который будут записаны команды GDI, или NULL. В последнем случае создается метафайл в оперативной памяти.
После выполнения рисования в контексте метафайла следует закрыть метафайл, вызвав функцию CloseMetaFile :
HMETAFILE WINAPI CloseMetaFile(HDC hdc);
Эта функция закрывает метафайл для контекста hdc и возвращает идентификатор метафайла. Идентификатор закрытого метафайла использовать нельзя, так как он не содержит никакой полезной информации.
Что можно сделать с полученным идентификатором метафайла?
Можно скопировать метафайл в обычный дисковый файл, вызвав функцию CopyMetaFile :
HMETAFILE WINAPI CopyMetaFile(HMETAFILE hmf, LPCSTR lpszFileName);
Параметр hmf определяет метафайл, параметр lpszFileName содержит путь к имени файла, в который будет записан метафайл .
Можно проиграть метафайл в контексте отображения или контексте устройства, вызвав функцию PlayMetaFile :
BOOL WINAPI PlayMetaFile(HDC hdc, HMETAFILE hmf);
Наконец, при помощи функции DeleteMetaFile можно удалить метафайл:
BOOL WINAPI DeleteMetaFile(HMETAFILE hmf);
Удаление метафайла с помощью функции DeleteMetaFile делает недействительным идентификатор метафайла hmf и освобождает оперативную память, занятую метафайлом. Если метафайл был создан как обычный дисковый файл, функция DeleteMetaFile не удаляет его с диска.
Для того чтобы воспользоваться метафайлом, хранящимся в виде дискового файла, его следует загрузить при помощи функции GetMetaFile , указав ей в качестве единственного параметра путь к соответствующему файлу:
HMETAFILE WINAPI GetMetaFile(LPCSTR lpszFileName);
Контекст для памяти
В работе с битовыми изображениями bitmap часто используется такое "устройство вывода", как оперативная память. Приложение может полностью подготовить изображение в оперативной памяти, получив контекст для памяти , и затем быстро вывести готовое изображение на экран. Этот способ во многих случаях работает намного быстрее и приятнее для пользователя, чем формирование изображения непосредственно на экране.Контекст для памяти создается совместимым с тем контекстом отображения, в котором будет выполняться вывод на физическое устройство. Для создания совместимого контекста используется функция CreateCompatibleDC :
HDC WINAPI CreateCompatibleDC(HDC hdc);
Созданный таким образом контекст памяти удаляется при помощи функции DeleteDC.
Использование контекста памяти будет подробно описано в главе, посвященной битовым изображениям bitmap.
Контекст для устройства DISPLAY
В некоторых случаях требуется получить контекст отображения, позволяющий приложению рисовать в любом месте экрана дисплея. Такой контекст можно создать при помощи функции CreateDC, указав в качестве имени драйвера строку "DISPLAY ", а в качестве остальных параметров - значение NULL:hdc = CreateDC("DISPLAY", NULL, NULL, NULL);
В данном случае будет создан контекст для видеомонитора, с помощью которого приложение может рисовать в любом месте экрана. Начало системы координат, выбранной в данный контекст, располагается в верхнем левом углу экрана видеомонитора.
После использования созданный таким образом контекст отображения следует удалить, вызвав функцию DeleteDC.
Есть еще два способа получения контекста для экрана.
Приложение может получить контекст отображения для всего экрана при помощи функции GetDC, указав в качестве параметра значение NULL:
hdc = GetDC(NULL);
Полученный таким образом контекст следует освободить после использования при помощи функции ReleaseDC, передав ей вместо идентификатора окна значение NULL:
ReleaseDC(NULL, hdc);
Еще один способ связан с использованием функции GetDCEx, описание которой будет приведено ниже.
Контекст физического устройства
Вывод изображений на такое устройство, как принтер, может выполняться с использованием тех же приемов, что и вывод в окно приложения. Прежде всего необходимо получить контекст устройства . Затем можно вызывать функции GDI, выполняющие рисование, передавая им идентификатор полученного контекста в качестве параметра.В отличие от контекста отображения, контекст физического устройства не получается, а создается, для чего используется функция CreateDC :
HDC WINAPI CreateDC( LPCSTR lpszDriver, // имя драйвера LPCSTR lpszDevice, // имя устройства LPCSTR lpszOutput, // имя файла или порта вывода const void FAR* lpvInitData); // данные для инициализации
Параметр lpszDriver является указателем на строку символов, содержащую имя драйвера, обслуживающего физическое устройство. Имя драйвера совпадает с именем файла *.drv, содержащего сам драйвер и расположенного в системном каталоге Windows.
Имя устройства lpszDevice - это название самого устройства, описанное, например, в файле win.ini в разделе [devices].
Параметр lpszOutput указывает на структуру данных типа DEVMODE, используемую при инициализации устройства вывода. Если при работе с устройством нужно использовать параметры, установленные при помощи приложения Control Panel, параметр lpszOutput следует указать как NULL.
Более подробно вопросы работы с принтером будут рассмотрены в отдельной главе этого тома.
В приведенном ниже примере создается контекст устройства для лазерного принтера HP Laserjet III, подключенного к порту LPT1:, причем в системном каталоге Windows для этого принтера установлен драйвер hppcl5a.drv:
hdc = CreateDC("hppcl5a", "HP LaserJet III", "LPT1:", NULL);
Аналогично, для принтера Epson FX-850, подключенного к порту LPT2:, и работающему через драйвер epson9.drv:
hdc = CreateDC("epson9", "Epson FX-850", "LPT2:", NULL);
Созданный при помощи функции CreateDC контекст устройства следует удалить (но не освободить), вызвав функцию DeleteDC:
BOOL WINAPI DeleteDC(HDC hdc);
Эта функция возвращает TRUE при нормальном завершении и FALSE при возникновении ошибки.
Контекст отображения для класса окна
Общий контекст отображения , описанный нами в предыдущем разделе, кешируется операционной системой Windows для ускорения доступа к нему. Однако вы можете создать такой контекст отображения, который хранится отдельно в единственном экземпляре и используется всеми окнами, созданными на базе класса окна. При регистрации такого класса окна вы должны указать стиль CS_CLASSDC :wc.style = CS_CLASSDC;
Все окна, созданные на базе класса, имеющего стиль CS_CLASSDC, будут использовать один общий контекст отображения.
В отличие от общего контекста отображения, приложения, однажды получив контекст отображения для класса окна, могут не освобождать его. То есть для контекста этого типа после функций BeginPaint и GetDC можно не вызывать функции EndPaint и ReleaseDC. Если же приложение вызовет функцию EndPaint или ReleaseDC, они не будут ничего делать и сразу вернут управление. Для уменьшения вероятности ошибки мы рекомендуем вам всегда освобождать контекст отображения, даже если это и не требуется для данного типа контекста.
Несмотря на то, что для всех окон, созданных на базе класса стиля CS_CLASSDC, создается один контекст отображения, не следует думать, что приложение, однажды получив этот контекст, не должно получать его вновь при очередном вызове обработчика сообщения, выполняющего рисование.
Дело в том, что при получении контекста отображения для окна в нем устанавливаются атрибуты области ограничения и начало системы физических координат устройства отображения. Если на базе нашего класса создано несколько окон, при получении ими контекста отображения эти атрибуты "настраиваются" на окно, получившее контекст отображения. Но если на базе класса стиля CS_CLASSDC приложение создало только одно окно, оно может получить контекст отображения для класса окна один раз и не освобождать его.
Зачем используется контекст отображения класса окна?
Вы можете использовать его в тех случаях, когда по соображениям повышения производительности нежелательно выполнять настройку многочисленных атрибутов контекста отображения после каждого вызова функции BeginPaint или EndPaint. Эту настройку можно выполнить только один раз. Каждый раз, когда функция окна получает контекст отображения класса окна, в нем выполняется настройка только двух атрибутов - области ограничения и начала системы физических координат устройства вывода. Остальные атрибуты остаются без изменений и, следовательно, не требуют повторной настройки.
Контекст отображения для окна
Все описанные выше контексты отображения позволяют рисовать только во внутренней области окна (client region). С помощью функции GetWindowDC приложение может получить контекст отображения для окна , позволяющий рисовать в любом месте окна (в области заголовка окна, в области системного меню, рамки окна, кнопок изменения размера и т. п.).Контекст отображения, полученный для окна, используется аналогично общему контексту отображения. После получения контекста его атрибуты принимают значения по умолчанию. Приложение обязано освобождать этот контекст сразу после использования, вызывая функцию ReleaseDC.
Особенностью данного контекста является то, что в нем выбрана система координат, начало которой находится в левом верхнем углу окна (всего окна, а не его внутренней области).
Такой контекст может использоваться при необходимости заменить стандартные элементы окна (заголовок, полосы просмотра и т. п.) на собственные, или если приложение желает нарисовать что-нибудь в области заголовка или рамки.
Контекст отображения для принтера
На первый взгляд, контекст отображения для принтера получить нетрудно - достаточно вызвать функцию CreateDC , указав имя драйвера, имя устройства и имя порта вывода, к которому подключен принтер:HDC WINAPI CreateDC( LPCSTR lpszDriver, // имя драйвера LPCSTR lpszDevice, // имя устройства LPCSTR lpszOutput, // имя файла или порта вывода const void FAR* lpvInitData); // данные для инициализации
Созданный при помощи функции CreateDC контекст устройства следует удалить после использования, вызвав функцию DeleteDC :
BOOL WINAPI DeleteDC(HDC hdc);
Как мы уже говорили, параметр lpszDriver является указателем на строку символов, содержащую имя драйвера, обслуживающего физическое устройство. Имя драйвера совпадает с именем файла *.drv, содержащего драйвер. Этот драйвер находится в системном каталоге Windows.
Имя устройства lpszDevice - это название устройства.
Параметр lpszOutput указывает на структуру данных типа DEVMODE, используемую при инициализации устройства вывода. Если при работе с устройством нужно использовать параметры, установленные при помощи приложения Control Panel, параметр lpszOutput следует указать как NULL.
Однако как определить эти параметры?
Текущий принтер описан в файле win.ini в разделе [windows]:
[windows] ... ... device=HP LaserJet III,hppcl5a,LPT1: ...
Вы можете получить контекст отображения для текущего принтера, указав эти параметры функции CreateDC:
hdc = CreateDC("hppcl5a", "HP LaserJet III", "LPT1:", NULL);
Однако к компьютеру может быть подключено несколько принтеров. Например, к порту LPT1: может быть подключен лазерный принтер, а к порту LPT2: - матричный или струйный принтер.
Список подключенных принтеров, имена драйверов и портов ввода/вывода можно найти в разделе [devices] файла win.ini:
[devices] Epson FX-850=EPSON9,LPT1: HP LaserJet III=hppcl5a,LPT1:
Ваше приложение может получить параметры принтера, используемого по умолчанию, а также параметры всех установленных принтеров непосредственно из файла win.ini.
Это можно легко сделать при помощи функции GetProfileString:
int GetProfileString( LPCSTR lpszSection; // адрес раздела LPCSTR lpszEntry; // адрес элемента раздела LPCSTR lpszDefault; // адрес строки по умолчанию LPSTR lpszReturnBuffer; // адрес буфера для записи int cbReturnBuffer; // размер буфера
Параметр lpszSection должен указывать на имя раздела, в нашем случае на строку "windows". Через параметр lpszEntry передается адрес текстовой строки, содержащий имя элемента раздела, в нашем случае это адрес строки "device".
Если указанного элемента или раздела нет в файле win.ini, используется строка по умолчанию, адрес которой передается через параметр lpszDefault.
Найденная строка (или строка по умолчанию) будет записана в буфер, адрес которого передается через параметр lpszReturnBuffer. Размер буфера должен быть указан в параметре cbReturnBuffer.
Для получения контекста отображения текущего принтера обычно используется такая функция:
HDC PASCAL GetPrinterDC() { char msgbuf[128]; LPSTR pch; LPSTR pchFile; LPSTR pchPort;
// Определяем текущий принтер из файла win.ini if(!GetProfileString("windows", "device", "", msgbuf, sizeof(msgbuf))) return NULL;
// Выполняем разбор строки для выделения имени драйвера, // имени устройства и имени порта ввода/вывода for(pch=msgbuf; *pch && *pch != ','; pch=AnsiNext(pch));
if(*pch) *pch++ = 0;
// Пропускаем управляющие символы и символ табуляции while(*pch && *pch <= ' ') pch=AnsiNext(pch); pchFile = pch;
while(*pch && *pch != ',' && *pch > ' ') pch = AnsiNext(pch);
if(*pch) *pch++ = 0;
while(*pch && (*pch <= ' ' *pch == ',')) pch = AnsiNext(pch);
pchPort = pch;
while(*pch && *pch > ' ') pch = AnsiNext(pch);
*pch = 0;
// Возвращаем контекст отображения для принтера return CreateDC(pchFile, msgbuf, pchPort, NULL); }
Приведенная выше функция способна работать с двухбайтовыми кодами символов, так как для сканирования строки используется функция AnsiNext.
Ситуация, однако, усложняется при необходимости сделать выбор между одним из установленных принтеров. Хорошо спроектированное приложение должно позволять пользователю выбирать любой из установленных принтеров, а не только тот, который используется по умолчанию.
В этом случае вам надо создать диалоговую панель (или меню), с помощью которой пользователь мог бы выбрать нужный принтер.
Для получения списка установленных принтеров вы можете воспользоваться все той же функцией GetProfileString , указав в качестве первого параметра адрес строки "devices", а в качестве второго - значение NULL:
GetProfileString("devices", NULL, "", msgbuf, sizeof(msgbuf));
В этом случае в буфер будут переписаны все строки из раздела [devices], каждая строка будет закрыта двоичным нулем, последняя строка будет закрыта двумя двоичными нулями.
Однако есть еще одна задача, которую должно уметь решать ваше приложение.
Как правило, для каждого принтера возможна настройка таких параметров, как размер и расположение бумаги, выбор устройства подачи бумаги, интенсивность печати и разрешающая способность и так далее. Для выполнения такой настройки ваше приложение должно вызвать функцию DeviceMode или более новую ExtDeviceMode , расположенную в драйвере нужного принтера.
Заметим, что эти функции экспортируются драйвером как обычной DLL-библиотекой (драйвер принтера и есть DLL-библиотека). Для вызова одной из этих функций вы должны загрузить драйвер явным образом, вызвав функцию LoadLibrary , а затем получить адрес точки входа при помощи функции GetProcAddress . DLL-библиотеки и перечисленные выше функции мы описали в 13 томе "Библиотеки системного программиста".
Все это выглядит достаточно сложно и громоздко, однако стандартное приложение Windows должно обеспечивать возможность выбора принтера для печати и возможность установки параметров для выбранного принтера.
К счастью, DLL-библиотека commdlg.dll содержит функцию PrintDlg, способную решить все перечисленные выше задачи и вдобавок получить для выбранного принтера контекст отображения, который можно использовать для рисования на принтере (или печати, если вам так больше нравится).
Контекст отображения
Итак, займемся описанием "листа бумаги", на "поверхности" которого выполняется рисование графических изображений и текста - контекста отображения.Прежде всего уточним понятия контекста отображения и контекста устройства .
Контекст устройства выступает в роли связующего звена между приложением и драйвером устройства (рис. 1.1) и представляет собой структуру данных размером примерно 800 байт. Эта структура данных содержит информацию о том, как нужно выполнять операции вывода на данном устройстве (цвет и толщину линии, тип системы координат и т. д.).
Рис. 1.1. Вывод данных через контекст устройства
Если приложение получает или создает контекст для устройства отображения, такой контекст называется контекстом отображения. Поэтому когда, например, приложение получает контекст для отображения в одном из своих окон, такой контекст называется контекстом отображения. Если же ему требуется выполнять операцию вывода для устройства (для принтера или для экрана дисплея), приложение должно получить или создать контекст устройства. Однако следует понимать, что контексты устройства и отображения содержат описания одних и тех же характеристик и имеют одинаковую структуру. Название контекста определяется только тем, относится ли контекст к окну отображения или устройству вывода.
LCustData
Произвольные данные, которые приложение может передать функции фильтра.LfCharSet
Набор символов.Можно использовать одну из следующих констант, определенных в файле windows.h:
| Константа | Значение | Описание |
| ANSI_CHARSET | 0 | Набор символов в кодировке ANSI |
| DEFAULT_CHARSET | 1 | Не используется при отображении шрифтов. Определяется при необходимости запросить шрифт с заданным именем и размером шрифта. Следует использовать с осторожностью, так как если указанного шрифта нет, GDI может выделить шрифт с любым набором символов |
| SYMBOL_CHARSET | 2 | Символьный шрифт, такой как, например, Wingdings |
| SHIFTJIS_CHARSET | 128 | Шрифт, в котором для представления символов используется двухбайтовая кодировка. Нужен для работы с японской версией Windows |
| OEM_CHARSET | 255 | Набор символов в кодировке OEM |
LfClipPrecision
Поле используется для определения способа, при помощи которого обрезается изображение символа, частично попавшего за пределы области ограничения вывода (clipping region), выбранную в контекст отображения.Можно использовать следующие константы: CLIP_DEFAULT_PRECIS , CLIP_CHARACTER_PRECIS , CLIP_STROKE_PRECIS , CLIP_MASK , CLIP_LH_ANGLES , CLIP_TT_ALWAYS , CLIP_EMBEDDED .
Если указана константа CLIP_LH_ANGLES , направление вращения текста зависит от установленного режима отображения.
LfEscapement
Угол между базовой линией шрифта и координатной осью X в десятых долях градуса (угол отсчитывается в направлении против часовой стрелки).Если в процессе отображения логического шрифта на физический будет выбран растровый или векторный шрифт, текст будет выведен в горизонтальном направлении, так как вращать можно только шрифты True Type и векторные шрифты.
LfFaceName
Строка, закрытая двоичным нулем, которая служит названием внешнего вида шрифта. Размер строки (включая закрывающий строку нуль) не должен превышать LF_FACESIZE байт.Вы можете указать, что вам нужен, например, шрифт "Arial", однако это вовсе не гарантирует, что именно этот шрифт будет предоставлен в распоряжение приложения.
LfHeight
Высота шрифта в логических единицах (зависят от установленного режима отображения).Можно указывать положительные и отрицательные значения, а также нуль. Если указано нулевое значение, выбирается шрифт размером в 12 пунктов (значение по умолчанию).
Положительные значения определяют высоту ячеек, в которых располагается буква, что соответствует содержимому поля tmHeight структуры TEXTMETRICS.
Абсолютная величина отрицательного значения определяет высоту символов, т.е. tmHeight - tmInternalLeading.
LfItalic
Если содержимое этого поля не равно нулю, запрашивается шрифт с наклонными буквами.LfOrientation
Это поле определяет ориентацию символов шрифта. К сожалению, операционная система Windows версии 3.1 игнорирует поле lfOrientation.LfOutPrecision
Требуемая степень соответствия параметров шрифта.Это поле используется для того, чтобы указать GDI способ выбора между двумя шрифтами, имеющими одинаковое название, но разный тип. Например, для удовлетворения запроса можно использовать растровый или масштабируемый шрифт с названием OddType. Если в поле lfOutPrecision указать константу OUT_TT_PRECIS, будет выбран масштабируемый шрифт.
Можно указывать одну из следующих констант:
| Константа | Значение | Описание |
| OUT_DEFAULT_PRECIS | 0 | Используется точность, заданная по умолчанию |
| OUT_STRING_PRECIS | 1 | Выбирается шрифт, для которого соблюдается наибольшее соответствие в размерах символов |
| OUT_CHARACTER_PRECIS | 2 | Аналогично OUT_STRING_PRECIS |
| OUT_STROKE_PRECIS | 3 | Требуется точное соответствие между запрошенными атрибутами и атрибутами полученного шрифта |
| OUT_TT_PRECIS | 4 | Выбирается масштабируемый шрифт True Type, даже если есть подходящий растровый или векторный шрифт |
| OUT_DEVICE_PRECIS | 5 | Выбирается шрифт устройства вывода |
| OUT_RASTER_PRECIS | 6 | Выбирается растровый шрифт |
| OUT_TT_ONLY_PRECIS | 7 | Используются только шрифты True Type |
LfPitchAndFamily
С помощью этого поля можно определить, нужна ли фиксированная или переменна ширина символов. Кроме этого, можно определить семейство, к которому должен принадлежать полученный шрифт.Фиксированная или переменная ширина символов задается при помощи следующих констант:
| Константа | Описание |
| DEFAULT_PITCH | Не имеет значения, будет ли шрифт иметь фиксированную или переменную ширину символов |
| FIXED_PITCH | Нужен шрифт с фиксированной шириной символов |
| VARIABLE_PITCH | Нужен шрифт с переменной шириной символов |
Вы можете объединить при помощи логической операции ИЛИ эти константы с константами, соответствующими семейству шрифта:
| Константа | Описание |
| FF_DECORATIVE | Шрифт, содержащий маленькие рисунки (пиктограммы). Примером такого шрифта может послужить шрифт Wingdings, поставляемый в составе Windows |
| FF_DONTCARE | Семейство шрифта не имеет значения |
| FF_MODERN | Семейство Modern. Фиксированная ширина символов, могут быть засечки (но могут и не быть) |
| FF_ROMAN | Семейство Roman. Переменная ширина букв, есть засечки |
| FF_SCRIPT | Семейство Script. Рукописный шрифт |
| FF_SWISS | Семейство Swiss. Переменная ширина букв, нет засечек |
LfQuality
Качество шрифта, полученного при отображении.Можно указывать одну из следующих констант:
| Константа | Описание |
| DEFAULT_QUALITY | Качество не имеет значения |
| DRAFT_QUALITY | Низкое качество. Допустимо масштабирование шрифтов, синтезирование наклонных, жирных, перечеркнутых и подчеркнутых символов |
| PROOF_QUALITY | Высокое качество. Масштабирование шрифтов не допускается. При этом могут быть получены символы, имеющие размер, немного меньший запрошенного |
LfStrikeOut
Если содержимое этого поля не равно нулю, запрашивается шрифт с перечеркнутыми буквами.LfUnderline
Если содержимое этого поля не равно нулю, запрашивается шрифт с подчеркиванием букв.LfWeight
Вес шрифта. Определяет жирность символов шрифта и может находиться в пределах от 0 до 1000. Файл windows.h содержит определение символических констант для этого поля:| Константа | Значение |
| FW_DONTCARE | 0 |
| FW_THIN | 100 |
| FW_EXTRALIGHT | 200 |
| FW_ULTRALIGHT | 200 |
| FW_LIGHT | 300 |
| FW_NORMAL | 400 |
| FW_REGULAR | 400 |
| FW_MEDIUM | 500 |
| FW_SEMIBOLD | 600 |
| FW_DEMIBOLD | 600 |
| FW_BOLD | 700 |
| FW_EXTRABOLD | 800 |
| FW_ULTRABOLD | 800 |
| FW_BLACK | 900 |
| FW_HEAVY | 900 |
Вы можете использовать любое из указанных значений, однако следует иметь в виду, что многие шрифты содержат описания символов только для веса FW_NORMAL, FW_REGULAR и FW_BOLD.
LfWidth
Ширина символов в логических единицах.Если указано нулевое значение, используется значение по умолчанию, которое зависит от высоты шрифта и отношения масштабов по осям координат (aspect ratio) для шрифта и устройства вывода.
Личный контекст отображения
Указав в стиле класса окна значение CS_OWNDC , можно добиться того, что для каждого окна, созданного на базе такого класса, Windows создаст отдельную структуру контекста отображения:wc.style = CS_OWNDC;
Личный контекст отображения можно, получив один раз, никогда не освобождать. Вы можете один раз настроить атрибуты контекста отображения сразу после получения самого контекста и использовать полученный контекст без изменений до завершения работы приложения.
Однако не будет ошибкой, если приложение будет использовать личный контекст отображения как общий, то есть каждый раз при обработке сообщения WM_PAINT (или другого сообщения, обработчик которого занимается рисованием) оно будет получать контекст и затем отдавать его. Соответствующие функции (BeginPaint, EndPaint, GetDC, ReleaseDC) будут возвращать управление, не выполняя никаких действий.
Личный контекст проще в использовании, так как его можно получать и настраивать только один раз, однако оперативная память будет расходоваться менее экономно.
Литература
Логическая система координат
Приложения Windows могут использовать одну из нескольких логических координат , устанавливая соответствующий режим отображения в контексте отображения. При этом, как мы уже говорили, можно использовать любое направление координатных осей и любое расположение начала координат. Например, возможна система координат, в которой задаются положительные и отрицательные координаты по любой оси (рис. 2.2).Рис. 2.2. Одна из возможных систем координат
Для установки режима отображения, непосредственно определяющего направление осей и размер логической единицы системы координат, используется функция SetMapMode :
int WINAPI SetMapMode(HDC hdc, int nMapMode);
Для контекста отображения hdc эта функция устанавливает новый режим отображения, заданный параметром nMapMode, возвращая номер режима отображения, который был установлен раньше.
Параметр nMapMode может принимать одно из следующих значений.
| Режим отображения | Направление оси X | Направление оси Y | Размер одной логической единицы |
| MM_TEXT | Вправо | Вниз | 1 пиксел |
| MM_LOMETRIC | Вправо | Вверх | 0,1 мм |
| MM_HIMETRIC | Вправо | Вверх | 0,01 мм |
| MM_LOENGLISH | Вправо | Вверх | 0,01 дюйм |
| MM_HIENGLISH | Вправо | Вверх | 0,001 дюйм |
| MM_TWIPS | Вправо | Вверх | 1/1440 дюйма |
| MM_ISOTROPIC | Можно выбирать | Можно выбирать | Произвольный, одинаковый для осей X и Y |
| MM_ANISOTROPIC | Можно выбирать | Можно выбирать | Произвольный, может быть разный для осей X и Y |
Как видно из этой таблицы, в режиме отображения MM_TEXT, выбранном в контекст отображения по умолчанию, используется нестандартное (для геометрии, математики и физики) направление оси Y - вниз от начала координат. Мы уже говорили, что такое направление оси Y удобно для отображения текста, поэтому этот режим отображения иногда называют текстовым.
Нетрудно заметить, что в режиме MM_TEXT логическая единица длины полностью соответствует физической, поэтому при рисовании геометрических фигур возможны искажения формы. Эти искажения связаны с тем, что форма пиксела для некоторых видеоконтроллеров может быть отличной от квадратной.
Режим MM_TEXT неудобен для рисования фигур.
В режимах MM_LOMETRIC, MM_HIMETRIC, MM_LOENGLISH, MM_HIENGLISH, MM_TWIPS используется более привычное направление осей координат и единицы длины, не зависящие от аппаратного обеспечения устройства вывода.
В режиме MM_ISOTROPIC вы можете выбирать произвольное направление осей координат и произвольный (но одинаковый) масштаб для осей X и Y. Заметим, что произвольное направление координат в нашем случае не подразумевает их произвольного расположения относительно вертикальной и горизонтальной осей - ось X может располагаться только горизонтально, ось Y - только вертикально.
Режим MM_ANISOTROPIC еще более универсален. Он позволяет устанавливать произвольное направление осей координат, произвольный масштаб для осей координат, причем для каждой оси можно установить свой собственный масштаб.
Во всех режимах отображения, кроме MM_TEXT и MM_ANISOTROPIC с разным масштабом для осей X и Y, приложение может не заботиться о "квадратуре пиксела", так как масштаб по осям координат одинаковый и не зависит от особенностей устройства вывода.
Сделаем небольшое пояснение относительно режима отображения MM_TWIPS. В этом режиме используется единица длины twip (от twentieth of a point - двадцатая часть точки, или, в терминах полиграфии, двадцатая часть пункта). Размер одного пункта приблизительно равен 1/72 дюйма, следовательно размер единицы длины twip равен 1/1440 дюйма.
С помощью функции GetMapMode приложение может в любой момент времени определить номер режима отображения, выбранный в контекст отображения hdc:
int WINAPI GetMapMode(HDC hdc);
LpfnPrintHook
Адрес функции фильтра для диалоговой панели "Print".LpfnSetupHook
Адрес функции фильтра для диалоговой панели "Print Setup".LpPrintTemplateName
Адрес текстовой строки, закрытой нулем, содержащей имя ресурса для шаблона диалоговой панели "Print". Для использования этого поля необходимо указать флаг PD_ENABLEPRINTTEMPLATE.LpSetupTemplateName
Адрес текстовой строки, закрытой нулем, содержащей имя ресурса для шаблона диалоговой панели "Print Setup". Для использования этого поля необходимо указать флаг PD_ENABLESETUPTEMPLATE.LStructSize
Размер структуры PRINTDLG в байтах. Это поле следует заполнить перед вызовом функции PrintDlg.Масштаб осей для окна
Для некоторых режимов отображения приложение может изменять масштаб осей в окне (window extent ), устанавливая для него новое значение в контексте отображения.По умолчанию используется значение (1,1), т. е. используется масштаб 1:1. Приложение может изменить масштаб осей для окна, вызвав функцию SetWindowExt .
Масштаб осей физических координат
Контекст отображения содержит масштаб осей для физического устройства (viewport extent ), который вместе с масштабом осей в окне используется в процессе преобразования координат.По умолчанию для масштаба осей физических координат используется значение (1,1), т. е. масштаб 1:1. Приложение может изменить масштаб осей физических координат, вызвав функцию SetViewportExt .
Механизм реализации логической палитры
В самом начале этой главы мы рассказали вам о системной цветовой палитре. Вы знаете, что в системной палитре, состоящей из 256 ячеек, 20 ячеек зарезервированы для статических цветов. Остальные 236 ячеек доступны для приложений. Что же приложения могут с ними сделать?Любое приложение может создать свою собственную палитру цветов в виде массива размером до 256 элементов, содержащего данные типа PALETTEENTRY, которые могут хранить RGB-цвета или номера цветов в системной палитре.
Подготовив массив, содержащий цвета, приложение может создать логическую палитру , вызвав функцию CreatePalette . Затем палитру нужно выбрать в контекст отображения функцией SelectPalette . Так как на экране могут отображаться только цвета, находящиеся в системной палитре, в процессе реализации палитры приложение должно перенести (или отобразить) цвета из логической палитры в системную палитру, вызвав функцию RealizePalette . Последний шаг называется реализацией палитры .
В зависимости от того, как подготовлена логическая палитра, а также от того, является приложение активным или фоновым, механизм реализации может выполняться по-разному. Рассмотрим обычный способ реализации.
Первоначально в системной палитре 20 ячеек отмечены как занятые для статических цветов и 236 - как свободные. Когда первое приложение реализует свою логическую палитру, цвета из этой палитры переписываются в свободные ячейки системной палитры, после чего они становятся доступными для отображения. Если свободных ячеек не хватает для размещения всей логической палитры, оставшиеся цвета будут отображены на ближайшие имеющиеся в системной палитре. При этом неизбежны цветовые искажения.
Однако в Windows одновременно может работать несколько приложений, одно из которых является активным, а остальные - фоновыми. Для активных и фоновых приложений используется разный механизм реализации логической палитры.
Активное приложение имеет больший приоритет в использовании свободных ячеек системной палитры. Как правило, запрос активного приложения (точнее говоря, активного окна) на реализацию логической палитры выполняется полностью, так как перед реализацией все ячейки системной палитры (кроме 20 зарезервированных) отмечаются как свободные.
Фоновые приложения довольствуются теми свободными ячейками, что остались после реализации логической палитры активного приложения. Поэтому для фоновых приложений качество "цветопередачи" может быть не очень высоким.
На рис. 3.6 показан процесс реализации логической палитры для активного окна.
Рис. 3.6. Реализация логической палитры для активного окна
На этом рисунке для наглядности мы уменьшили размер системной палитры. Разные цвета показаны различной штриховкой соответствующих прямоугольников. Из рисунка видно, что после реализации логической палитры активного окна в системной палитре осталось три свободные ячейки.
Теперь допустим, что фоновое приложение реализует логическую палитру для своего окна. В его распоряжении есть три свободные ячейки. Процесс реализации логической палитры для фонового окна показан на рис. 3.7.
Рис. 3.7. Реализация логической палитры для фонового окна
Часть цветов логической палитры фонового окна уже есть в системной палитре, поэтому они просто отображаются на соответствующие ячейки системной палитры. Три свободные ячейки используются для тех цветов, которых нет в системной палитре.
Однако в нашей логической палитре есть цвета, которым нет точного соответствия в системной палитре, причем свободных ячеек тоже не осталось. В этом случае GDI отображает эти цвета на близкие из системной палитры (на рис. 3.7 такое отображение показано пунктирной линией).
После такого упрощенного описания процесса реализации логической палитры перейдем к конкретным шагам, необходимым для использования палитр.
Метрические режимы отображения
Режим MM_LOMETRIC , наряду с режимами MM_HIMETRIC , MM_LOENGLISH , MM_HIENGLISH и MM_TWIPS, относится к метрическим режимам. Эти режимы отображения позволяют использовать привычные единицы измерения, такие как миллиметры и дюймы.В метрических режимах отображения используются полные формулы преобразования координат, приведенные выше в разделе "Преобразование координат". В этих формулах приложение может изменять переменные, определяющие смещение начала физической или логической системы координат xViewOrg, yViewOrg, xWinOrg и yWinOrg.
Приложение не может изменить значения переменных xViewExt, yViewExt, xWinExt и yWinExt, от которых зависит масштаб по осям координат. Отношения xViewExt/xWinExt и yViewExt/yWinExt имеют фиксированное значение для каждого из метрических режимов отображения.
Заметим, что для этих режимов отношение yViewExt/yWinExt имеет отрицательный знак, в результате чего ось Y оказывается направленной снизу вверх.
Обратим ваше внимание на одно важное обстоятельство, связанное с использованием метрических режимов отображения.
Сразу после переключения в метрический режим отображения система координат примет достаточно странный вид (рис. 2.4).
Рис. 2.4. Ориентация осей сразу после переключения в метрический режим отображения
Ось X, как и следовало ожидать, окажется направленной слева направо, а ось Y - снизу вверх. Точка с координатами (0,0) будет находиться в верхнем левом углу экрана, поэтому для того чтобы нарисовать что-нибудь в такой системе координат, вам придется для y-координаты графических объектов использовать отрицательные числа. Для того чтобы система координат приняла более удобный вид, можно переместить начало физических координат в нижний левый угол окна или в центр окна.
Прежде, чем выполнять перемещение начала координат, следует определить размеры внутренней области окна. Это можно сделать при обработке сообщения WM_SIZE :
static short cxClient, cyClient; .... case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); ....
return 0; }
Для того чтобы расположить начало координат в левом нижнем углу окна, следует вызвать функцию SetViewportOrg, передав ей новые координаты начала физической системы координат (0,cyClient):
SetViewportOrg(hdc, 0, cyClient);
Полученная в результате система координат показана на рис. 2.5.
Рис. 2.5. Метрическая система координат, начало координат находится в левом нижнем углу окна
Аналогичным образом можно расположить начало системы координат в середине окна (рис. 2.6), обеспечив возможность использования положительных и отрицательных координат вдоль оси X и Y:
SetViewportOrg(hdc, cxClient/2, cyClient/2);
Рис. 2.6. Метрическая система координат, начало координат находится в центре окна
Начальные координаты кисти
Начальные координаты кисти (brush origin ) используются для определения координат точки внутри кисти, которая будет служить начальной при закраске внутренней области фигуры или окна. По умолчанию используются координаты (0,0), соответствующие верхнему левому углу кисти (в системе координат, выбранной в контекст отображения по умолчанию).Приложение может изменить начальные координаты кисти при помощи функций SetBrushOrg и UnrealizeObject .
Начало системы физических координат
Начало системы физических координат (viewport origin ) относится не к окну приложения, а к физическому устройству вывода (например, ко всему экрану монитора) и используется при выполнении преобразования логических координат в физические.По умолчанию начало системы физических координат установлено в точку (0,0). Для перемещения начала системы координат окна можно использовать функцию SetViewportOrg .
Начало системы координат для окна
В контексте отображения хранится информация о расположении начала системы координат для окна и для физического устройства отображения. Эта информация используется GDI при выполнении преобразования логических координат в физические, которое выполняется различными способами в зависимости от установленного режима отображения. Подробнее этот вопрос будет рассмотрен при описании систем координат, доступных для приложений Windows версии 3.1.По умолчанию начало системы координат для окна (window origin ) установлено в точку (0,0). Для перемещения начала системы координат окна можно использовать функцию SetWindowOrg .
Настройка атрибутов контекста отображения для рисования линий
Как это нетрудно заметить, функции, предназначенные для рисования линий, не имеют никаких параметров, определяющих толщину, цвет и стиль линии. Эти, а также другие характеристики (например, режим прозрачности), выбираются при установке соответствующих атрибутов контекста отображения.NCopies
Значение для инициализации органа управления "Copies", если поле hDevMode содержит значение NULL.NFromPage
Начальное значение для инициализации органа управления "From" диалоговой панели "Print". Используется только в том случае, если в поле Flags указан флаг PD_PAGENUMS. Максимальное значение для поля nFromPage составляет 0xfffe.После возвращения из функции PrintDlg это поле содержит номер страницы документа, с которой должна начинаться печать.
NMaxPage
Максимальное количество страниц, которое можно задать при помощи органов управления "From" и "To".NMinPage
Минимальное количество страниц, которое можно задать при помощи органов управления "From" и "To".NToPage
Начальное значение для инициализации органа управления "To" диалоговой панели "Print". Используется только в том случае, если в поле Flags указан флаг PD_PAGENUMS. Максимальное значение для поля nFromPage составляет 0xfffe.После возвращения из функции PrintDlg это поле содержит номер страницы документа, до которой должна выполняться печать.
Область эллиптической формы
Область эллиптической формы (или, как частный случай, круглой формы) можно создать при помощи функции CreateEllipticRgn :HRGN WINAPI CreateEllipticRgn( int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
Параметры этой функции описывают координаты воображаемого прямоугольника, в который вписана область эллиптической формы.
Функция CreateEllipticRgnIndirect также используется для создания области в форме эллипса:
HRGN WINAPI CreateEllipticRgnIndirect(const RECT FAR* lprc);
В отличие от функции CreateEllipticRgn координаты прямоугольника задаются с помощью структуры типа RECT, указатель на которую передается через параметр lprc.
Область ограничения
По умолчанию в контексте отображения задана область ограничения вывода (clipping region ), совпадающая со всей областью вывода. Например, если приложение получило контекст отображения для окна, область ограничения совпадает с внутренней областью (client region) этого окна.Приложение может создавать область ограничения вывода сложной, практически произвольной, формы, исключая или включая в нее области в виде многоугольников или эллипсов. Это позволяет получить при отображении интересные эффекты, труднодостижимые без использования областей ограничения (правда, ценой снижения скорости вывода изображения).
Для работы с областями предназначены следующие функции: CreateEllipticRgn , CreateEllipticRgnIndirect , CreatePolygonRgn , CreatePolyPolygonRgn , CreateRectRgn , CreateRoundRectRgn , ExcludeClipRgn , IntersectClipRgn , OffsetClipRgn , SelectclipRgn .
Приложение может использовать область для маски, ограничивающей вывод. Для этого область следует выбрать в контекст отображения функцией SelectClipRgn :
int WINAPI SelectClipRgn(HDC hdc, HRGN hrgn);
В качестве значения параметра hrgn вы можете использовать значение NULL. В этом случае для ограничения вывода будет использована внутренняя область окна.
Область в виде многоугольника
Можно создать область в виде произвольного многоугольника. Для этого следует воспользоваться функцией CreatePolygonRgn :HRGN WINAPI CreatePolygonRgn( const POINT FAR* lppt, // адрес массива точек int cPoints, // размер массива int fnPolyFillMode); // режим заполнения
Функция CreatePolyPolygonRgn создает область, состоящую из нескольких многоугольников:
HRGN WINAPI CreatePolyPolygonRgn( const POINT FAR* lppt, // адрес массива точек int FAR* lpnPolyCounts, // адрес массива количества точек // в многоугольниках int cPolygons); // количество многоугольников
Назначение параметров описанных выше двух функций аналогично назначению параметров функций рисования многоугольников Polygon и PolyPolygon (за исключением того, что при создании области не требуется указывать идентификатор контекста отображения).
Области
В интерфейсе GDI есть средства, позволяющие приложениям создавать области достаточно сложной формы из прямоугольных, многоугольных и эллиптических областей. Такие области можно закрашивать или использовать в качестве маски при выводе графического изображения. В последнем случае область называется областью ограничения. Она должна быть выбрана в контекст отображения.Общий контекст отображения
Этот контекст используется чаще всего и поэтому для ускорения доступа к нему Windows использует кеширование (как мы уже говорили, размер кеша достаточен для хранения только пяти контекстов отображения).Для получения общего контекста отображения приложение должно вызвать функцию BeginPaint (при обработке сообщения WM_PAINT ) или GetDC (при обработке других сообщений). При этом перед регистрацией класса окна в поле стиля класса окна в структуре WNDCLASS не должны использоваться значения CS_OWNDC , CS_PARENTDC или CS_CLASSDC :
wc.style = 0;
Функция BeginPaint возвращает контекст отображения для окна hwnd:
HDC WINAPI BeginPaint(HWND hwnd, PAINTSTRUCT FAR* lpps);
Перед этим она подготавливает указанное окно для рисования, заполняя структуру типа PAINTSTRUCT (адрес которой передается через параметр lpps) информацией, которую можно использовать в процессе рисования.
Структура PAINTSTRUCT и указатели на нее (различных типов) описаны в файле windows.h:
typedef struct tagPAINTSTRUCT { HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; BYTE rgbReserved[16]; } PAINTSTRUCT; typedef PAINTSTRUCT* PPAINTSTRUCT; typedef PAINTSTRUCT NEAR* NPPAINTSTRUCT; typedef PAINTSTRUCT FAR* LPPAINTSTRUCT;
Рассмотрим назначение отдельных полей структуры PAINTSTRUCT.
Поле hdc после возврата из функции будет содержать идентификатор полученного контекста отображения, который должен передаваться в качестве параметра функциям интерфейса GDI, выполняющим рисование. Можно использовать также значение идентификатора контекста, возвращенное функцией BeginPaint, так как эти значения одинаковые.
Анализируя содержимое поля fErase, приложение может определить, нужно ли перерисовывать фон окна. Если в этом поле находится значение TRUE, фон окна должен быть перерисован. Такая необходимость может возникнуть в том случае, если в классе, на базе которого создано окно, при регистрации не была выбрана кисть для закрашивания фона (поле hbrBackground структуры WNDCLASS).
Поле rcPaint, которое представляет собой структуру типа RECT, содержит координаты верхнего левого и правого нижнего угла прямоугольника, внутри которого нужно рисовать.
Напомним вам формат структуры RECT , описанной в файле windows.h:
typedef struct tagRECT { int left; int top; int right; int bottom; } RECT;
Мы уже говорили вам в 11 томе "Библиотеки системного программиста", что при обработке сообщения WM_PAINT приложение должно суметь перерисовать все окно или любую его часть. Сообщение WM_PAINT может попасть в функцию окна в том случае, если все окно или его часть требуют перерисовки. Поле rcPaint в структуре PAINTSTRUCT содержит координаты прямоугольной области, расположенной в окне и требующей перерисовки.
Остальные поля зарезервированы для Windows и не используются приложениями.
Контекст отображения, полученный при помощи функции BeginPaint, необходимо освободить перед завершением обработки сообщения WM_PAINT, вызвав функцию EndPaint :
void WINAPI EndPaint(HWND hwnd, const PAINTSTRUCT FAR* lpps);
Функции EndPaint передаются те же параметры, что и функции BeginPaint.
Обычно обработчик сообщения WM_PAINT выглядит следующим образом:
PAINTSTRUCT ps; HDC hdc; ........ case WM_PAINT: { // Получаем контекст отображения hdc = BeginPaint(hwnd, &ps);
// После получения контекста отображения // можно вызывать функции GDI TextOut(hdc, 0, 0, (LPSTR)"String", 6); . . // Освобождаем контекст отображения EndPaint(hwnd, &ps); break; }
Подобный фрагмент кода вы можете найти в приложениях, описанных в одном из предыдущих томов "Библиотеки системного программиста".
Функции BeginPaint и EndPaint можно использовать только внутри обработчика сообщения WM_PAINT. Если же приложению требуется рисовать во время обработки других сообщений, оно должно получить контекст отображения с помощью функции GetDC . После завершения процедуры рисования перед выходом из обработчика сообщения следует освободить полученный контекст отображения, вызвав функцию ReleaseDC .
Функция GetDC возвращает контекст отображения для окна с идентификатором hwnd:
HDC WINAPI GetDC(HWND hwnd);
Полученный таким образом контекст отображения можно использовать для рисования во внутренней области окна (window client region).
Функция ReleaseDC освобождает контекст отображения hdc, полученный для окна hwnd:
int WINAPI ReleaseDC(HWND hwnd, HDC hdc);
Мы еще раз обращаем ваше внимание на необходимость своевременного освобождения общего контекста отображения.
Каждый раз, когда приложение получает общий контекст отображения, его атрибуты принимают значения по умолчанию, перечисленные нами ранее. Если перед выполнением рисования приложение изменит атрибуты контекста отображения, вызвав соответствующие функции GDI, в следующий раз при получении общего контекста отображения эти атрибуты снова примут значения по умолчанию. Поэтому установка атрибутов должна выполняться каждый раз после получения общего контекста отображения. Такая процедура отнимает дополнительное время, но она необходима при использовании контекста отображения этого типа.
Определение логического шрифта
Если приложение ограничивает себя только встроенными шрифтами, оно не сможет воспользоваться всем многообразием созданных масштабируемых шрифтов и даже просто не сможет изменить размер букв или сделать текст жирным или наклонным. Для полного использования шрифтовых возможностей операционной системы Windows вы должны познакомиться с процедурой определения логических шрифтов.Приложение может получить идентификатор шрифта, указав его параметры (такие как размеры символов, семейство шрифта, наклон относительно горизонтальной оси и т. п.) функции CreateFont . Эта функция имеет 14 параметров, поэтому не слишком удобна в использовании. Вместо нее лучше пользоваться функцией CreateFontIndirect :
HFONT WINAPI CreateFontIndirect(const LOGFONT FAR* lplf);
Функция возвращает идентификатор созданного логического шрифта, который можно выбрать в контекст отображения макрокомандой SelectFont, при этом для вывода будет подобран наиболее подходящий физический шрифт.
В качестве параметра функции CreateFontIndirect передается указатель на структуру типа LOGFONT , определенную в файле windows.h:
typedef struct tagLOGFONT { int lfHeight; int lfWidth; int lfEscapement; int lfOrientation; int lfWeight; BYTE lfItalic; BYTE lfUnderline; BYTE lfStrikeOut; BYTE lfCharSet; BYTE lfOutPrecision; BYTE lfClipPrecision; BYTE lfQuality; BYTE lfPitchAndFamily; char lfFaceName[LF_FACESIZE]; } LOGFONT; typedef LOGFONT* PLOGFONT; typedef LOGFONT NEAR* NPLOGFONT; typedef LOGFONT FAR* LPLOGFONT;
Перед вызовом функции CreateFontIndirect вы должны заполнить структуру LOGFONT нужными значениями, определяющими параметры шрифта. В неиспользованные поля следует записать нулевые значения. Можно записать нулевые значения во все поля, однако это едва ли имеет смысл.
Опишем назначение отдельных полей структуры LOGFONT. При этом мы будем пользоваться метриками шрифта, описанными в 11 томе "Библиотеки системного программиста" (стр. 144).
Определение метрик шрифта
Для удобства мы напомним вам методику определения метрик шрифта.Метрику шрифта , выбранного в контекст отображения, можно определить с помощью функции GetTextMetrics :
BOOL WINAPI GetTextMetrics(HDC hdc, TEXTMETRIC FAR* lptm);
Параметр hdc указывает контекст отображения или устройства, для которого требуется получить информацию о метрике шрифта. В качестве этого параметра можно использовать значение, возвращаемое функцией BeginPaint или GetDC.
Параметр lptm является дальним указателем на структуру типа TEXTMETRIC, в которую будет записана информация о метриках шрифта, выбранного в указанный контекст устройства.
В случае успешного завершения функция возвращает значение TRUE, в противном случае - FALSE.
Структура TEXTMETRIC описана в файле windows.h следующим образом:
typedef struct tagTEXTMETRIC { int tmHeight; int tmAscent; int tmDescent; int tmInternalLeading; int tmExternalLeading; int tmAveCharWidth; int tmMaxCharWidth; int tmWeight; BYTE tmItalic; BYTE tmUnderlined; BYTE tmStruckOut; BYTE tmFirstChar; BYTE tmLastChar; BYTE tmDefaultChar; BYTE tmBreakChar; BYTE tmPitchAndFamily; BYTE tmCharSet; int tmOverhang; int tmDigitizedAspectX; int tmDigitizedAspectY; } TEXTMETRIC;
Параметры, имеющие отношение к вертикальным размерам букв, представлены на рис. 5.5.
Рис. 5.5. Метрики шрифта
Отсчет всех размеров выполняется от так называемой базовой линии шрифта. Для размеров используются логические единицы, которые зависят от режима отображения, установленного в контексте устройства.
Общая высота букв находится в поле tmHeight структуры TEXTMETRIC. Эта высота складывается из двух компонент - tmAscent и tmDescent. Компонента tmAscent представляет собой высоту букв от базовой линии с учетом таких элементов, как тильда в букве "Й". Компонента tmDescent определяет пространство, занимаемое буквами ниже базовой линии. Сумма tmAscent и tmDescent в точности равна tmHeight.
Величина tmInternalLeading определяет размер выступающих элементов букв и может быть равна нулю.
Величина tmExternalLeading определяет минимальный межстрочный интервал, рекомендуемый разработчиком шрифта. Ваше приложение может игнорировать межстрочный интервал, однако в этом случае строки будут соприкасаться друг с другом, что не улучшит внешнего вида окна.
Для ширины букв в структуре TEXTMETRIC есть два поля с именами tmAveCharWidth и tmMaxCharWidth. Поле tmAveCharWidth содержит среднее значение ширины строчных букв шрифта. Это значение приблизительно соответствует ширине латинской буквы "x". Поле tmMaxCharWidth определяет ширину самой широкой буквы в шрифте. Для шрифта с фиксированной шириной букв поля tmAveCharWidth и tmMaxCharWidth содержат одинаковые значения, которые зависят от самого шрифта.
Поле tmWeight определяет жирность шрифта. Может находиться в пределах от 0 до 1000. Файл windows.h содержит определение символических констант для этого поля:
| Константа | Значение |
| FW_DONTCARE | 0 |
| FW_THIN | 100 |
| FW_EXTRALIGHT | 200 |
| FW_ULTRALIGHT | 200 |
| FW_LIGHT | 300 |
| FW_NORMAL | 400 |
| FW_REGULAR | 400 |
| FW_MEDIUM | 500 |
| FW_SEMIBOLD | 600 |
| FW_DEMIBOLD | 600 |
| FW_BOLD | 700 |
| FW_EXTRABOLD | 800 |
| FW_ULTRABOLD | 800 |
| FW_BLACK | 900 |
| FW_HEAVY | 900 |
Поля tmFirstChar и tmLastChar определяют, соответственно, коды первого и последнего символа, определенных в шрифте.
Если приложение пытается вывести символ, код которого отсутствует в шрифте, вместо него будет выведен символ с кодом, расположенным в поле tmDefaultChar.
Поле tmBreakChar содержит код символа, который используется для переноса слов с одной строки на другую при выравнивании текста.
Поле tmPitchAndFamily содержит код семейства шрифта. В нем могут находится следующие флаги, соответствующие четырем младшим битам:
| Значение | Описание |
| TMPF_FIXED_PITCH | Шрифт с фиксированной шириной букв |
| TMPF_VECTOR | Векторный шрифт или масштабируемый шрифт True Type |
| TMPF_TRUETYPE | Шрифт True Type |
| TMPF_DEVICE | Шрифт устройства вывода, например, принтерный шрифт |
Одновременно может быть установлено несколько флагов с префиксом имени TMPF.
Старшие четыре бита описывают семейство шрифта:
| Константа | Описание |
| FF_DECORATIVE | Шрифт, содержащий маленькие рисунки (пиктограммы). Примером такого шрифта может послужить шрифт Wingdings, поставляемый в составе Windows |
| FF_DONTCARE | Семейство шрифта не имеет значения или неизвестно |
| FF_MODERN | Семейство Modern. Фиксированная ширина символов, могут быть засечки (но могут и не быть) |
| FF_ROMAN | Семейство Roman. Переменная ширина букв, есть засечки |
| FF_SCRIPT | Семейство Script. Рукописный шрифт |
| FF_SWISS | Семейство Swiss. Переменная ширина букв, нет засечек |
| Константа | Значение | Описание |
| ANSI_CHARSET | 0 | Набор символов в кодировке ANSI |
| DEFAULT_CHARSET | 1 | Не используется при отображении шрифтов. Определяется при необходимости запросить шрифт с заданным именем и размером шрифта. Следует использовать с осторожностью, так как если указанного шрифта нет, GDI может выделить шрифт с любым набором символов |
| SYMBOL_CHARSET | 2 | Символьный шрифт, такой как Wingdings |
| SHIFTJIS_CHARSET | 128 | Шрифт, в котором для представления символов используется двухбайтовая кодировка. Нужен для работы с японской версией Windows |
| OEM_CHARSET | 255 | Набор символов в кодировке OEM |
Поля tmDigitizedAspectX и tmDigitizedAspectY содержат значения, которые можно использовать для определения отношения масштабов устройства отображения по горизонтали и вертикали.
Основные определения
Прежде всего необходимо определить понятия "физические координаты " и "логические координаты ".Физические координаты, как это следует из названия, имеют непосредственное отношение к физическому устройству вывода. В качестве единицы измерения длины в системе физических координат всегда используется пиксел. Если устройством вывода является экран монитора, физические координаты обычно называют экранными координатами.
Логические координаты передаются функциям GDI, выполняющим рисование фигур или вывод текста. Используемые единицы измерения зависят от режима отображения.
При отображении GDI преобразует логические координаты в физические. Способ преобразования зависит от режима отображения и других атрибутов контекста отображения, таких как расположение начала системы координат для окна, расположение начала системы физических координат, масштаб осей для окна и масштаб осей физических координат.
Основные понятия
1.1.1.2.
Интерфейс графических устройств GDI операционной системы Microsoft Windows (в дальнейшем - просто GDI), как это можно предположить из названия, предназначен для взаимодействия приложений Windows с графическими устройствами, такими как видеомонитор, принтер или плоттер.
Когда приложения обращаются к GDI для выполнения операции вывода графического изображения, они работают не с реальными (физическими) устройствами вывода, а с логическими. Приложения Windows не определяют тип видеомонитора (EGA, VGA или SVGA), а работают с логическим видеомонитором, имеющим феноменальные характеристики: способность отображать практически любой цвет, огромное разрешение и т. д. Выполняя запрос приложения, GDI обращается к драйверу соответствующего устройства вывода. Драйвер работает непосредственно с физическим устройством вывода. В процессе выполнения запроса GDI (или драйвер) учитывает ограниченные возможности физического устройства вывода и его аппаратные особенности, делая необходимые приближения.
Например, приложение может указать для цвета линии любой из примерно 16 млн. цветов, однако далеко не всякое устройство обладает таким цветовым разрешением. В зависимости от типа физического устройства, используемого для вывода, GDI может выбрать для отображения цвет, наиболее соответствующий запрошенному и поддерживаемый устройством. Если устройство вывода монохромное, вместо различных цветов могут использоваться градации серого цвета. Поэтому приложение может запросить для вывода любой цвет, но для рисования будет использован только такой, который есть на данном физическом устройстве.
Такая ситуация, когда приложение запрашивает у Windows одно, а получает другое, возникает не только при работе с цветом. Приложение может запросить для вывода шрифт, описав его характеристики. GDI подберет для вывода наиболее подходящий (с его точки зрения) шрифт, соответствующий описанию, и предоставит его приложению.
На первый взгляд, это обескураживает, однако такой механизм удобен для обеспечения аппаратной независимости.
Составляя программы для MS-DOS, вы работали с видеоадаптерами, указывая конкретные цвета и загружая в его память конкретные шрифты из отдельных файлов. Поэтому программы MS-DOS были крепко "привязаны" к аппаратуре. Для использования новых возможностей требовалось вносить изменения в программы. Приложения Windows способны работать в неизменном виде на любом оборудовании, лишь бы был соответствующий драйвер. Чем лучше используемая аппаратура, чем большими возможностями она обладает, тем ближе будут параметры полученного шрифта и цвета соответствовать запрошенным.
Поэтому даже если сейчас в вашем распоряжении есть только видеоконтроллер VGA, при разработке приложений Windows вы можете не ограничивать себя дюжиной цветов. Ваше приложение должно быть сделано так, чтобы оно могло использовать любой цвет. Со временем, когда у вас появится видеоконтроллер True Color, экран вашего приложения засветится всеми цветами радуги, причем для этого не придется вносить никаких изменений в само приложение.
Интерфейс GDI - одна из наиболее сложных компонент Microsoft Windows версии 3.1. В Windows NT она получила дальнейшее развитие, однако все базовые понятия сохранились.
Из чего состоит интерфейс GDI с точки зрения приложения?
Прежде всего, это контекст отображения и инструменты для рисования. Контекст отображения можно сравнить с листом бумаги, на котором приложение рисует то или иное графическое изображение, а также пишет текст. Инструменты для рисования - это перья, кисти (а также шрифты и даже целые графические изображения), с помощью которых создается изображение. Кроме контекста отображения и инструментов для рисования, приложениям доступны десятки функций программного интерфейса GDI, предназначенные для работы с контекстом отображения и инструментами.
Если говорить более точно, контекст отображения является структурой данных, описывающей устройство отображения. В этой структуре хранятся различные характеристики устройства и набор инструментов для рисования, выбранный по умолчанию.
Перерисовка области
Вы можете отметить область как требующую перерисовки, вызвав функцию InvalidateRgn . В результате этого приложению будет передано сообщение WM_PAINT .Приведем прототип функции InvalidateRgn:
void WINAPI InvalidateRgn(HWND hwnd, HRGN hrgn, BOOL fErase);
Через параметр hwnd следует передать идентификатор окна, содержащего обновленную область hrgn.
Параметр fErase определяет необходимость стирания фона окна перед перерисовкой. Если этот параметр имеет значение TRUE, фон стирается, если FALSE - нет.
Если ваше приложение обновило содержимое области, но не во время обработки сообщения WM_PAINT, оно может удалить область из списка областей, подлежащих перерисовке, вызвав функцию ValidateRgn :
void WINAPI ValidateRgn(HWND hwnd, HRGN hrgn);
Перья
Перья используются для рисования линий и простейших геометрических фигур. Приложение может выбрать одно из трех предопределенных перьев, либо создать собственное, выбрав нужный цвет и стиль. На рис. 1.2 изображены линии, нарисованные с использованием перьев различной ширины и стиля.Рис. 1.2. Линии разной ширины, нарисованные с использованием различных стилей
Как мы уже говорили, интерфейс GDI операционной системы Windows версии 3.1 позволяет рисовать сплошные линии различной ширины, а также пунктирные, штриховые и штрих-пунктирные линии шириной в 1 пиксел. Можно запросить любой цвет для рисования линии, однако вовсе не обязательно линия будет нарисована запрошенным цветом. Тонкости, связанные с цветом и цветовыми палитрами, мы рассмотрим позже.
На рис. 1.3. показаны геометрические фигуры, нарисованные с использованием различных перьев, сплошных и пунктирных.
Рис. 1.3. Геометрические фигуры, нарисованные с использованием различных перьев
Перо
Для того чтобы нарисовать линию или геометрическую фигуру, приложение Windows должно создать собственное перо (pen ) или воспользоваться пером, выбранным в контекст отображения по умолчанию (черное перо шириной в один пиксел).Можно создать новое перо, изменив его ширину, цвет или стиль (сплошная, пунктирная, штриховая и штрих-пунктирная линии, а также линия, в которой на одну черточку приходится по две точки). К сожалению, вы можете изменить ширину только сплошной линии.
Для работы с перьями приложение Windows должно использовать функции CreatePen , CreatePenIndirect , SelectObject .
Получение и освобождение контекста отображения
Способы получения (и, соответственно, освобождения) контекста отображения разные для контекстов разного типа. Можно выделить следующие типы контекста отображения:Каждый из перечисленных выше контекстов имеет свои особенности и свое назначение.
Получение информации о шрифте
В программном интерфейсе GDI имеется несколько функций, с помощью которых приложение может получить различную информацию о шрифте, выбранном в контекст отображения. Наибольший интерес представляют метрики шрифта, о которых мы рассказывали в 11 томе "Библиотеки системного программиста", однако для масштабируемых шрифтов True Type можно получить и другую информацию.Преобразование DDB в DIB
Если перед вами встанет задача преобразования DDB в DIB (например, для последующей записи изображения в bmp-файл), вам не обойтись без функции GetDIBits :int WINAPI GetDIBits( HDC hdc, // контекст отображения HBITMAP hbmp, // изображение DDB UINT uStartScan, // номер первой строки UINT uScanLines, // количество строк void FAR* lpvBits, // биты изображения BITMAPINFO FAR* lpbmi, // заголовок изображения UINT fuColorUse); // содержимое таблицы цветов
Параметры этой функции полностью аналогичны параметрам функции SetDIBits, однако действие прямо противоположное. Функция преобразует биты изображения в формат DIB и записывает их по адресу, заданному параметром lpvBits. Дополнительно заполняется заголовок lpbmi (если параметр lpvBits указан как NULL, функция ограничивается заполнением заголовка изображения)
Если вы собираетесь сохранить изображение DIB в bmp-файле, вы должны самостоятельно сформировать заголовок файла BITMAPFILEHEADER.
Преобразование координат
Теперь немного математики (не волнуйтесь, совсем немного).Приложение, вызывая для рисования функции GDI, указывает логические координаты. Перед выводом GDI преобразует их в физические с использованием следующих формул:
В этих формулах используются следующие обозначения.
Переменные xWindow и yWindow обозначают, соответственно, логические координаты по оси X и Y. Физические координаты обозначаются как xViewport и yViewport. Таким образом, логические координаты (xWindow,yWindow) преобразуются в физические координаты (xViewport,yViewport).
С помощью переменных xViewOrg и yViewOrg можно изменить расположение начала физических координат. По умолчанию в контексте отображения значения атрибутов, соответствующих этим переменным, равны 0. Приложение может сместить начало координат, изменив значения переменных xViewOrg и yViewOrg.
Для перемещения начала логической системы координат приложение может изменить значения переменных xWinOrg и yWinOrg, которые также определены как атрибуты контекста отображения. По умолчанию в этих переменных находятся нулевые значения.
Переменные xViewExt, yViewExt, xWinExt и yWinExt (вернее, их отношения) задают масштаб, который используется в процессе преобразования координат. Этот масштаб зависит от установленного режима отображения. Приложения могут изменить его только в режимах MM_ISOTROPIC и MM_ANISOTROPIC , для остальных режимов отображения используются фиксированные значения.
Обратные преобразования (из логических координат в физические) выполняются с использованием следующих формул:
Для выполнения подобных вычислений удобна функция MulDiv , определенная в программном интерфейсе Windows:
int WINAPI MulDiv( int nMultiplicand, // множимое int nMultiplier, // множитель int nDivisor); // делитель
Эта функция выполняет умножение 16-разрядного параметра nMultiplicand на 16-разрядный параметр nMultiplier, а затем делит полученное 32-разрядное произведение на 16-разрядный параметр nDivisor. В случае переполнения или при нулевом значении делителя функция возвращает отрицательное значение32768.
Однако есть специальные функции, предназначенные для преобразования логических координат в физические и физических в логические. Это функции LPtoDP и DPtoLP.
Функция LPtoDP выполняет преобразование логических координат в физические , причем одновременно можно преобразовать несколько пар координат, что ускоряет работу приложения за счет сокращения количества вызовов функции:
BOOL WINAPI LPtoDP( HDC hdc, // идентификатор контекста отображения POINT FAR* lppt, // указатель на массив структур POINT int cPoints); // размер массива структур POINT
Параметр hdc указывает контекст отображения, для которого требуется выполнить преобразования. В процессе преобразования используются атрибуты контекста, такие как смещение и масштаб осей координат.
Через параметр lppt передается указатель на массив структур POINT, в котором находятся преобразуемые координаты. Размер массива определяется значением параметра cPoints. Структура POINT определена в файле windows.h следующим образом:
typedef struct tagPOINT { int x; int y; } POINT;
После успешного выполнения преобразования функция возвращает значение TRUE. При возникновении ошибки возвращается FALSE.
Обратное преобразование (физических координат в логические ) выполняется функцией DPtoLP :
BOOL WINAPI DPtoLP( HDC hdc, // идентификатор контекста отображения POINT FAR* lppt, // указатель на массив структур POINT int cPoints); // размер массива структур POINT
Назначение параметров функции DPtoLP и возвращаемое ей значение аналогично назначению параметров и возвращаемому значению функции LPtoDP.
Есть еще две функции, предназначенные для преобразования координат - ClientToScreen и ScreenToClient.
Функция ClientToScreen выполняет преобразование координат в системе координат, связанной с внутренней областью окна, в экранные координаты:
void WINAPI ClientToScreen(HWND hwnd, POINT FAR* lppt);
Параметр hwnd содержит идентификатор окна, для которого выполняется преобразование.
Параметр lppt содержит указатель на структуру типа POINT, в которую перед вызовом функции следует записать преобразуемые координаты.
Функция ScreenToClient имеет аналогичные параметры, но выполняет обратное преобразование:
void WINAPI ScreenToClient(HWND hwnd, POINT FAR* lppt);
Приложение BMPINFO
После всего сказанного выше у вас могло сложиться впечатление, что процедура рисования изображений DIB значительно труднее, чем процедура рисования файлов DDB. В самом деле, для отображения содержимого bmp-файла вы должны считать его в память, проверить формат всех структур, при необходимости создать палитру и следить за ее изменениями со стороны других приложений. В момент рисования вам нужно подготовить значительное количество структур, указателей и параметров. Увы, для рисования изображений DIB в программном интерфейсе GDI операционной системы Windows версии 3.1 нет ничего более удобного, чем описанные нами функции.Большинство приложений, тем не менее, нуждается в рисовании изображений DIB. Для иллюстрации методов работы с такими изображениями мы подготовили приложение BMPINFO. Это приложение умеет загружать bmp-файлы любых "легальных" форматов Windows и Presentation Manager версии 1.x, и выводит соответствующую информацию из заголовков этих файлов, однако оно способно рисовать только некомпрессованные изображения в формате Windows. Как мы уже говорили, в большинстве случаев это как раз именно то, что нужно.
В меню "File" есть строки "Open", "Info..." и, конечно, "Exit". С помощью строки "Open" вы можете выбрать bmp-файл. Если это некомпрессованный файл в формате Windows, он рисуется в окне приложения (рис. 4.7).
Рис. 4.7. Главное окно приложения BMPINFO
Выбрав из меню "File" строку "Info...", вы можете просмотреть информацию о файле, такую, как размер файла и заголовка, формат файла, размер изображения в пикселах и т. д. (рис. 4.8).
Рис. 4.8. Информация о bmp-файле
Если выбранный bmp-файл имеет формат Presentation Manager, на экране появится диалоговая панель, аналогичная изображенной на рис. 4.8.
Основной файл исходных текстов приложения BMPINFO приведен в листинге 4.6.
Листинг 4.6. Файл bmpinfo/bmpinfo.cpp
// ---------------------------------------- // Приложение BMPINFO // Просмотр и анализ bmp-файлов в формате DIB // ----------------------------------------
#define STRICT #include
#include "dib.hpp" #include "bmpinfo.hpp"
// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
// Имя класса окна char const szClassName[] = "BmpInfoClass";
// Заголовок окна char const szWindowTitle[] = "Bitmap Information";
// Размеры внутренней области окна short cxClient, cyClient;
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;
// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);
// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;
// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));
// Подключаем меню wc.lpszMenuName = "APP_MENU";
wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH); wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0); }
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;
static HFILE hfDIBFile; static HDIB hDib; static HPALETTE hPal, hOldPal;
// Размер файла static DWORD dwFileSize;
switch (msg) { case WM_CREATE: { hfDIBFile = NULL; hDib = NULL; hPal = NULL; return 0; }
// При изменении размеров окна сохраняем // новые значения для ширины и высоты case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; }
// Рисование в окне case WM_PAINT: { // Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);
// Если DIB был загружен, и он в формате // некомпрессованного bmp-файла для Windows, // рисуем его if((hDib != NULL) && (DIBType(hDib) == WINRGB_DIB)) { // Если при загрузке была создана палитра, // выбираем ее if(hPal) { hOldPal = SelectPalette(hdc, hPal, FALSE); RealizePalette(hdc); }
// Рисуем DIB DIBPaint(hdc, 0, 0, hDib);
// Выбираем старую палитру if(hPal) { SelectPalette(hdc, hOldPal, FALSE); } }
// Для других форматов bmp-файлов выводим // информацию из заголовка файла else { if(hDib) DIBInfo(hDib, dwFileSize); }
// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }
// Обработка сообщений от меню case WM_COMMAND: { switch (wParam) { case CM_HELPABOUT: { MessageBox(hwnd, "Bitmap Information, v.1.0\n" "(C) Frolov A.V., 1994", "About BMPINFO", MB_OK | MB_ICONINFORMATION); return 0; }
// Загрузка bmp-файла case CM_FILEOPEN: { // Выбираем файл hfDIBFile = DIBSelectFile();
if(hfDIBFile != NULL) { // Читаем файл в память hDib = DIBReadFile(hfDIBFile, &dwFileSize);
// Если файл прочитан, создаем палитру на // базе таблицы цветов. Если таблицы цветов нет, // палитра не создается if((hDib != NULL) && (DIBType(hDib) == WINRGB_DIB)) { hPal = DIBCreatePalette(hDib); }
// Перерисовываем окно InvalidateRect(hwnd, NULL, TRUE); } return 0; }
// Выводим диалоговую панель с информацией из // заголовка bmp-файла case CM_FILEINFO: { if(hDib != NULL) DIBInfo(hDib, dwFileSize);
return 0; }
// Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd); return 0; }
default: return 0; } }
// Это сообщение приходит при изменении // системной палитры. Наше приложение в ответ // на это сообщение заново реализует свою логическую // палитру и при необходимости перерисовывает окно case WM_PALETTECHANGED: { // Если это не наше окно, передаем управление // обработчику сообщения WM_QUERYNEWPALETTE if (hwnd == (HWND) wParam) break; }
// В ответ на это сообщение приложение должно // реализовать свою логическую палитру и // обновить окно case WM_QUERYNEWPALETTE: { HDC hdc; HPALETTE hOldPal; int nChanged;
// Выбираем логическую палитру в // контекст отображения hdc = GetDC(hwnd);
// При обработке сообщения WM_QUERYNEWPALETTE // палитра выбирается для активного окна, // а при обработке сообщения WM_PALETTECHANGED - // для фонового hOldPal = SelectPalette(hdc, hPal, (msg == WM_QUERYNEWPALETTE) ? FALSE : TRUE);
// Реализуем логическую палитру и выбираем // ее в контекст отображения nChanged = RealizePalette(hdc); SelectPalette(hdc, hOldPal, TRUE);
// Освобождаем контекст отображения ReleaseDC(hwnd, hdc);
// Если были изменения палитры, // перерисовываем окно if(nChanged) InvalidateRect(hwnd, NULL, TRUE);
return nChanged; }
case WM_DESTROY: {
// Удаляем логическую палитру
if(hPal)
DeletePalette(hPal);
PostQuitMessage(0); return 0; }
default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
Обработчик сообщения WM_CREATE сбрасывает содержимое переменных, в которых находятся идентификатор открытого bmp-файла, идентификатор загруженного изображения DIB, а также идентификатор палитры:
hfDIBFile = NULL; hDib = NULL; hPal = NULL;
Обработчик сообщения WM_PAINT, рисующий изображение DIB, достаточно прост:
hdc = BeginPaint(hwnd, &ps); if((hDib != NULL) && (DIBType(hDib) == WINRGB_DIB)) { if(hPal) { hOldPal = SelectPalette(hdc, hPal, FALSE); RealizePalette(hdc); } DIBPaint(hdc, 0, 0, hDib); if(hPal) { SelectPalette(hdc, hOldPal, FALSE); } } else { if(hDib) DIBInfo(hDib, dwFileSize); } EndPaint(hwnd, &ps); return 0;
Если загружено изображение DIB, его идентификатор отличен от NULL. В этом случае вызывается функция DIBType, определенная в нашем приложении в файле dib.cpp (листинг 4.8). Эта функция выполняет все необходимые проверки полей структуры заголовка изображения и возвращает тип изображения. Для некомпрессованных изображений DIB в формате Windows возвращается значение WINRGB_DIB.
Если при загрузке bmp-файла выяснилось, что он содержит таблицу цветов, создается палитра, идентификатор которой записывается в переменную hPal. Если содержимое этой переменной отлично от NULL, перед рисованием обработчик сообщения WM_PAINT выбирает палитру в контекст отображения и реализует ее, вызывая функции SelectPalette и RealizePalette.
Далее вызывается функция DIBPaint, определенная в файле dib.cpp, которая рисует изображение DIB. В качестве параметров этой функции передается идентификатор контекста отображения, координаты (x,y) левого верхнего угла прямоугольной области, в которой нужно нарисовать изображение, и идентификатор загруженного изображения DIB.
После этого восстанавливается старая палитра (если были изменения палитры).
Если же было загружено изображение DIB в формате Presentation Manager, вызывается функция DIBInfo, которая выводит на экран диалоговую панель с параметрами этого изображения.
При выборе в меню "File" строки "Open" получает управление обработчик сообщения WM_COMMAND. Этот обработчик вызывает функцию DIBSelectFile, определенную в файле dib.cpp. Функция DIBSelectFile выводит на экран стандартную диалоговую панель "Open" и позволяет вам выбрать для загрузки любой файл. Идентификатор открытого файла записывается в переменную hfDIBFile.
Далее файл читается в память, для чего вызывается функция DIBReadFile, определенная в файле dib.cpp. Функция читает весь файл в заказанный ей глобальный блок памяти, причем перед возвратом управления отмечает этот блок как перемещаемый и возвращает идентификатор блока памяти. Дополнительно она записывает размер файла в байтах в переменную, адрес которой указан ей в качестве второго параметра.
После удачного чтения с помощью функции DIBType определяется тип файла. Если это некомпрессованный файл в формате Windows, вызывается функция DIBCreatePalette, которая проверяет наличие таблицы цветов и при необходимости создает логическую палитру, записывая ее идентификатор в переменную hPal.
Затем для перерисовки окна и отображения загруженного изображения вызывается функция InvalidateRect.
Обработчики сообщений об изменении системной палитры WM_PALETTECHANGED и WM_QUERYNEWPALETTE аналогичны использованным в приложении PALETTE, поэтому мы не будем их описывать еще раз.
Перед завершением работы приложения функция окна удаляет логическую палитру, если она была создана, вызывая макрокоманду DeletePalette.
Идентификаторы строк меню описаны в файле bmpihfo.hpp (листинг 4.7).
Листинг 4.7. Файл bmpinfo/bmpinfo.hpp
#define CM_HELPABOUT 301 #define CM_FILEOPEN 302 #define CM_FILEINFO 303 #define CM_FILEEXIT 304
Все функции, предназначенные для работы с bmp-файлами и изображениями DIB, загруженными в оперативную память, мы вынесли в отдельный файл dib.cpp (листинг4.8).
Листинг 4.8. Файл bmpinfo/dib.cpp
// ----------------------------------------------------- // Функции для работы с файлами в формате DIB // -----------------------------------------------------
#define STRICT #include
#include "dib.hpp"
// ------------------------------- // Функция DIBSelectFile // Выбор DIB-файла // -------------------------------
HFILE DIBSelectFile(void) { // Структура для выбора файла OPENFILENAME ofn;
// Буфер для записи пути к выбранному файлу char szFile[256];
// Буфер для записи имени выбранного файла char szFileTitle[256];
// Фильтр расширений имени файлов char szFilter[256] = "Bitmap Files\0*.bmp;*.dib;*.rle\0Any Files\0*.*\0";
// Идентификатор открываемого файла HFILE hf;
// Инициализация имени выбираемого файла // не нужна, поэтому создаем пустую строку szFile[0] = '\0';
// Записываем нулевые значения во все поля // структуры, которая будет использована для // выбора файла memset(&ofn, 0, sizeof(OPENFILENAME));
// Инициализируем нужные нам поля
// Размер структуры ofn.lStructSize = sizeof(OPENFILENAME);
// Идентификатор окна ofn.hwndOwner = NULL;
// Адрес строки фильтра ofn.lpstrFilter = szFilter;
// Номер позиции выбора ofn.nFilterIndex = 1;
// Адрес буфера для записи пути // выбранного файла ofn.lpstrFile = szFile;
// Размер буфера для записи пути // выбранного файла ofn.nMaxFile = sizeof(szFile);
// Адрес буфера для записи имени // выбранного файла ofn.lpstrFileTitle = szFileTitle;
// Размер буфера для записи имени // выбранного файла ofn.nMaxFileTitle = sizeof(szFileTitle);
// В качестве начального каталога для // поиска выбираем текущий каталог ofn.lpstrInitialDir = NULL;
// Определяем режимы выбора файла ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; // Выбираем входной файл if (GetOpenFileName(&ofn)) {
// Открываем выбранный файл hf = _lopen(ofn.lpstrFile, OF_READ);
// Возвращаем идентификатор файла return hf; } // При отказе от выбора возвращаем // нулевое значение else return 0; }
// ------------------------------- // Функция DIBReadFile // Чтение DIB-файла // -------------------------------
HDIB DIBReadFile(HFILE hfDIBFile, DWORD *dwFileSize) { // Идентификатор глобального блока // памяти, который будет использован для // чтения файла HDIB hDib;
HCURSOR hCursor; // идентификатор курсора
// Указатель на глобальный блок памяти LPDIB lpBuf;
// Курсор в виде песочных часов hCursor = SetCursor (LoadCursor(NULL, IDC_WAIT));
// Определяем размер файла. Для этого // устанавливаем текущую позицию на // конец файла *dwFileSize = _llseek(hfDIBFile, 0l, 2);
// Устанавливаем текущую позицию // на начало файла _llseek(hfDIBFile, 0l, 0);
// Заказываем глобальный блок памяти, // размер которого равен длине файла hDib = (HDIB)GlobalAlloc(GMEM_FIXED, *dwFileSize); lpBuf = (unsigned char huge *)GlobalLock(hDib);
// Если мало свободной памяти, // возвращаем признак ошибки if(lpBuf == NULL) return(NULL);
// Читаем файл в полученный блок памяти _hread(hfDIBFile, lpBuf, *dwFileSize);
// Восстанавливаем курсор SetCursor (hCursor);
// Расфиксируем память GlobalUnlock(hDib);
// Закрываем файл _lclose(hfDIBFile);
return hDib; }
// ------------------------------- // Функция DIBInfo // Вывод диалоговой панели с информацией // о DIB-файле // -------------------------------
BOOL DIBInfo(HDIB hDib, DWORD dwFileSize) { char szBuf[256], szBuf1[256]; DWORD biSize; LPBITMAPFILEHEADER lpDIBFileHeader; LPBITMAPINFOHEADER lpDIBInfoHeader; LPBITMAPCOREHEADER lpDIBCoreHeader; DWORD bfOffset; BOOL bWDIB; LPDIB lpDIBPtr; int nDIBType; WORD wNumColors;
// Определяем тип битового изображения nDIBType = DIBType(hDib);
if(!nDIBType) // если ошибка, выдаем сообщение { MessageBox(NULL, "Ошибка в формате DIB-файла", "Bitmap Info", MB_OK | MB_ICONHAND); return FALSE; }
// Фиксируем область памяти, в которую загружен DIB lpDIBPtr = (unsigned char huge *)GlobalLock(hDib); if(lpDIBPtr == NULL) return(FALSE);
lpDIBFileHeader = (LPBITMAPFILEHEADER)lpDIBPtr;
// Определяем смещение бит изображения // и размер заголовка bfOffset = lpDIBFileHeader->bfOffBits; biSize = (DWORD)(lpDIBPtr[sizeof(BITMAPFILEHEADER)]);
// Готовим текстовую строку для вывода wsprintf(szBuf, "Размер заголовка, байт:\t%ld\n", biSize); wsprintf(szBuf1, "Размер файла, байт: \t%ld\n", dwFileSize); lstrcat(szBuf, szBuf1); wsprintf(szBuf1, "Смещение изображения, байт:\t%ld\n", bfOffset); lstrcat(szBuf, szBuf1);
// В зависимости от формата DIB (PM или Windows) // выводим различную информацию из заголовка файла if((nDIBType == WINRGB_DIB) (nDIBType == WINRLE4_DIB) (nDIBType == WINRLE8_DIB)) { wsprintf(szBuf1, "\nBMP для Windows\n", biSize); lstrcat(szBuf, szBuf1); lpDIBInfoHeader = (LPBITMAPINFOHEADER)(lpDIBPtr + sizeof(BITMAPFILEHEADER));
wsprintf(szBuf1, "Размер:\t%ldx%ld\n", lpDIBInfoHeader->biWidth, lpDIBInfoHeader->biHeight); lstrcat(szBuf, szBuf1);
wsprintf(szBuf1, "Бит на пиксел:\t%d\n", lpDIBInfoHeader->biBitCount); lstrcat(szBuf, szBuf1);
wNumColors = DIBNumColors(lpDIBPtr); wsprintf(szBuf1, "Таблица цветов:\t%d\n", wNumColors); lstrcat(szBuf, szBuf1);
if(lpDIBInfoHeader->biCompression == BI_RGB) { lstrcat(szBuf, "Без компрессии\n"); } else if(lpDIBInfoHeader->biCompression == BI_RLE4) { lstrcat(szBuf, "Компрессия RLE4\n"); } else if(lpDIBInfoHeader->biCompression == BI_RLE8) { lstrcat(szBuf, "Компрессия RLE8\n"); } }
// Для файлов DIB в формате PM else { wsprintf(szBuf1, "\nBMP для Presentation Manager\n", biSize); lstrcat(szBuf, szBuf1); lpDIBCoreHeader = (LPBITMAPCOREHEADER)(lpDIBPtr + sizeof(BITMAPFILEHEADER));
wsprintf(szBuf1, "Размер:\t%dx%d\n", lpDIBCoreHeader->bcWidth, lpDIBCoreHeader->bcHeight); lstrcat(szBuf, szBuf1);
wsprintf(szBuf1, "Бит на пиксел:\t%d\n", lpDIBCoreHeader->bcBitCount); lstrcat(szBuf, szBuf1); }
MessageBox(NULL, (LPSTR)szBuf, "Bitmap Info", MB_OK | MB_ICONINFORMATION);
GlobalUnlock(hDib); return TRUE; }
// ------------------------------- // Функция DIBType // Определение и проверка формата DIB // -------------------------------
int DIBType(HDIB hDib) { LPBITMAPFILEHEADER lpDIBFileHeader; LPBITMAPINFOHEADER lpih; LPBITMAPCOREHEADER lpch;
DWORD biSize; LPDIB hDIBPtr; int nDIBType;
if(hDib == NULL) // Неправильный идентификатор DIB return(-2);
// Фиксируем память, в которой находится DIB hDIBPtr = (LPDIB)GlobalLock(hDib); if(hDIBPtr == NULL) return(-1);
lpDIBFileHeader = (LPBITMAPFILEHEADER)hDIBPtr;
// Проверяем тип файла if(lpDIBFileHeader->bfType != 0x4d42) { GlobalUnlock(hDib); return 0; }
// Проверяем размер заголовка biSize = (DWORD)(hDIBPtr[sizeof(BITMAPFILEHEADER)]);
if(biSize == sizeof(BITMAPINFOHEADER)) // 40 байт { // Это заголовок DIB в формате Windows lpih = (LPBITMAPINFOHEADER)(hDIBPtr + sizeof(BITMAPFILEHEADER));
// Проверяем основные поля заголовка DIB if((lpih->biPlanes == 1) && ((lpih->biBitCount == 1) (lpih->biBitCount == 4) (lpih->biBitCount == 8) (lpih->biBitCount == 24)) && ((lpih->biCompression == BI_RGB) (lpih->biCompression == BI_RLE4 && lpih->biBitCount == 4) (lpih->biCompression == BI_RLE8 && lpih->biBitCount == 8))) { // Определяем метод компрессии файла if(lpih->biCompression == BI_RGB) nDIBType = WINRGB_DIB; else if(lpih->biCompression == BI_RLE4) nDIBType = WINRLE4_DIB; else if(lpih->biCompression == BI_RLE8) nDIBType = WINRLE8_DIB; else nDIBType = 0; } else nDIBType = 0; }
else if(biSize == sizeof(BITMAPCOREHEADER)) // 12 байт { // Это заголовок DIB в формате Presentation Manager lpch = (LPBITMAPCOREHEADER)(hDIBPtr + sizeof(BITMAPFILEHEADER));
// Проверяем основные поля заголовка DIB if((lpch->bcPlanes == 1) && (lpch->bcBitCount == 1 lpch->bcBitCount == 4 lpch->bcBitCount == 8 lpch->bcBitCount == 24)) { nDIBType = PM_DIB; } else nDIBType = 0; }
else nDIBType = 0;
GlobalUnlock(hDib);
// Возвращаем тип файла или признак ошибки return nDIBType; }
// ------------------------------- // Функция DIBNumColors // Определение размера палитры // -------------------------------
WORD DIBNumColors(LPDIB lpDib) { DWORD dwColorUsed; LPBITMAPINFOHEADER lpih;
lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER));
// Количество цветов dwColorUsed = lpih->biClrUsed;
// Если используется палитра уменьшенного размера, // возвращаем нужный размер if(dwColorUsed) return((WORD)dwColorUsed);
// Если количество использованных цветов не указано, // вычисляем стандартный размер палитры исходя из // количества бит, определяющих цвет пиксела switch(lpih->biBitCount) { case 1: return 2; case 4: return 16; case 8: return 256; default: return 0; // палитра не используется } }
// ------------------------------- // Функция DIBHeight // Определение высоты DIB в пикселах // -------------------------------
WORD DIBHeight(LPDIB lpDib) { LPBITMAPINFOHEADER lpih;
lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER)); return lpih->biHeight; }
// ------------------------------- // Функция DIBWidth // Определение ширины DIB в пикселах // -------------------------------
WORD DIBWidth(LPDIB lpDib) { LPBITMAPINFOHEADER lpih;
lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER)); return lpih->biWidth; }
// ------------------------------- // Функция DIBFindBits // Определение адреса массива бит изображения // -------------------------------
LPSTR DIBFindBits(LPDIB lpDib) { LPBITMAPFILEHEADER lpfh; LPBITMAPINFOHEADER lpih;
lpfh = (LPBITMAPFILEHEADER)lpDib;
// Используем значение, указанное в заголовке // файла ( если оно не равно нулю) if(lpfh->bfOffBits) return((LPSTR)lpfh + lpfh->bfOffBits);
// Вычисляем адрес исходя из размеров заголовков и // таблицы цветов lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER));
return((LPSTR)lpih + lpih->biSize + (DWORD)(DIBNumColors(lpDib) * sizeof(RGBQUAD))); }
// ------------------------------- // Функция DIBPaint // Рисование DIB при помощи функции StretchDIBits // -------------------------------
BOOL DIBPaint(HDC hdc, int x, int y, HDIB hDib) { HBITMAP hbmp; HDC hMemDC; WORD wHeight, wWidth; LPDIB lpDib; LPBITMAPINFOHEADER lpih;
lpDib = (LPDIB)GlobalLock(hDib); if(lpDib == NULL) return(-1);
lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER));
// Определяем размеры DIB wHeight = lpih->biHeight; wWidth = lpih->biWidth;
// Рисуем DIB без масштабирования StretchDIBits(hdc, x, y, wWidth, wHeight, 0, 0, wWidth, wHeight, DIBFindBits(lpDib), (LPBITMAPINFO)lpih, DIB_RGB_COLORS, SRCCOPY);
GlobalUnlock(hDib);
return TRUE; }
// ------------------------------- // Функция DIBPaintBlt // Рисование DIB при помощи функции BitBlt // -------------------------------
BOOL DIBPaintBlt(HDC hdc, int x, int y, HDIB hDib) { HBITMAP hbmp; HDC hMemDC; WORD wHeight, wWidth; LPDIB lpDib; LPBITMAPINFOHEADER lpih;
lpDib = (LPDIB)GlobalLock(hDib); if(lpDib == NULL) return(-1);
lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER));
wHeight = lpih->biHeight; wWidth = lpih->biWidth;
// Создаем совместимое битовое изображение hbmp = CreateCompatibleBitmap(hdc, wWidth, wHeight);
// Создаем совместимый контекст памяти hMemDC = CreateCompatibleDC(hdc);
// Преобразуем DIB в DDB SetDIBits(hdc, hbmp, 0, wHeight, DIBFindBits(lpDib), (LPBITMAPINFO)lpih, DIB_RGB_COLORS);
// Выбираем DDB в контекст отображения hbmp = (HBITMAP)SelectObject(hMemDC, hbmp);
// Рисуем DDB BitBlt(hdc, x, y, wWidth, wHeight, hMemDC, 0, 0, SRCCOPY);
// Удаляем контекст памяти DeleteObject(SelectObject(hMemDC, hbmp)); DeleteDC(hMemDC);
GlobalUnlock(hDib); return TRUE; }
// ------------------------------- // Функция DIBCreatePalette // Создаем палитру на базе таблицы цветов DIB // -------------------------------
HPALETTE DIBCreatePalette(HDIB hDib) { LPLOGPALETTE lpPal; HPALETTE hPal = NULL; HANDLE hLogPal; int i, wNumColors; LPSTR lpbi; LPBITMAPINFO lpbmi;
if (!hDib) return NULL;
lpbi = (LPSTR)GlobalLock(hDib); lpbmi = (LPBITMAPINFO)(lpbi + sizeof(BITMAPFILEHEADER));
// Определяем размер таблицы цветов wNumColors = DIBNumColors(lpbi);
// Если в DIB есть таблица цветов, создаем палитру if (wNumColors) { // Заказываем память для палитры hLogPal = GlobalAlloc(GHND, sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * wNumColors);
if (!hLogPal) { GlobalUnlock(hDib); return NULL; }
// Получаем указатель на палитру lpPal = (LPLOGPALETTE)GlobalLock(hLogPal);
// Заполняем заголовок 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; }
// Создаем палитру hPal = CreatePalette(lpPal); if (!hPal) { GlobalUnlock(hLogPal); GlobalFree(hLogPal); return NULL; }
GlobalUnlock(hLogPal); GlobalFree(hLogPal); }
GlobalUnlock(hDib);
// Возвращаем идентификатор созданной палитры return hPal; }
Опишем функции, определенные в файле dib.cpp.
Приложение BMPLOGO
Приведем пример приложения BMPLOGO, которое демонстрирует работу с битовыми изображениями в формате DDB. Это приложение создает одно битовое изображение в памяти и рисует его в верхнем левом углу окна, а также (что на наш взгляд, самое интересное), рисует текстовую строку с оттенением (рис. 4.3).Рис. 4.3. Приложение BMPLOGO
Заметим, что для рисования слова "Bitmap" мы не пользовались какими-либо особенностями шрифтов True Type. Эффект теней был получен при помощи двухкратного вывода монохромного битового изображения со сдвигом, причем каждый раз мы использовали различные растровые операции.
Исходный текст главного модуля приложения представлен в листинге 4.1.
Листинг 4.1. Файл bmplogo/bmplogo.cpp
// ---------------------------------------- // Приложение BMPLOGO // Демонстрация различных способов рисования // битовых изображений DDB // ----------------------------------------
#define STRICT #include
// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); void DrawBitmapRop(HDC hDC, int x, int y, HBITMAP hBitmap, DWORD dwRop);
// Имя класса окна char const szClassName[] = "BmpLogoClass";
// Заголовок окна char const szWindowTitle[] = "Bitmap Logo";
// Размеры внутренней области окна short cxClient, cyClient;
// Идентификатор копии приложения HINSTANCE hInst;
// Битовое изображение 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 };
// Структура, описывающая битовое изображение BITMAP bmp = { 0, 64, 9, 8, 1, 1, NULL };
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;
// Сохраняем идентификатор приложения hInst = hInstance;
// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);
// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;
// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));
// Устанавливаем системный цвет для фона окна wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0); }
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;
switch (msg) { case WM_CREATE: { return 0; }
// При изменении размеров окна сохраняем // новые значения для ширины и высоты case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; }
// Рисование в окне case WM_PAINT: { HBITMAP bmLogo1, bmLogo2;
// Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);
// Загружаем изображение из ресурсов приложения bmLogo1 = LoadBitmap(hInst, "Logo1");
// Выводим изображение два раза со смещением, // используя разные коды растровых операций. // Это дает эффект тени DrawBitmapRop(hdc, 20, 20, bmLogo1, SRCAND); DrawBitmapRop(hdc, 15, 15, bmLogo1, MERGEPAINT);
// Завершаем формирование структуры bmp bmp.bmBits = (LPSTR)bBytes;
// Создаем битовое изображение из массива // данных, расположенных в памяти bmLogo2 = CreateBitmapIndirect(&bmp);
// Рисуем это изображение DrawBitmapRop(hdc, 0, 0, bmLogo2, SRCCOPY);
// Удаляем изображения DeleteBitmap(bmLogo1); DeleteBitmap(bmLogo2);
// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }
case WM_DESTROY: { PostQuitMessage(0); return 0; }
default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
Здесь для нас представляет интерес главным образом обработчик сообщения WM_PAINT, который и рисует битовые изображения.
Изображение слова "Bitmap" находится в ресурсах и имеет идентификатор Logo1. Для загрузки его в памяти вызывается функция LoadBitmap:
bmLogo1 = LoadBitmap(hInst, "Logo1");
Далее изображение выводится в первый раз, при этом используется растровая операция SRCAND:
DrawBitmapRop(hdc, 20, 20, bmLogo1, SRCAND);
Затем то же самое изображение выводится еще раз, но с небольшим смещением и с использованием другой растровой операции:
DrawBitmapRop(hdc, 15, 15, bmLogo1, MERGEPAINT);
Функция DrawBitmapRop аналогична описанной нами ранее функции DrawBitmap, однако она имеет дополнительный параметр, позволяющий выбрать растровую операцию.
Мы привели исходный текст этой функции в листинге 4.2.
Затем обработчик сообщения WM_PAINT создает в памяти и выводит на экран еще одно монохромное битовое изображение:
bmp.bmBits = (LPSTR)bBytes; bmLogo2 = CreateBitmapIndirect(&bmp); DrawBitmapRop(hdc, 0, 0, bmLogo2, SRCCOPY);
При создании изображения используются приемы, описанные в предыдущем разделе.
Перед возвратом управления обработчик удаляет оба созданных им битовых изображения:
DeleteBitmap(bmLogo1); DeleteBitmap(bmLogo2);
Листинг 4.2. Файл bmplogo/drawbmp.cpp
// ---------------------------------------- // Рисование изображения типа bitmap // с использованием различных растровых операций // ----------------------------------------
#define STRICT #include
void DrawBitmapRop(HDC hDC, int x, int y, HBITMAP hBitmap, DWORD dwRop) { 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, dwRop);
// Восстанавливаем контекст памяти SelectObject(hMemDC, hOldbm); }
// Удаляем контекст памяти DeleteDC(hMemDC); }
Файл описания ресурсов приложения (листинг 4.3) содержит только одну строку, которая ссылается на файл битового изображения logo1.bmp.
Листинг 4.3. Файл bmplogo/bmplogo.rc
Logo1 BITMAP logo1.bmp
В листинге 4.4 показано битовое изображение, содержащее слово "Bitmap".
Листинг 4.4. Файл bmplogo/logo1.bmp
Файл определения модуля для приложения BMPLOGO приведен в листинге 4.5.
Листинг 4.5. Файл bmplogo/bmplogo.def
; ============================= ; Файл определения модуля ; ============================= NAME BMPLOGO DESCRIPTION 'Приложение BMPLOGO, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Приложение DASHLINE
Приложение DASHLINE демонстрирует использование функции LineDDA для рисования пунктирных линий увеличенной толщины (рис. 2.28). Напомним, что вы не можете создать перо для рисования таких линий обычными средствами.Рис. 2.28. Пунктирные линии увеличенной толщины
Основной файл исходного текста приложения приведен в листинге 2.8.
Листинг 2.8. Файл dashline/dashline.cpp
// ---------------------------------------- // Приложение DASHLINE // Демонстрация использования функции LineDDA // ----------------------------------------
#define STRICT #include
// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
void CALLBACK _export LineProc(int xPos, int yPos, LPSTR lphdc);
// Имя класса окна char const szClassName[] = "DashLineClass";
// Заголовок окна char const szWindowTitle[] = "Dash Line";
// Идентификатор копии приложения HINSTANCE hInst;
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;
// Сохраняем идентификатор копии приложения hInst = hInstance;
// Создаем главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);
// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;
// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0); }
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static FARPROC lpfnLineProc; HPEN hpen, hpenOldPen;
switch (msg) { case WM_CREATE: { // Создаем переходник для функции LineProc lpfnLineProc = MakeProcInstance((FARPROC)LineProc, hInst); return 0; }
// Рисование в окне case WM_PAINT: { RECT rc;
// Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);
// Создаем перо толщиной 3 пиксела и выбираем // его в контекст отображения hpen = CreatePen(PS_SOLID, 3, RGB(0, 0, 0)); hpenOldPen = SelectPen(hdc, hpen);
// Рисуем несколько штриховых линий, // используя выбранное перо
LineDDA(50, 50, 300, 50, (LINEDDAPROC)lpfnLineProc, (DWORD)(LPSTR)&hdc);
LineDDA(50, 50, 300, 100, (LINEDDAPROC)lpfnLineProc, (DWORD)(LPSTR)&hdc);
LineDDA(50, 50, 50, 100, (LINEDDAPROC)lpfnLineProc, (DWORD)(LPSTR)&hdc);
LineDDA(50, 100, 300, 100, (LINEDDAPROC)lpfnLineProc, (DWORD)(LPSTR)&hdc);
LineDDA(300, 50, 300, 100, (LINEDDAPROC)lpfnLineProc, (DWORD)(LPSTR)&hdc);
// Выбираем старое перо и удаляем созданное SelectPen(hdc, hpenOldPen); DeletePen(hpen);
// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }
case WM_DESTROY: { // Освобождаем переходник функции LineProc FreeProcInstance(lpfnLineProc);
PostQuitMessage(0); return 0; }
default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
// -------------------------------------------------- // Функция LineProc вызывается для каждой точки линии // -------------------------------------------------- void CALLBACK _export LineProc(int xPos, int yPos, LPSTR lphdc) { // Счетчик точек static short cSpaces = 1;
// Для каждой первой точки устанавливаем текущую // позицию пера if(cSpaces == 1) { MoveToEx(*(HDC FAR*) lphdc, xPos, yPos, NULL); cSpaces++; }
// Для каждой десятой точки рисуем линию else if(cSpaces == 10) { LineTo(*(HDC FAR*) lphdc, xPos, yPos); cSpaces++; }
// Для каждой двадцатой точки устанавливаем // текущую позицию пера и сбрасываем счетчик else if(cSpaces == 20) { MoveToEx(*(HDC FAR*) lphdc, xPos, yPos, NULL); cSpaces = 1; } else cSpaces++; }
В процессе инициализации главного окна приложения при обработке сообщения WM_CREATE создается переходник для функции рисования, которая является функцией обратного вызова:
lpfnLineProc = MakeProcInstance ((FARPROC)LineProc, hInst);
Рисование линий выполняется обработчиком сообщения WM_PAINT.
Этот обработчик получает контекст отображения и создает перо толщиной 3 пиксела, с помощью которого он будет рисовать пунктирную линию. Перо выбирается в контекст отображения:
hpen = CreatePen(PS_SOLID, 3, RGB(0, 0, 0)); hpenOldPen = SelectPen(hdc, hpen);
Далее приложение рисует несколько пунктирных линий, вызывая функцию LineDDA:
LineDDA(50, 50, 300, 50, (LINEDDAPROC)lpfnLineProc, (DWORD)(LPSTR)&hdc);
Идентификатор контекста отображения, необходимый для рисования линии, передается через последний параметр функции.
В функции рисования есть статический счетчик cSpaces, с помощью которого организуется цикл рисования штриховых линий. Первоначальное значение этого счетчика равно 1.
Алгоритм рисования понятен из комментариев, которые есть в исходном тексте функции.Заметим, что для рисования линии вы можете использовать любые функции. Можно, например, нарисовать линию, состоящую из окружностей, эллипсов, прямоугольников или даже из битовых изображений.
Файл определения модуля приложения DASHLINE приведен в листинге 2.9.
Листинг 2.9. Файл dashline/dashline.def
; ============================= ; Файл определения модуля ; ============================= NAME DASHLINE DESCRIPTION 'Приложение DASHLINE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Приложение FONTVIEW
Для того чтобы вы могли быстро попробовать основные функции, предназначенные для выбора шрифта, мы подготовили приложение FONTVIEW.Меню "Font" приложения FONTVIEW позволяет вам выбрать шрифт двумя способами - вы можете указать семейство шрифта или выбрать конкретный шрифт при помощи диалоговой панели "Font".
Меню "Orientation" позволяет задать угол поворота текстовой строки, выводя ее с наклоном (рис. 5.3) или даже перевернутой "вверх ногами" (рис. 5.4).
Рис. 5.3. Вывод текста с наклоном
С помощью этого приложения вы можете убедиться в том, что повернуть можно только масштабируемые шрифты True Type.
Рис. 5.4. Вывод перевернутого текста
Исходный текст приложения приведен в листинге 5.1.
Листинг 5.1. Файл fontview/fontview.cpp
// ---------------------------------------- // Приложение FONTVIEW // Просмотр шрифтов // ----------------------------------------
#define STRICT #include
#include "fontview.hpp"
// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); BOOL GetFont(HWND hWnd, LOGFONT *lf, CHOOSEFONT *cf);
// Имя класса окна char const szClassName[] = "FontViewClass";
// Заголовок окна char const szWindowTitle[] = "Font Viewer";
// Размеры внутренней области окна short cxClient, cyClient;
// Идентификатор копии приложения HINSTANCE hInst;
// Строка для вывода char szChars[] = ": AaBbCcDdEeFfGg АаБбВвГгДдЕе"; char szBuf[256];
// Угол наклона строки при выводе int nOrientation = 0;
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;
hInst = hInstance;
// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);
// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;
// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна
// Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));
// Подключаем меню wc.lpszMenuName = "APP_MENU";
wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0); }
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static CHOOSEFONT cf; static LOGFONT lf; static HFONT hfont, hfOldFont;;
switch (msg) { // При изменении размеров окна сохраняем // новые значения для ширины и высоты case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; }
// Рисование в окне case WM_PAINT: { // Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);
// Устанавливаем угол наклона строки lf.lfOrientation = lf.lfEscapement = nOrientation;
// Создаем шрифт на базе заполненной // структуры LOGFONT hfont = CreateFontIndirect(&lf);
if(hfont) { // Выбираем шрифт в контекст отображения hfOldFont = SelectFont(hdc, hfont);
// Определяем название шрифта GetTextFace(hdc, 80, szBuf);
// Добавляем к нему текстовую строку lstrcat(szBuf, szChars);
// Устанавливаем цвет текста SetTextColor(hdc, cf.rgbColors);
// Выводим текст, пользуясь выбранным шрифтом TextOut(hdc, cxClient/2, cyClient/2, szBuf, lstrlen(szBuf));
// Выбираем старый шрифт SelectFont(hdc, hfOldFont);
// Удаляем созданный нами шрифт DeleteFont(hfont); }
// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }
// Обработка сообщений от меню case WM_COMMAND: { switch (wParam) { // Выбор шрифта при помощи диалоговой панели case CM_FONTSEL: { // Записываем во все поля структуры типа // LOGFONT нулевые значения memset(&lf, 0, sizeof(LOGFONT));
// Выбираем шрифт if(GetFont(hwnd, &lf, &cf)) { // Перерисовываем окно InvalidateRect(hwnd, NULL, TRUE); } return 0; }
// Выбираем шрифт, указывая семейство case CM_FDECOR: { memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_DECORATIVE; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FMODERN: { memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_MODERN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FROMAN: { memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_ROMAN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FSCRIPT: { memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_SCRIPT; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FSWISS: { memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_SWISS; InvalidateRect(hwnd, NULL, TRUE); return 0; }
// Выбираем угол поворота строки case CM_FONT00: { nOrientation = 0; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT30: { nOrientation = 300; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT45: { nOrientation = 450; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT90: { nOrientation = 900; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT180: { nOrientation = 1800; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT270: { nOrientation = 2700; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT360: { nOrientation = 3600; InvalidateRect(hwnd, NULL, TRUE); return 0; }
case CM_HELPABOUT: { MessageBox(hwnd, "Font Viewer, v.1.0\n" "(C) Frolov A.V., 1994", "About FONTVIEW", MB_OK | MB_ICONINFORMATION); return 0; }
// Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd); return 0; }
default: return 0; } }
case WM_DESTROY: { PostQuitMessage(0); return 0; }
default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
// ===================================== // Функция GetFont // =====================================
BOOL GetFont(HWND hWnd, LOGFONT *lf, CHOOSEFONT *cf) { LPSTR szFontStyle[LF_FACESIZE];
// Записываем нулевые значения во все поля // структуры, которая будет использована для // выбора шрифта memset(cf, 0, sizeof(CHOOSEFONT));
// Размер структуры cf->lStructSize = sizeof(CHOOSEFONT);
// Идентификатор окна cf->hwndOwner = hWnd;
// Указатель на структуру LOGFONT cf->lpLogFont = lf;
// Флаги, определяющие внешний вид диалоговой панели cf->Flags = CF_SCREENFONTS | CF_USESTYLE | CF_EFFECTS;
// Дополнительные данные cf->lCustData = 0L;
// Цвет текста cf->rgbColors = RGB(0,0,0);
// Адрес функции фильтра cf->lpfnHook = (FARPROC)NULL;
// Адрес шаблона диалоговой панели cf->lpTemplateName = (LPSTR)NULL;
// Идентификатор копии приложения cf->hInstance = hInst;
// Стиль шрифта cf->lpszStyle = (LPSTR)szFontStyle;
// Тип шрифта cf->nFontType = SCREEN_FONTTYPE;
// Ограничения на минимальный и максимальный // размер шрифта cf->nSizeMin = 0; cf->nSizeMax = 0;
// Вызываем функцию выбора шрифта return ChooseFont(cf); }
Текстовая строка, которая выводится на экран, находится в глобальном массиве szChars. В переменной nOrientation находится текущее значение угла поворота, которое задается при помощи меню "Orientation".
Обработчик сообщения WM_PAINT пользуется структурой lf типа LOGFONT, подготовленной при выборе шрифта.
Перед созданием шрифта обработчик устанавливает нужный угол наклона строки:
lf.lfOrientation = lf.lfEscapement = nOrientation;
Затем создается шрифт:
hfont = CreateFontIndirect(&lf);
Далее шрифт выбирается в контекст отображения, для чего используется макрокоманда SelectFont:
hfOldFont = SelectFont(hdc, hfont);
Идентификатор шрифта, который был выбран в контекст отображения раньше, сохраняется в переменной hfOldFont.
Затем обработчик вызывает функцию GetTextFace, которая копирует в буфер szBuf текстовую строку с названием шрифта, выбранного в контекст отображения. Эта строка затем дописывается ко строке szChars и выводится на экран.
Перед выводом устанавливается цвет текста, который берется из заполненной на этапе выбора шрифта структуры CHOOSEFONT:
SetTextColor(hdc, cf.rgbColors);
Для вывода текста мы используем функцию TextOut, которая была подробно описана в 11 томе "Библиотеки системного программиста".
Перед возвратом управления обработчик сообщения WM_PAINT выбирает в контекст отображения старый шрифт и удаляет созданный шрифт:
SelectFont(hdc, hfOldFont); DeleteFont(hfont);
Когда вы выберите строку "Fonts..." из меню "Font", получит управление обработчик сообщения WM_COMMAND. Он запишет во все поля структуры lf типа LOGFONT нулевые значения и вызовет функцию GetFont, определенную в нашем приложении. После этого он вызовет функцию InvalidateRect для перерисовки окна приложения.
Функция GetFont инициализирует нулевыми значениями структуру cf типа CHOOSEFONT, а затем заполняет в этой структуре нужные поля и вызывает функцию ChooseFont:
memset(cf, 0, sizeof(CHOOSEFONT)); cf->lStructSize = sizeof(CHOOSEFONT); cf->hwndOwner = hWnd; cf->lpLogFont = lf; cf->Flags = CF_SCREENFONTS | CF_USESTYLE | CF_EFFECTS; cf->lCustData = 0L; cf->rgbColors = RGB(0,0,0); cf->lpfnHook = (FARPROC)NULL; cf->lpTemplateName = (LPSTR)NULL; cf->hInstance = hInst; cf->lpszStyle = (LPSTR)szFontStyle; cf->nFontType = SCREEN_FONTTYPE; cf->nSizeMin = 0; cf->nSizeMax = 0; return ChooseFont(cf);
Если вы выбираете из меню "Font" одно из семейств шрифтов, структура lf инициализируется нулевыми значениями, а затем в ней устанавливается поле lfPitchAndFamily:
memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_DECORATIVE;
Затем вызывается функция InvalidateRect, что приводит к перерисовке окна приложения.
Установка угла наклона выполняется достаточно просто и заключается в изменении значения переменной nOrientation с последующей перерисовкой окна:
case CM_FONT30: { nOrientation = 300; InvalidateRect(hwnd, NULL, TRUE); return 0; }
Все константы, которые используются для работы с меню, описаны в файле fontview.hpp (листинг 5.2).
Листинг 5.2. Файл fontview/fontview.hpp
#define CM_HELPABOUT 301 #define CM_FONTSEL 302 #define CM_FILEEXIT 303
#define CM_FONT30 304 #define CM_FONT45 305 #define CM_FONT90 306 #define CM_FONT180 307 #define CM_FONT270 308 #define CM_FONT360 309 #define CM_FONT00 310
#define CM_FDECOR 311 #define CM_FMODERN 312 #define CM_FROMAN 313 #define CM_FSCRIPT 314 #define CM_FSWISS 315
Меню определено в файле ресурсов приложения (листинг 5.3).
Листинг 5.3. Файл fontview/fontview.rc
#include "fontview.hpp"
APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", CM_FILEEXIT END
POPUP "F&ont" BEGIN MENUITEM "FF_DECORATIVE", CM_FDECOR MENUITEM "FF_MODERN", CM_FMODERN MENUITEM "FF_ROMAN", CM_FROMAN MENUITEM "FF_SCRIPT", CM_FSCRIPT MENUITEM "FF_SWISS", CM_FSWISS MENUITEM SEPARATOR MENUITEM "&Select Font...",CM_FONTSEL END
POPUP "&Orientation" BEGIN MENUITEM "0", CM_FONT00 MENUITEM "30",CM_FONT30 MENUITEM "45",CM_FONT45 MENUITEM "90",CM_FONT90 MENUITEM "180",CM_FONT180 MENUITEM "270",CM_FONT270 MENUITEM "360",CM_FONT360 END
POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END
Файл определения модуля приложения FONTVIEW приведен в листинге 5.4.
Листинг 5.4. Файл fontview/fontview.def
; ============================= ; Файл определения модуля ; ============================= NAME FONTVIEW DESCRIPTION 'Приложение FONTVIEW, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Приложение GETCOLOR
Приложение GETCOLOR демонстрирует использование системных цветов и только что описанной функции ChooseColor. Оно также обрабатывает сообщение WM_SYSCOLORCHANGE.В окне приложения (рис. 3.5) во время обработки сообщения WM_PAINT рисуется прямоугольник (при помощи специально созданной кисти и пера). Для цвета фона и кисти используются системные цвета. Для выбора цвета пера следует щелкнуть мышью в окне приложения, при этом будет вызвана функция ChooseColor.
Рис. 3.5. Главное окно приложения GETCOLOR
Экспериментируя с приложением GETCOLOR, попробуйте изменить системные цвета при помощи приложения Control Panel. Вы увидите, что после выполнения изменений изменится цвет фона окна и цвет, которым закрашена внутренняя область прямоугольника (этот цвет совпадает с цветом заголовка окна). Цвет рамки не зависит от системных цветов и устанавливается при помощи диалоговой панели.
Обратите также внимание на то, что хотя диалоговая панель позволяет вам выбирать смешанные цвета, для пера всегда используются только чистые цвета.
Так как функция ChooseColor не поддерживает работу с цветовыми палитрами, в режиме со средним цветовым разрешением (256 цветов) вам будет доступен тот же самый набор цветов, что и в режиме с низким цветовым разрешением.
Если у вас есть такая возможность, запустите приложение GETCOLOR в режиме высокого цветового разрешения True Color. Убедитесь, что в этом случае вы можете выбрать для пера любой цвет. Все цвета будут чистыми, так как в этом режиме смешанные цвета не используются.
Исходный текст приложения приведен в листинге 3.1.
Листинг 3.1. Файл getcolor/getcolor.cpp
// ---------------------------------------- // Приложение GETCOLOR // Выбор цвета, работа с системными цветами // ----------------------------------------
#define STRICT #include
// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); BOOL GetPenColor(HWND, COLORREF *);
// Имя класса окна char const szClassName[] = "GetColorClass";
// Заголовок окна char const szWindowTitle[] = "Get Color";
// Идентификатор копии приложения HINSTANCE hInst;
// Идентификаторы кистей и перьев HBRUSH hbrush, hbrushOldBrush; HPEN hpen, hpenOldPen;
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;
// Сохраняем идентификатор копии приложения hInst = hInstance;
// Создаем главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);
// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;
// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));
// Устанавливаем системный цвет для фона окна wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0); }
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;
switch (msg) { case WM_CREATE: { // Создаем кисть и перо для рисования прямоугольника hbrush = CreateSolidBrush(GetSysColor(COLOR_ACTIVECAPTION)); hpen = CreatePen(PS_SOLID, 10, RGB(0,0,0)); return 0; }
case WM_SYSCOLORCHANGE: { // Если кисть была создана, удаляем ее // и затем создаем заново if(hbrush) { DeleteBrush(hbrush); hbrush = CreateSolidBrush(GetSysColor(COLOR_ACTIVECAPTION)); } return 0; }
// Рисование в окне case WM_PAINT: { RECT rc;
// Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);
// Выбираем новую кисть и новое перо hbrushOldBrush = SelectBrush(hdc, hbrush); hpenOldPen = SelectPen(hdc, hpen);
// Рисуем прямоугольник Rectangle(hdc, 10, 20, 300, 50);
// Выбираем старую кисть и старое перо SelectBrush(hdc, hbrushOldBrush); SelectPen(hdc, hpenOldPen);
// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }
case WM_LBUTTONDOWN: { COLORREF clrref;
// Выбираем новый цвет для пера if(GetPenColor(hwnd, &clrref)) { // Если выбрали новый цвет, удаляем перо, // а затем создаем новое DeletePen(hpen); hpen = CreatePen(PS_SOLID, 10, clrref);
// Перерисовываем окно InvalidateRect(hwnd, NULL, TRUE); } return 0; }
case WM_DESTROY: { // Удаляем созданные нами кисть и перо DeleteBrush(hbrush); DeletePen(hpen);
PostQuitMessage(0); return 0; }
default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
// --------------------------------------------------- // Функция GetPenColor // Выбор цвета пера // ---------------------------------------------------
BOOL GetPenColor(HWND hwnd, COLORREF *clrref) { CHOOSECOLOR cc; COLORREF aclrCust[16]; int i;
// Подготавливаем массив цветов // "Custom Colors" for (i = 0; i < 16; i++) aclrCust[i] = RGB(255, 255, 255);
// Записываем нулевые значения во все // неиспользуемые поля структуры CHOOSECOLOR memset(&cc, 0, sizeof(CHOOSECOLOR));
// Заполняем необходимые поля cc.lStructSize = sizeof(CHOOSECOLOR); cc.hwndOwner = hwnd; cc.rgbResult = RGB(0, 0, 0); cc.lpCustColors = aclrCust; cc.Flags = 0;
// Выбираем цвет и возвращаем результат if (ChooseColor(&cc)) { *clrref = cc.rgbResult; return TRUE; } else return FALSE; }
Обратите внимание, что в в файл исходного текста приложения включается файл commdlg.h:
#include
В этом файле описана функция ChoseColor, необходимые для нее константы и структура данных.
При регистрации класса окна для фона окна устанавливается системный цвет COLOR_APPWORKSPACE, соответствующей цвету окна MDI-приложений (примером такого приложения является Program Manager):
wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
Когда вы изменяете системные цвета, GDI автоматически перерисовывает фон окна, определенный в классе окна (если для фона окна выбран системный цвет).
В процессе создания окна обработчик сообщения WM_CREATE создает кисть и перо, с помощью которых будет нарисован прямоугольник:
hbrush = CreateSolidBrush(GetSysColor(COLOR_ACTIVECAPTION)); hpen = CreatePen(PS_SOLID, 10, RGB(0,0,0));
Перед завершением работы приложения обработчик сообщения WM_DESTROY удаляет созданные перо и кисть:
DeleteBrush(hbrush); DeletePen(hpen);
Задача обработчика сообщения WM_PAINT заключается в том, чтобы выбрать в контекст отображения перо и кисть, нарисовать прямоугольник, а затем выбрать в контекст отображения старые перо и кисть:
hbrushOldBrush = SelectBrush(hdc, hbrush); hpenOldPen = SelectPen(hdc, hpen); Rectangle(hdc, 10, 20, 300, 50); SelectBrush(hdc, hbrushOldBrush); SelectPen(hdc, hpenOldPen);
Что происходит, когда вы с помощью приложения Control Panel изменяете системные цвета?
Все окна верхнего уровня активных приложений получают сообщение WM_SYSCOLORCHANGE. В ответ на это сообщение наше приложение удаляет кисть и создает ее заново:
case WM_SYSCOLORCHANGE: { if(hbrush) { DeleteBrush(hbrush); hbrush = CreateSolidBrush(GetSysColor(COLOR_ACTIVECAPTION)); } return 0; }
Так как системные цвета изменились, константа COLOR_ACTIVECAPTION может теперь соответствовать другому цвету, поэтому в результате этой операции цвет кисти hbrush может измениться (но может и не измениться).
Заметьте, что приложение не выполняет никаких дополнительных действий, направленных на то, чтобы цвет фона окна изменялся в соответствии с изменением системных цветов - так как для цвета фона окна используется системный цвет, GDI выполняет все изменения самостоятельно.
Когда вы щелкаете левой клавишей мыши в окне приложения, обработчик сообщения WM_LBUTTONDOWN вызывает функцию GetPenColor, определенную в нашем приложении. Эта функция вызывает функцию ChooseColor, и, если вы выбрали с ее помощью цвет для пера, записывает этот цвет в переменную clrref. Затем обработчик сообщения от мыши удаляет существующее перо и создает новое, используя выбранный вами цвет. После этого он вызывает функцию InvalidateRect, что вызывает перерисовку окна приложения.
Функция GetPenColor подготавливает массив из 16 переменных типа COLORREF, содержащих белый цвет. Этот массив будет использован для инициализации цветов в области "Custom Colors" диалоговой панели выбора цветов.
Затем она инициализирует нужные поля структуры CHOOSECOLOR, записывая в остальные поля нулевые значения, после чего вызывает функцию ChooseColor.
Файл определения модуля для приложения GETCOLOR приведен в листинге 3.2.
Листинг 3.2. Файл getcolor/getcolor.def
; ============================= ; Файл определения модуля ; ============================= NAME GETCOLOR DESCRIPTION 'Приложение GETCOLOR, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Приложение LINER
Для демонстрации функций рисования графических изображений в различных режимах фона и с использованием различных растровых операций мы подготовили приложение LINER. Кроме всего прочего, в этом приложении мы получаем контекст отображения, пригодный для рисования во всем окне, в частности, в области заголовка окна (рис. 2.23).Рис. 2.23. Приложение LINER
Меню "Draw" предназначено для выбора фигур, отображаемых в окне приложения. Вы можете выбрать прямые линии (строка "Lines"), ломаную линию (строка "Polyline"), дугу эллипса ("Arc"), прямоугольники ("Rectangles"), многоугольники ("Polygon"), эллипс ("Ellipse"), окружность ("Circle"), сегмент эллипса ("Chord") или сектор эллипса ("Pie").
Запустите приложение LINER (готовый загрузочный модуль и исходные тексты приложения есть на дискете, которую можно купить вместе с книгой) и выберите из меню "Draw" строку "Rectangles". В главном окне будет нарисовано несколько прямоугольников, в том числе один прямоугольник будет нарисован по периметру внутренней области окна.
По умолчанию сразу после запуска приложения используется режим фона OPAQUE. Смените его на TRANSPARENT, выбрав соответствующую строку из меню "Background Mode". Режим фона изменится на прозрачный (рис. 2.24).
Рис. 2.24. Прозрачный режим фона
Посмотрите, как отражается изменение фона на рисовании других фигур, например, прямых линий (рис. 2.25).
Рис. 2.25. Рисование прямых линий различной толщины и стиля
Меню "ROP" позволит вам провести эксперименты с различными растровыми операциями.
Выбрав из меню "Draw" строку "Polygon", вы сможете посмотреть, как закрашиваются самопересекающиеся многоугольники в режимах ALTERNATE и WINDING (рис. 2.26).
Рис. 2.26. Режимы закрашивания многоугольников
Главный файл приложения LINER приведен в листинге 2.1.
Листинг 2.1. Файл liner/liner.cpp
// ---------------------------------------- // Приложение LINER // Демонстрация использования функций // рисования графических изображений // ----------------------------------------
#define STRICT #include
#include "liner.hpp"
// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); void DrawLines(HDC); void DrawPolyline(HDC); void DrawArc(HDC); void DrawRectangles(HDC); void DrawPolygon(HDC); void DrawEllipse(HDC); void DrawCircle(HDC); void DrawPie(HDC); void DrawChord(HDC);
// Имя класса окна char const szClassName[] = "LinerClass";
// Заголовок окна char const szWindowTitle[] = "Liner";
// Размеры внутренней области окна short cxClient, cyClient;
// Код выбранной строки меню "Draw" int nFigures = 0;
// Режим фона int nBkMode = OPAQUE;
// Код растровой операции int nROP2 = R2_COPYPEN;
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;
// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);
// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;
// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна
// Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));
// Подключаем меню wc.lpszMenuName = "APP_MENU";
wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH); wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0); }
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;
switch (msg) { // При изменении размеров окна сохраняем // новые значения для ширины и высоты case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; }
// Рисование в окне case WM_PAINT: { HBRUSH hbrush, hbrushOldBrush; int nCapHeight, nFrameHeight;
// Рисуем в области заголовка окна
// Получаем контекст отображения для // всего окна hdc = GetWindowDC(hwnd);
// Определяем высоту заголовка окна nCapHeight = GetSystemMetrics(SM_CYCAPTION);
// Определяем толщину рамки окна nFrameHeight = GetSystemMetrics(SM_CYFRAME);
// Создаем кисть зеленого цвета hbrush = CreateSolidBrush(RGB(0,0xff,0));
// Выбираем кисть в контекст отображения, сохраняя // идентификатор старой кисти hbrushOldBrush = SelectBrush(hdc, hbrush);
// Рисуем зеленый прямоугольник в левой части // заголовка окна Rectangle(hdc, 2*nCapHeight, nFrameHeight, 4*nCapHeight, nCapHeight);
// Создаем и выбираем зеленую кисть hbrush = CreateSolidBrush(RGB(0xff,0,0)); SelectBrush(hdc, hbrush);
// Вписываем в прямоугольник эллипс // красного цвета Ellipse(hdc, 2*nCapHeight, nFrameHeight, 4*nCapHeight, nCapHeight);
// Выбираем старую кисть SelectBrush(hdc, hbrushOldBrush);
// Освобождаем контекст отображения, // позволяющий рисовать во всем окне ReleaseDC(hwnd, hdc);
// Рисуем во внутренней области окна
// Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);
// Устанавливаем метрическую систему // координат с началом координат в // левом нижнем углу внутренней области // окна SetMapMode(hdc, MM_LOMETRIC); SetViewportOrg(hdc, 0, cyClient);
// Устанавливаем режим отображения и // растровую операцию SetBkMode(hdc, nBkMode); SetROP2(hdc, nROP2);
// В зависимости от содержимого переменной // nFigures вызываем одну из функций рисования if(nFigures == CM_LINES) { DrawLines(hdc); // прямые линии } else if(nFigures == CM_ARC) { DrawArc(hdc); // дуга окружности } else if(nFigures == CM_RECT) { DrawRectangles(hdc); // прямоугольники } else if(nFigures == CM_POLYGON) { DrawPolygon(hdc); // многоугольники } else if(nFigures == CM_ELLIPSE) { DrawEllipse(hdc); // эллипс } else if(nFigures == CM_CIRCLE) { DrawCircle(hdc); // окружность } else if(nFigures == CM_PIE) { DrawPie(hdc); // сектор эллипса } else if(nFigures == CM_CHORD) { DrawChord(hdc); // сегмент эллипса } else if(nFigures == CM_POLYLINE) { DrawPolyline(hdc); // ломаная линия }
// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }
// Обработка сообщений от меню case WM_COMMAND: { switch (wParam) { case CM_HELPABOUT: { MessageBox(hwnd, "Liner, v.1.0\n" "Drawing Graphics Demo\n" "(C) Frolov A.V., 1994", "About Liner", MB_OK | MB_ICONINFORMATION); return 0; }
// Режим непрозрачного фона case CM_OPAQUE: { // Записываем код режима nBkMode = OPAQUE;
// Перерисовываем окно приложения InvalidateRect(hwnd, NULL, TRUE); return 0; }
// Режим прозрачного фона case CM_TRANSPARENT: { nBkMode = TRANSPARENT; InvalidateRect(hwnd, NULL, TRUE); return 0; }
// Выбор растровой операции case CM_R2_BLACK: { // Записываем код растровой операции nROP2 = R2_BLACK;
// Перерисовываем окно приложения InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_NOTMERGEPEN: { nROP2 = R2_NOTMERGEPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_MASKNOTPEN: { nROP2 = R2_MASKNOTPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_NOTCOPYPEN: { nROP2 = R2_NOTCOPYPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_MASKPENNOT: { nROP2 = R2_MASKPENNOT; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_NOT: { nROP2 = R2_NOT; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_XORPEN: { nROP2 = R2_XORPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_NOTMASKPEN: { nROP2 = R2_NOTMASKPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_MASKPEN: { nROP2 = R2_MASKPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_NOTXORPEN: { nROP2 = R2_NOTXORPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_NOP: { nROP2 = R2_NOP; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_MERGENOTPEN: { nROP2 = R2_MERGENOTPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_COPYPEN: { nROP2 = R2_COPYPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_MERGEPENNOT: { nROP2 = R2_MERGEPENNOT; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_MERGEPEN: { nROP2 = R2_MERGEPEN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_R2_WHITE: { nROP2 = R2_WHITE; InvalidateRect(hwnd, NULL, TRUE); return 0; }
// Выбор из меню "Draw" case CM_LINES: { nFigures = CM_LINES; InvalidateRect(hwnd, NULL, TRUE); return 0; }
case CM_POLYLINE: { nFigures = CM_POLYLINE; InvalidateRect(hwnd, NULL, TRUE); return 0; }
case CM_ARC: { nFigures = CM_ARC; InvalidateRect(hwnd, NULL, TRUE); return 0; }
case CM_RECT: { nFigures = CM_RECT; InvalidateRect(hwnd, NULL, TRUE); return 0; }
case CM_POLYGON: { nFigures = CM_POLYGON; InvalidateRect(hwnd, NULL, TRUE); return 0; }
case CM_ELLIPSE: { nFigures = CM_ELLIPSE; InvalidateRect(hwnd, NULL, TRUE); return 0; }
case CM_CIRCLE: { nFigures = CM_CIRCLE; InvalidateRect(hwnd, NULL, TRUE); return 0; }
case CM_PIE: { nFigures = CM_PIE; InvalidateRect(hwnd, NULL, TRUE); return 0; }
case CM_CHORD: { nFigures = CM_CHORD; InvalidateRect(hwnd, NULL, TRUE); return 0; }
// Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd); return 0; }
default: return 0; } }
case WM_DESTROY: { PostQuitMessage(0); return 0; }
default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
Исходные тексты обильно снабжены комментариями, поэтому мы сделаем только самые необходимые пояснения.
При регистрации класса окна указываются стили CS_HREDRAW и CS_VREDRAW, в результате чего окно перерисовывается при изменении его высоты или ширины:
wc.style = CS_HREDRAW | CS_VREDRAW;
Функция окна содержит обработчик сообщения WM_SIZE, который сохраняет размеры внутренней области окна в глобальных переменных cxClient и cyClient.
Обработчик сообщения WM_PAINT два раза получает контекст отображения. Вначале он получает контекст отображения, позволяющий рисовать во всем окне приложения:
hdc = GetWindowDC(hwnd);
Используя этот контекст отображения, обработчик сообщения WM_PAINT рисует в области заголовка окна эллипс, вписанный в прямоугольник.
После этого указанный контекст отображения освобождается:
ReleaseDC(hwnd, hdc);
Далее обработчик сообщения WM_PAINT получает общий контекст отображения, связанный с внутренней областью окна, вызывая функцию BeginPaint.
В этом контексте отображения устанавливается метрический режим отображения, причем в качестве единицы длины используется одна десятая миллиметра. Начало осей координат сдвигается в левый нижний угол:
SetMapMode(hdc, MM_LOMETRIC); SetViewportOrg(hdc, 0, cyClient);
Далее в соответствии с содержимым глобальной переменной nBkMode устанавливается режим фона:
SetBkMode(hdc, nBkMode);
Первоначально в этой переменной находится значение OPAQUE. Вы можете изменить содержимое переменной nBkMode с помощью меню "Background Mode" на TRANSPARENT.
Аналогично устанавливается растровая операция, используемая для рисования:
SetROP2(hdc, nROP2);
Далее обработчик сообщения WM_PAINT анализирует содержимое переменной nFigures, которое сразу после запуска приложения равно 0. Когда вы выбираете строку из меню "Draw", в эту переменную записывается идентификатор выбранной из меню строки. При обработке сообщения WM_PAINT в зависимости от содержимого переменной вызывается одна из нескольких функций, рисующих различные фигуры. Исходные тексты этих функций вынесены в отдельный файл (листинг 2.2).
Перед возвратом управления контекст отображения освобождается функцией EndPaint.
Обработчик сообщения WM_COMMAND предназначен для меню. После каждого изменения режима или кода растровой операции вызывается функция InvalidateRect, в результате чего в очередь сообщений записывается сообщение WM_PAINT:
case CM_TRANSPARENT: { nBkMode = TRANSPARENT; InvalidateRect(hwnd, NULL, TRUE); return 0; }
Функции, рисующие изображения, мы вынесли в отдельный файл (листинг 2.2).
Листинг 2.2. Файл liner/drawfn.cpp
// ---------------------------------------- // Функции для приложения LINER // ---------------------------------------- #define STRICT #include
// Прототипы функций void DrawLines(HDC); void DrawPolyline(HDC); void DrawArc(HDC); void DrawRectangles(HDC); void DrawPolygon(HDC); void DrawEllipse(HDC); void DrawCircle(HDC); void DrawPie(HDC); void DrawChord(HDC);
// Размеры внутренней области окна extern short cxClient, cyClient;
// ------------------------------------------- // DrawLines // Рисование линий различной толщины и стиля // ------------------------------------------- void DrawLines(HDC hdc) { HPEN hpenW10, hpenW50; HPEN hpenDot, hpenDash, hpenDashDot, hpenOldPen;
// Создаем несколько перьев: два пера толщиной // 1 мм и 5 мм, пунктирное, штриховое и // штрих-пунктирное hpenW10 = CreatePen(PS_SOLID, 10, RGB(0,0,0)); hpenW50 = CreatePen(PS_SOLID, 50, RGB(0,0,0)); hpenDot = CreatePen(PS_DOT, 0, RGB(0,0,0)); hpenDash = CreatePen(PS_DASH, 0, RGB(0,0,0)); hpenDashDot = CreatePen(PS_DASHDOT, 0, RGB(0,0,0));
// Рисуем тонкую линию пером, выбранным в контекст // отображения по умолчанию, и подписываем эту линию MoveToEx(hdc, 100, 100, NULL); LineTo(hdc, 600, 100); TextOut(hdc, 700, 100, "PS_SOLID, 1 pixel", 17);
// Выбираем перо толщиной 1 мм hpenOldPen = SelectPen(hdc, hpenW10);
// Рисуем линию выбранным пером MoveToEx(hdc, 100, 200, NULL); LineTo(hdc, 600, 200); TextOut(hdc, 700, 200, "PS_SOLID, 1 mm", 14);
// Перебираем остальные перья
SelectPen(hdc, hpenW50);
MoveToEx(hdc, 100, 300, NULL); LineTo(hdc, 600, 300); TextOut(hdc, 700, 300, "PS_SOLID, 5 mm", 14);
SelectPen(hdc, hpenDot);
MoveToEx(hdc, 100, 400, NULL); LineTo(hdc, 600, 400); TextOut(hdc, 700, 400, "PS_DOT", 6);
SelectPen(hdc, hpenDash);
MoveToEx(hdc, 100, 500, NULL); LineTo(hdc, 600, 500); TextOut(hdc, 700, 500, "PS_DASH", 7);
SelectPen(hdc, hpenDashDot);
MoveToEx(hdc, 100, 600, NULL); LineTo(hdc, 600, 600); TextOut(hdc, 700, 600, "PS_DASHDOT", 10);
// Выбираем старое перо SelectPen(hdc, hpenOldPen);
// Удаляем созданные нами перья DeletePen(hpenW10); DeletePen(hpenW50); DeletePen(hpenDot); DeletePen(hpenDash); DeletePen(hpenDashDot); }
// ------------------------------------------- // DrawPolyline // Рисование ломаной линии // ------------------------------------------- void DrawPolyline(HDC hdc) { // Массив координат точек излома POINT ptPoints[] = { {10, 10}, {100, 310}, {40, 300}, {300, 15}, {135, 340}, {113, 125}, {250, 137}, {300, 300} };
// Рисуем ломаную линию Polyline(hdc, ptPoints, sizeof ptPoints / sizeof ptPoints[0]); }
// ------------------------------------------- // DrawArc // Рисование дуги эллипса // ------------------------------------------- void DrawArc(HDC hdc) { HPEN hpenW10, hpenOldPen;
// Создаем перо толщиной 1 мм и выбираем его hpenW10 = CreatePen(PS_SOLID, 10, RGB(0,0,0)); hpenOldPen = SelectPen(hdc, hpenW10);
// Рисуем дугу Arc(hdc, 100, 600, // верхний левый угол прямоугольника 800, 100, // нижний правый угол прямоугольника 650, 650, // начало 750, 0); // конец
// Выбираем старое перо SelectPen(hdc, hpenOldPen);
// Удаляем созданное перо DeletePen(hpenW10); }
// ------------------------------------------- // DrawRectangles // Рисование прямоугольников // ------------------------------------------- void DrawRectangles(HDC hdc) { HPEN hpenW10, hpenOldPen; HBRUSH hbrush, hbrushOldBrush; POINT pt[2]; RECT rc = {350, 500, 500, 400};
// Рисуем прямоугольник вокруг внутренней области окна // Так как установлен метрический режим отображения, // а размеры окна, передаваемые вместе с сообщением // WM_SIZE, выражены в пикселах, выполняем // преобразование физических координат в логические pt[0].x = 0; pt[0].y = cyClient; pt[1].x = cxClient; pt[1].y = 0; DPtoLP(hdc, pt, 2);
// Создаем перо толщиной 1 мм и выбираем его hpenW10 = CreatePen(PS_SOLID, 10, RGB(0,0,0)); hpenOldPen = SelectPen(hdc, hpenW10);
// Рисуем прямоугольник Rectangle(hdc, pt[0].x, pt[0].y, pt[1].x, pt[1].y);
// Выбираем серую кисть hbrush = GetStockBrush(GRAY_BRUSH); hbrushOldBrush = SelectBrush(hdc, hbrush);
// Рисуем прямоугольник, закрашенный серым цветом Rectangle(hdc, 100, 500, 300, 50);
// Создаем и выбираем кисть для штриховки hbrush = CreateHatchBrush(HS_DIAGCROSS, RGB(0,0,0)); SelectBrush(hdc, hbrush);
// Рисуем заштрихованный прямоугольник Rectangle(hdc, 50, 300, 500, 100);
// Выбираем старое перо и кисть SelectPen(hdc, hpenOldPen); SelectBrush(hdc, hbrushOldBrush);
// Заштриховываем прямоугольную область кистью // hbrush, которая НЕ ВЫБРАНА в контекст FillRect(hdc, &rc, hbrush);
// Рисуем прямоугольник со скругленными углами RoundRect(hdc, 550, 200, 800, 100, 50, 50);
// Удаляем созданные нами перо и кисть DeletePen(hpenW10); DeleteBrush(hbrush);}
// ------------------------------------------- // DrawPolygon // Рисование многоугольника // ------------------------------------------- void DrawPolygon(HDC hdc) { HBRUSH hbrush, hbrushOldBrush; int nOldPolyFillMode;
// Координаты вершин первого многоугольника POINT ptPoints1[] = { {10, 10}, {100, 310}, {40, 300}, {300, 15}, {135, 340}, {113, 125}, {250, 137}, {300, 300} };
// Координаты вершин второго многоугольника POINT ptPoints2[] = { {310, 10}, {400, 310}, {340, 300}, {600, 15}, {435, 340}, {413, 125}, {550, 137}, {600, 300} };
// Выбираем встроенную серую кисть hbrush = GetStockBrush(GRAY_BRUSH); hbrushOldBrush = SelectBrush(hdc, hbrush);
// Рисуем первый многоугольник в режиме // заполнения ALTERNATE, установленном // по умолчанию Polygon(hdc, ptPoints1, sizeof ptPoints1 / sizeof ptPoints1[0]);
// Устанавливаем режим заполнения WINDING nOldPolyFillMode = SetPolyFillMode(hdc, WINDING);
// Рисуем второй многоугольник Polygon(hdc, ptPoints2, sizeof ptPoints2 / sizeof ptPoints2[0]);
// Восстанавливаем старый режим заполнения SetPolyFillMode(hdc, nOldPolyFillMode); SelectBrush(hdc, hbrushOldBrush); }
// ------------------------------------------- // DrawEllipse // Рисование эллипса // ------------------------------------------- void DrawEllipse(HDC hdc) { POINT pt[2];
// Эллипс будет вписан во внутреннюю область // окна, поэтому определяем координаты углов // в текущей (метрической) системе координат, // выполняя преобразование pt[0].x = 0; pt[0].y = cyClient; pt[1].x = cxClient; pt[1].y = 0; DPtoLP(hdc, pt, 2);
// Рисуем эллипс Ellipse(hdc, pt[0].x, pt[0].y, pt[1].x, pt[1].y); }
// ------------------------------------------- // DrawCircle // Рисование окружности // ------------------------------------------- void DrawCircle(HDC hdc) { // Рисуем эллипс, вписанный в квадрат Ellipse(hdc, 100, 600, 600, 100); }
// ------------------------------------------- // DrawPie // Рисование сектора круга // ------------------------------------------- void DrawPie(HDC hdc) { HPEN hpenW10, hpenOldPen;
// Создаем перо и выбираем его hpenW10 = CreatePen(PS_SOLID, 10, RGB(0,0,0)); hpenOldPen = SelectPen(hdc, hpenW10);
// Рисуем сектор круга Pie(hdc, 100, 600, 800, 100, 650, 650, 750, 0);
// Выбираем старое перо и удаляем созданное SelectPen(hdc, hpenOldPen); DeletePen(hpenW10); }
// ------------------------------------------- // DrawChord // Рисование сегмента круга // ------------------------------------------- void DrawChord(HDC hdc) { HPEN hpenW10, hpenOldPen;
hpenW10 = CreatePen(PS_SOLID, 10, RGB(0,0,0)); hpenOldPen = SelectPen(hdc, hpenW10);
// Рисуем сегмент круга Chord(hdc, 100, 600, 800, 100, 650, 650, 750, 0);
SelectPen(hdc, hpenOldPen); DeletePen(hpenW10); }
Символические имена констант, используемые для идентификации строк меню, описаны в файле liner.hpp (листинг 2.3).
Листинг 2.3. Файл liner/liner.hpp
#define CM_HELPABOUT 301 #define CM_OPAQUE 302 #define CM_TRANSPARENT 303 #define CM_FILEEXIT 304 #define CM_POLYGON 305 #define CM_PIE 306 #define CM_CHORD 307 #define CM_ELLIPSE 308 #define CM_RECT 309 #define CM_ARC 310 #define CM_LINES 311 #define CM_POLYLINE 312 #define CM_CIRCLE 313
#define CM_R2_BLACK 201 #define CM_R2_NOTMERGEPEN 202 #define CM_R2_MASKNOTPEN 203 #define CM_R2_NOTCOPYPEN 204 #define CM_R2_MASKPENNOT 205 #define CM_R2_NOT 206 #define CM_R2_XORPEN 207 #define CM_R2_NOTMASKPEN 208 #define CM_R2_MASKPEN 209 #define CM_R2_NOTXORPEN 210 #define CM_R2_NOP 211 #define CM_R2_MERGENOTPEN 212 #define CM_R2_COPYPEN 213 #define CM_R2_MERGEPENNOT 214 #define CM_R2_MERGEPEN 215 #define CM_R2_WHITE 216
Меню приложения определено в файле ресурсов liner.rc (листинг 2.4).
Листинг 2.4. Файл liner/liner.rc
#include "liner.hpp"
APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", CM_FILEEXIT END
POPUP "&Draw" BEGIN MENUITEM "&Lines", CM_LINES MENUITEM "&Polyline", CM_POLYLINE MENUITEM "&Arc", CM_ARC MENUITEM "&Rectangles", CM_RECT MENUITEM "P&opygon", CM_POLYGON MENUITEM "&Ellipse", CM_ELLIPSE MENUITEM "C&ircle", CM_CIRCLE MENUITEM "&Chord", CM_CHORD MENUITEM "&Pie", CM_PIE END
POPUP "&Background Mode" BEGIN MENUITEM "&Opaque", CM_OPAQUE MENUITEM "&Transparent", CM_TRANSPARENT END
POPUP "&ROP" BEGIN MENUITEM "R2_BLACK", CM_R2_BLACK MENUITEM "R2_NOTMERGEPEN", CM_R2_NOTMERGEPEN MENUITEM "R2_MASKNOTPEN", CM_R2_MASKNOTPEN MENUITEM "R2_NOTCOPYPEN", CM_R2_NOTCOPYPEN MENUITEM "R2_MASKPENNOT", CM_R2_MASKPENNOT MENUITEM "R2_NOT", CM_R2_NOT MENUITEM "R2_XORPEN", CM_R2_XORPEN MENUITEM "R2_NOTMASKPEN", CM_R2_NOTMASKPEN MENUITEM "R2_MASKPEN", CM_R2_MASKPEN MENUITEM "R2_NOTXORPEN", CM_R2_NOTXORPEN MENUITEM "R2_NOP", CM_R2_NOP MENUITEM "R2_MERGENOTPEN", CM_R2_MERGENOTPEN MENUITEM "R2_COPYPEN", CM_R2_COPYPEN MENUITEM "R2_MERGEPENNOT", CM_R2_MERGEPENNOT MENUITEM "R2_MERGEPEN", CM_R2_MERGEPEN MENUITEM "R2_WHITE", CM_R2_WHITE END
POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END
Файл liner. def используется для определения модуля приложения LINER (листинг 2.5).
Листинг 2.5. Файл liner/liner.def
; ============================= ; Файл определения модуля ; ============================= NAME LINER DESCRIPTION 'Приложение LINER, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Приложение PALETTE
Для демонстрации методов работы с цветовыми палитрами мы подготовили приложение PALETTE. Это приложение создает палитру из 256 градаций серого цвета и рисует в своем окне вертикальные прямоугольники с использованием палитры.На рис. 3.8 изображен внешний вид окна приложения PALETTE. Из-за ограниченных возможностей типографии вместо плавных переходов оттенков серого на этом рисунке использованы смешанные цвета. Кстати, если вы запустите приложение PALETTE в стандартном режиме VGA с использованием 16 цветов, внешний вид окна на экране монитора будет полностью соответствовать рис. 3.8.
Рис. 3.8. Окно приложения PALETTE
Перед началом работы приложение выводит на экран диалоговую панель, в которой сообщает о том, используется ли в текущем видеорежиме механизм цветовых палитр и каков размер системной палитры.
Вы можете попробовать работу этого приложения в различных видеорежимах, убедившись в том, что оно работает правильно во всех видеорежимах, кроме стандартного режима VGA. В последнем случае вместо оттенков серого цвета используются смешанные цвета.
В режиме среднего цветового разрешения используется механизм цветовых палитр. Если же вы запустите приложение в режиме высокого цветового разрешения, несмотря на то, что палитры не используются, приложение по-прежнему будет рисовать правильное изображение.
На дискете, которая продается вместе с книгой, в каталоге palette есть несколько bmp-файлов, содержащих 256-цветные изображения. Загружая эти изображения в графический редактор Paint Brush, вы сможете изменить системную палитру и посмотреть, как это отразится на окне приложения PALETTE.
Запустите приложения PALETTE и Paint Brush. Измените размеры окон этих приложений так, чтобы окна были видны одновременно. Затем загрузите в приложение Paint Brush изображение sky.bmp. Вы увидите, что внешний вид окна приложения PALETTE изменился. Переключитесь на приложение PALETTE. Внешний вид его окна восстановится, но качество изображения в окне Paint Brush ухудшится.
Описанное явление возникает из за того, что размер системной палитры цветов равен 256.
Когда вы загружаете в Paint Brush файл sky.bmp, системная палитра изменяется так, чтобы наилучшим образом соответствовать цветам изображения. Если при этом было запущено приложение PALETTE (создающее свою собственную палитру из 256 градаций серого цвета), и оно находится в фоновом режиме, для него используется фоновый алгоритм реализации палитры.
В первую очередь будут удовлетворен запрос на системную палитру для активного приложения (Paint Brush), а затем уже фонового (PALETTE).
Проследите, как изменяется внешний вид окна приложения PALETTE при загрузке других bmp-файлов, имеющихся на дискете в каталоге palette. Обратите внимание, что при загрузке файла gray.bmp качество изображения в окне PALETTE практически не изменяется, так как изображение gray.bmp содержит только оттенки серого цвета.
Обратимся теперь к исходному тексту приложения PALETTE (листинг 3.3).
Листинг 3.3. Файл palette/palette.cpp
// ---------------------------------------- // Приложение PALETTE // Демонстрация использования цветовых палитр // ----------------------------------------
#define STRICT #include
// Размер создаваемой логической палитры #define PALETTESIZE 256
// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); void PaletteInfo(void);
// Имя класса окна char const szClassName[] = "PaletteClass";
// Заголовок окна char const szWindowTitle[] = "Palette Demo";
// Размеры внутренней области окна short cxClient, cyClient;
// Идентификаторы палитр HPALETTE hPal, hOldPalette;
// Указатель на логическую палитру NPLOGPALETTE pLogPal;
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
// Инициализируем приложение if(!hPrevInstance) if(!InitApp(hInstance)) return FALSE;
// Выводим сведения о палитре PaletteInfo();
// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);
// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;
// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0); }
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;
switch (msg) { case WM_CREATE: { int i;
// Получаем память для палитры pLogPal = (NPLOGPALETTE) LocalAlloc(LMEM_FIXED, (sizeof (LOGPALETTE) + (sizeof (PALETTEENTRY) * (PALETTESIZE))));
// Заполняем заголовок палитры pLogPal->palVersion = 0x300; pLogPal->palNumEntries = PALETTESIZE;
// Заполняем палитру градациями // серого цвета for (i=0; i < 256; i++) { pLogPal->palPalEntry[i].peRed = pLogPal->palPalEntry[i].peGreen = pLogPal->palPalEntry[i].peBlue = i; pLogPal->palPalEntry[i].peFlags = 0; }
// Создаем логическую палитру hPal = CreatePalette((LPLOGPALETTE) pLogPal); return 0; }
// При изменении размеров окна сохраняем // новые значения для ширины и высоты case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; }
// Рисование в окне case WM_PAINT: { RECT rc; int i, nWidth; HBRUSH hBrush;
// Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);
// Выбираем палитру hOldPalette = SelectPalette(hdc, hPal, FALSE);
// Реализуем логическую палитру RealizePalette(hdc);
// Координаты первого прямоугольника nWidth = 2; rc.left = rc.top = 0; rc.right = nWidth; rc.bottom = cyClient;
// Рисуем 256 прямоугольников во внутренней // области окна for (i=0; i < 256; i++) { // Выбираем кисть. Вы можете использовать одну из // двух приведенных ниже строк, переместив символ // комментария
// Косвенная ссылка на палитру // hBrush = CreateSolidBrush(PALETTERGB(i, i, i));
// Прямая ссылка на палитру hBrush = CreateSolidBrush(PALETTEINDEX(i));
// Закрашиваем прямоугольную область FillRect(hdc, &rc, hBrush);
// Координаты следующего прямоугольника rc.left = rc.right; rc.right += nWidth;
// Удаляем кисть DeleteBrush(hBrush); }
// Выбираем старую палитру SelectPalette(hdc, hOldPalette, TRUE);
// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }
// Это сообщение приходит при изменении // системной палитры. Наше приложение в ответ // на это сообщение заново реализует свою логическую // палитру и при необходимости перерисовывает окно case WM_PALETTECHANGED: { // Если это не наше окно, передаем управление // обработчику сообщения WM_QUERYNEWPALETTE if (hwnd == (HWND) wParam) break; }
// В ответ на это сообщение приложение должно // реализовать свою логическую палитру и // обновить окно case WM_QUERYNEWPALETTE: { HDC hdc; HPALETTE hOldPal; int nChanged;
// Выбираем логическую палитру в // контекст отображения hdc = GetDC(hwnd);
// При обработке сообщения WM_QUERYNEWPALETTE // палитра выбирается для активного окна, // а при обработке сообщения WM_PALETTECHANGED - // для фонового hOldPal = SelectPalette(hdc, hPal, (msg == WM_QUERYNEWPALETTE) ? FALSE : TRUE);
// Реализуем логическую палитру и выбираем // ее в контекст отображения nChanged = RealizePalette(hdc); SelectPalette(hdc, hOldPal, TRUE);
// Освобождаем контекст отображения ReleaseDC(hwnd, hdc);
// Если были изменения палитры, // перерисовываем окно if(nChanged) InvalidateRect(hwnd, NULL, TRUE);
return nChanged; }
case WM_DESTROY: { // Удаляем созданную нами // логическую палитру DeletePalette(hPal);
// Освобождаем память, выделенную для палитры LocalFree(pLogPal);
PostQuitMessage(0); return 0; }
default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
// -------------------------------------------------------- // Функция PaletteInfo // Вывод некоторых сведений о палитре // --------------------------------------------------------
void PaletteInfo(void) { HDC hdc; int iPalSize, iRasterCaps; char szMsg[256]; char szPal[20];
// Получаем контекст отображения для // всего экрана hdc = GetDC(NULL);
// Определяем размер палитры и слово, // описывающее возможности драйвера // видеоконтроллера как растрового устройства iPalSize = GetDeviceCaps(hdc, SIZEPALETTE); iRasterCaps = GetDeviceCaps(hdc, RASTERCAPS);
// Проверяем, используется ли механизм палитр if(iRasterCaps & RC_PALETTE) { iRasterCaps = TRUE; lstrcpy(szPal, "используются"); } else { iRasterCaps = FALSE; lstrcpy(szPal, "не используются"); }
// Освобождаем контекст отображения ReleaseDC(NULL, hdc);
// Выводим сведения о палитре wsprintf(szMsg, "Палитры %s\n" "Размер системной палитры: %d\n", (LPSTR)szPal, iPalSize);
MessageBox(NULL, szMsg, "Palette Demo", MB_OK); }
В начале файла определена константа PALETTESIZE, значение которой равно размеру создаваемой приложением логической палитры:
#define PALETTESIZE 256
Для того чтобы можно было запустить несколько копий приложения PALETTE, мы выполняем регистрацию класса окна только для первой копии приложения:
if(!hPrevInstance) if(!InitApp(hInstance)) return FALSE;
После регистрации класса окна вызывается функция PaletteInfo, которая предназначена для определения факта использования палитр в текущем видеорежиме.
Получив контекст отображения для всего экрана видеомонитора, эта функция вызывает функцию GetDeviceCaps, определяя размер системной палитры и растровые возможности устройства вывода (в данном случае, драйвера видеомонитора):
iPalSize = GetDeviceCaps(hdc, SIZEPALETTE); iRasterCaps = GetDeviceCaps(hdc, RASTERCAPS);
Если используется цветовые палитры, в слове iRasterCaps должен быть установлен бит RC_PALETTE:
if(iRasterCaps & RC_PALETTE) { iRasterCaps = TRUE; lstrcpy(szPal, "используются"); } else { iRasterCaps = FALSE; lstrcpy(szPal, "не используются"); }
На обработчик сообщения WM_CREATE возложена задача создания палитры.
Прежде всего заказываем память для структуры, содержащей палитру:
pLogPal = (NPLOGPALETTE) LocalAlloc(LMEM_FIXED, (sizeof (LOGPALETTE) + (sizeof (PALETTEENTRY) * (PALETTESIZE))));
Размер нужного буфера равен размеру структуры LOGPALETTE (заголовок палитры), плюс размер самой палитры, равный количеству элементов (PALETTESIZE), умноженному на размер одного элемента (sizeof (PALETTEENTRY) ).
В заголовке палитры необходимо заполнить два поля - версию и размер палитры:
pLogPal->palVersion = 0x300; pLogPal->palNumEntries = PALETTESIZE;
Далее обработчик сообщения WM_PAINT заполняет в цикле всю палитру оттенками серого, причем в поле peFlags записывается нулевое значение (для использования стандартного алгоритма реализации цветовой палитры):
for (i=0; i < 256; i++) { pLogPal->palPalEntry[i].peRed = pLogPal->palPalEntry[i].peGreen = pLogPal->palPalEntry[i].peBlue = i; pLogPal->palPalEntry[i].peFlags = 0; }
После заполнения структуры данных вызывается функция CreatePalette, создающая палитру:
hPal = CreatePalette((LPLOGPALETTE) pLogPal);
В глобальную переменную hPal записывается идентификатор созданной логической палитры.
Обработчик сообщения WM_SIZE определяет и сохраняет размеры внутренней области окна приложения, необходимые для рисования.
Рисование выполняется, как и следовало ожидать, при обработке сообщения WM_PAINT.
После получения контекста отображения приложение выбирает в него и реализует логическую палитру:
hOldPalette = SelectPalette(hdc, hPal, FALSE); RealizePalette(hdc);
Далее приложение в цикле рисует 256 прямоугольников шириной 2 пиксела. Высота этих прямоугольников равна высоте внутренней области окна приложения. Для каждого прямоугольника создается кисть, причем цвет кисти определяется как ссылка на элемент логической палитры с использованием макрокоманды PALETTEINDEX:
for (i=0; i < 256; i++) { hBrush = CreateSolidBrush(PALETTEINDEX(i)); FillRect(hdc, &rc, hBrush); rc.left = rc.right; rc.right += nWidth; DeleteBrush(hBrush); }
После использования кисти она удаляется.
Вы можете попробовать создавать кисть при помощи макрокоманды PALETTERGB:
hBrush = CreateSolidBrush(PALETTERGB(i, i, i));
Перед возвращением управления обработчик сообщения WM_PAINT выбирает в контекст отображения старую палитру:
SelectPalette(hdc, hOldPalette, TRUE);
Рассмотрим теперь обработчики сообщений WM_PALETTECHANGED и WM_QUERYNEWPALETTE.
Обработчик сообщения WM_PALETTECHANGED получает управление, когда какое-либо приложение изменяет системную палитру. Так как наше приложение тоже может изменить системную палитру, оно также может выступать инициатором рассылки этого сообщения.
Параметр wParam сообщения WM_PALETTECHANGED содержит идентификатор окна приложения, изменившего палитру. Если этот параметр равен идентификатору нашего окна, ничего делать не надо, поэтому мы просто выходим из функции окна:
case WM_PALETTECHANGED: { if (hwnd == (HWND) wParam) break; }
В противном случае управление передается обработчику сообщения WM_QUERYNEWPALETTE, который выполняет реализацию логической палитры приложения и перерисовку окна.
Если бы мы при обработке сообщения WM_PALETTECHANGED не выполнили проверку идентификатора окна, инициировавшего изменение системной палитры, а просто реализовали палитру приложения, это привело бы к "зацикливанию" приложения (так как в ответ на изменение палитры наше окно снова получит сообщение WM_PALETTECHANGED).
Теперь займемся обработчиком сообщения WM_QUERYNEWPALETTE. Для нашего приложения он выполняет почти те же самые действия, что и обработчик сообщения WM_PALETTECHANGED, поэтому для экономии места мы объединили эти обработчики.
При получении сообщения WM_QUERYNEWPALETTE или WM_PALETTECHANGED (как результата изменения системной палитры другим приложением) наше приложение получает контекст отображения и выбирает в него логическую палитру:
hOldPal = SelectPalette(hdc, hPal, (msg == WM_QUERYNEWPALETTE) ? FALSE : TRUE);
Заметьте, что для сообщения WM_QUERYNEWPALETTE палитра выбирается как для активного окна, а для WM_PALETTECHANGED - как для фонового.
Затем палитра реализуется в контексте отображения:
nChanged = RealizePalette(hdc);
После этого мы выбираем в контекст отображения старую палитру и освобождаем контекст отображения:
SelectPalette(hdc, hOldPal, TRUE); ReleaseDC(hwnd, hdc);
Если реализация логической палитры нашим приложением привела к изменению системной палитры, необходимо перерисовать окно. Для этого приложение вызывает функцию InvalidateRect:
if(nChanged) InvalidateRect(hwnd, NULL, TRUE);
Перед завершением работы приложение удаляет созданную логическую палитру и освобождает созданный для нее блок памяти (блок памяти можно было освободить и сразу после создания логической палитры):
DeletePalette(hPal); LocalFree(pLogPal);
Файл определения модуля для приложения PALETTE приведен в листинге 3.4.
Листинг 3.4. Файл palette/palette.def
; ============================= ; Файл определения модуля ; ============================= NAME PALETTE DESCRIPTION 'Приложение PALETTE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Приложение PRNFILE
Для иллюстрации всего сказанного выше мы немного изменили приложение TEDIT, описанное в 12 томе "Библиотеки системного программиста", добавив в него возможность печати. В главном окне этого простейшего редактора текста появилась кнопка "Print", с помощью которой вы можете распечатать текст на любом установленном в системе принтере (рис. 6.5).Рис. 6.5. Главное окно приложения PRNFILE
Исходный основного файла приложения приведен в листинге 6.1.
Листинг 6.1. Файл prnfile/prnfile.cpp
// ---------------------------------------- // Редактор текстовых файлов с возможностью печати // ----------------------------------------
#define STRICT #include
// Идентификатор редактора текста #define ID_EDIT 1
// Идентификаторы кнопок #define ID_NEW 2 #define ID_OPEN 3 #define ID_SAVE 4 #define ID_PRINT 5 #define ID_EXIT 6
// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); HFILE OpenFile(void); HFILE OpenSaveFile(void); int PrintFile(HWND, NPSTR, WORD);
// Имя класса окна char const szClassName[] = "TEditAppClass";
// Заголовок окна char const szWindowTitle[] = "Text Editor";
// Идентификатор копии приложения HINSTANCE hInst;
// Флаг изменений в тексте BOOL bUpdate;
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;
// Сохраняем идентификатор копии приложения // в глобальной переменной hInst = hInstance;
// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем расположение и размеры CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, // CW_USEDEFAULT, // 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные // параметры // Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;
// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна
memset(&wc, 0, sizeof(wc));
wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0); }
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Идентификатор редактора текста static HWND hEdit;
// Идентификаторы кнопок static HWND hButtNew; static HWND hButtOpen; static HWND hButtSave; static HWND hButtPrint; static HWND hButtExit;
// Идентификаторы файлов static HFILE hfSrcFile, hfDstFile;
switch (msg) { case WM_CREATE: { // Создаем редактор текста hEdit = CreateWindow("edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_HSCROLL | WS_VSCROLL | ES_LEFT | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE, 0, 0, 0, 0, hwnd, (HMENU) ID_EDIT, hInst, NULL);
// Устанавливаем максимальную длину // редактируемого текста, равную 32000 байт SendMessage(hEdit, EM_LIMITTEXT, 32000, 0L);
// Сбрасываем флаг обновления текста bUpdate = FALSE;
// Создаем кнопки hButtNew = CreateWindow("button", "New", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0, 0, 80, 20, hwnd, (HMENU) ID_NEW, hInst, NULL);
hButtOpen = CreateWindow("button", "Open", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 80, 0, 80, 20, hwnd, (HMENU) ID_OPEN, hInst, NULL);
hButtSave = CreateWindow("button", "Save", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 160, 0, 80, 20, hwnd, (HMENU) ID_SAVE, hInst, NULL);
hButtPrint = CreateWindow("button", "Print", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 240, 0, 80, 20, hwnd, (HMENU) ID_PRINT, hInst, NULL);
hButtExit = CreateWindow("button", "Exit", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 320, 0, 80, 20, hwnd, (HMENU) ID_EXIT, hInst, NULL);
return 0; }
case WM_SIZE: { // Устанавливаем размер органа управления // (текстового редактора) в соответствии // с размерами главного окна приложения MoveWindow(hEdit, 0, 20, LOWORD(lParam), HIWORD(lParam) - 20, TRUE); return 0; }
// Когда главное окно приложения получает // фокус ввода, отдаем фокус редактору текста case WM_SETFOCUS: { SetFocus(hEdit); return 0; }
case WM_COMMAND: { // Обработка извещений текстового редактора if(wParam == ID_EDIT) { // Ошибка if(HIWORD(lParam) == EN_ERRSPACE) { MessageBox(hwnd, "Мало памяти", szWindowTitle, MB_OK); }
// Произошло изменение в редактируемом // тексте else if(HIWORD(lParam) == EN_UPDATE) { // Устанавливаем флаг обновления текста bUpdate = TRUE; } return 0; }
// Нажата кнопка сохранения текста else if(wParam == ID_SAVE) { WORD wSize; HANDLE hTxtBuf; NPSTR npTextBuffer;
// Открываем выходной файл hfDstFile = OpenSaveFile(); if(!hfDstFile) return 0;
// Определяем размер текста wSize = GetWindowTextLength(hEdit);
// Получаем идентификатор блока памяти, // в котором находится редактируемый текст hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L);
// Фиксируем блок памяти и получаем указатель // на него npTextBuffer = (NPSTR)LocalLock(hTxtBuf);
// Записываем содержимое блока памяти в файл if(wSize != _lwrite(hfDstFile, npTextBuffer, wSize)) { // При ошибке закрываем файл и выдаем сообщение _lclose(hfDstFile); MessageBox(hwnd, "Ошибка при записи файла", szWindowTitle, MB_OK); return 0; }
// Закрываем файл _lclose(hfDstFile);
// Расфиксируем блок памяти LocalUnlock(hTxtBuf);
// Так как файл был только что сохранен, // сбрасываем флаг обновления bUpdate = FALSE;
SetFocus(hEdit); return 0; }
// Создание нового файла else if(wParam == ID_NEW) { // Проверяем флаг обновления if(bUpdate) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) return 0; }
// Сбрасываем содержимое текстового редактора SetWindowText(hEdit, "\0");
// Сбрасываем флаг обновления bUpdate = FALSE;
SetFocus(hEdit); return 0; }
// Загрузка файла для редактирования else if(wParam == ID_OPEN) { LPSTR lpTextBuffer; DWORD dwFileSize, dwCurrentPos;
// Проверяем флаг обновления if(bUpdate) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) return 0; }
// Открываем входной файл. hfSrcFile = OpenFile(); if(!hfSrcFile) return 0;
// Определяем размер файла dwCurrentPos = _llseek(hfSrcFile, 0L, 1); dwFileSize = _llseek(hfSrcFile, 0L, 2); _llseek(hfSrcFile, dwCurrentPos, 0);
// Размер файла не должен превосходить 32000 байт if(dwFileSize >= 32000) { _lclose(hfSrcFile); MessageBox(hwnd, "Размер файла больше 32000 байт", szWindowTitle, MB_OK); return 0; }
// Заказываем память для загрузки файла lpTextBuffer = (LPSTR)malloc(32000); if(lpTextBuffer == NULL) return 0;
// Загружаем текст из файла в буфер _lread(hfSrcFile, lpTextBuffer, dwFileSize);
// Закрываем буфер двоичным нулем lpTextBuffer[(WORD)dwFileSize] = '\0';
// Закрываем файл _lclose(hfSrcFile);
// Переносим содержимое буфера в // текстовый редактор SetWindowText(hEdit, lpTextBuffer);
// Освобождаем буфер free((void *)lpTextBuffer);
// сбрасываем флаг обновления bUpdate = FALSE;
SetFocus(hEdit); return 0; }
// ------------------------------------------ // Печать текста // ------------------------------------------ else if(wParam == ID_PRINT) { WORD wSize; HANDLE hTxtBuf; NPSTR npTextBuffer;
// Определяем размер текста wSize = GetWindowTextLength(hEdit);
// Получаем идентификатор блока памяти, // в котором находится редактируемый текст hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L);
// Фиксируем блок памяти и получаем указатель // на него npTextBuffer = (NPSTR)LocalLock(hTxtBuf);
PrintFile(hwnd, npTextBuffer, wSize);
// Расфиксируем блок памяти LocalUnlock(hTxtBuf);
SetFocus(hEdit); return 0; }
else if(wParam == ID_EXIT) { // Проверяем флаг обновления if(bUpdate) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) return 0; }
// Посылаем в функцию главного окна // сообщение WM_CLOSE SendMessage(hwnd, WM_CLOSE, 0, 0L); return 0; } return 0; }
case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); }
// ------------------------------- // Функция OpenFile // Сохранение файла // -------------------------------
HFILE OpenFile(void) { // Структура для выбора файла OPENFILENAME ofn;
// Буфер для записи пути к выбранному файлу char szFile[256];
// Буфер для записи имени выбранного файла char szFileTitle[256];
// Фильтр расширений имени файлов char szFilter[256] = "Text Files\0*.txt;*.doc\0Any Files\0*.*\0";
// Идентификатор открываемого файла HFILE hf;
// Инициализация имени выбираемого файла // не нужна, поэтому создаем пустую строку szFile[0] = '\0';
// Записываем нулевые значения во все поля // структуры, которая будет использована для // выбора файла memset(&ofn, 0, sizeof(OPENFILENAME));
// Инициализируем нужные нам поля
// Размер структуры ofn.lStructSize = sizeof(OPENFILENAME);
// Идентификатор окна ofn.hwndOwner = NULL;
// Адрес строки фильтра ofn.lpstrFilter = szFilter;
// Номер позиции выбора ofn.nFilterIndex = 1;
// Адрес буфера для записи пути // выбранного файла ofn.lpstrFile = szFile;
// Размер буфера для записи пути // выбранного файла ofn.nMaxFile = sizeof(szFile);
// Адрес буфера для записи имени // выбранного файла ofn.lpstrFileTitle = szFileTitle;
// Размер буфера для записи имени // выбранного файла ofn.nMaxFileTitle = sizeof(szFileTitle);
// В качестве начального каталога для // поиска выбираем текущий каталог ofn.lpstrInitialDir = NULL;
// Определяем режимы выбора файла ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; // Выбираем входной файл if (GetOpenFileName(&ofn)) {
// Открываем выбранный файл hf = _lopen(ofn.lpstrFile, OF_READ);
// Возвращаем идентификатор файла return hf; } // При отказе от выбора возвращаем // нулевое значение else return 0; }
// ------------------------------- // Функция OpenSaveFile // Выбор файла для редактирования // -------------------------------
HFILE OpenSaveFile(void) { OPENFILENAME ofn;
char szFile[256]; char szFileTitle[256]; char szFilter[256] = "Text Files\0*.txt\0Any Files\0*.*\0";
HFILE hf;
szFile[0] = '\0'; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = NULL; ofn.Flags = OFN_HIDEREADONLY;
// Выбираем выходной файл if (GetSaveFileName(&ofn)) {
// При необходимости создаем файл hf = _lcreat(ofn.lpstrFile, 0); return hf; } else return 0; }
Подробное описание этого файла вы найдете в 12 томе, здесь же для экономии места мы расскажем только о фрагменте, выполняющем печать.
Когда вы нажимаете кнопку "Print", соответствующий обработчик определяет размер текста, загруженного в редактор, вызывая функцию GetWindowTextLength:
wSize = GetWindowTextLength(hEdit);
Далее он получает адрес блока памяти, содержащий текст, фиксируя его:
hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L); npTextBuffer = (NPSTR)LocalLock(hTxtBuf);
После этого вызывается функция печати PrintFile, определенная в файле print.cpp (листинг 6.2):
PrintFile(hwnd, npTextBuffer, wSize);
В качестве параметров этой функции передаются идентификатор окна приложения, адрес буфера, содержащего печатаемый текст, и размер этого буфера в байтах.
После выполнения печати буфер расфиксируется, после чего редактор текста получает фокус ввода:
LocalUnlock(hTxtBuf); SetFocus(hEdit); return 0;
Все функции, предназначенные для работы с принтером, мы вынесли в отдельный файл (листинг 6.2).
Листинг 6.2. Файл prnfile/print.cpp
// ---------------------------------------------------- // Функции для работы с принтером // ---------------------------------------------------- #define STRICT #include
// Прототипы функций BOOL CALLBACK _export AbortDlgFunc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK _export AbortFunc(HDC hdc, int nCode); HDC GetPrinterDC(HWND);
// Внешние глобальные переменные extern HWND hdlgAbort; extern BOOL fAbort;
BOOL fAbort = FALSE; HWND hdlgAbort = 0; static PRINTDLG pd;
// ---------------------------------------------------- // Функция PrintFile // Печать файла // ----------------------------------------------------
BOOL PrintFile(HWND hwnd, NPSTR npBuff, WORD wSize) { HDC hdc; int cyPage; int cyChar, yPos, nLength; int i; WORD wCurPos = 0; TEXTMETRIC tm;
ABORTPROC lpAbortFunc; BOOL fDone; char abBuffer[256]; DOCINFO docinfo; DLGPROC lpAbortDlgFunc; HINSTANCE hInst; int rc;
// Получаем контекст устройства для принтера hdc = GetPrinterDC(hwnd);
// Определяем разрешение принтера по вертикали cyPage = GetDeviceCaps(hdc, VERTRES);
// Определяем метрики текста GetTextMetrics(hdc, &tm);
// Вычисляем высоту шрифта cyChar = tm.tmHeight + tm.tmExternalLeading;
// Создаем переходник для функции AbortFunc hInst = GetWindowInstance(hwnd); lpAbortFunc = (ABORTPROC)MakeProcInstance((FARPROC)AbortFunc, hInst);
// Устанавливаем функцию AbortProc rc = SetAbortProc(hdc, lpAbortFunc); if(rc <= 0) { DeleteDC(hdc); return FALSE; }
// Создаем переходник для функции диалога lpAbortDlgFunc = (DLGPROC)MakeProcInstance((FARPROC)AbortDlgFunc, hInst);
// Создаем диалог для отмены печати hdlgAbort = CreateDialogParam ( hInst, MAKEINTRESOURCE(IDD_ABORT), hwnd, lpAbortDlgFunc, NULL) ;
if(!hdlgAbort) { FreeProcInstance((FARPROC)lpAbortFunc); DeleteDC(hdc); return FALSE; }
// Отображаем созданную диалоговую панель ShowWindow(hdlgAbort, SW_SHOWNORMAL); UpdateWindow(hdlgAbort);
// Переводим окно приложения в неактивное // состояние EnableWindow(hwnd, FALSE);
// Заполняем структуру docinfo docinfo.cbSize = sizeof(docinfo); docinfo.lpszDocName = NULL; docinfo.lpszOutput = NULL;
// Начинаем печать документа rc = StartDoc(hdc, &docinfo); if(rc <= 0) { DestroyWindow(hdlgAbort); FreeProcInstance((FARPROC)lpAbortFunc); FreeProcInstance((FARPROC)lpAbortDlgFunc); DeleteDC(hdc); return FALSE; }
// Флаг завершения печати документа fDone = FALSE;
// Цикл печати страниц документа while(!fDone && !fAbort) { // Начинаем печать страницы документа StartPage(hdc);
// Начальная позиция по вертикали yPos = 0;
// Цикл по строкам страницы while(yPos + cyChar < cyPage) { // Проверка завершения печати страницы if(wCurPos > wSize) { fDone = TRUE; break; }
i=0; nLength = 0;
// Цикл по строке // Копируем строку в буфер abBuffer while((npBuff[wCurPos] != 0x0d) && (wCurPos < wSize)) { abBuffer[i] = npBuff[wCurPos]; i++; wCurPos++; nLength++; }
// Рисуем одну строку текста TextOut(hdc, 0, yPos, abBuffer, nLength);
// Переходим к следующей строке wCurPos += 2; yPos += cyChar ; }
// Инициируем печать страницы rc = EndPage(hdc); if(rc < 0) { fAbort = TRUE; break; } }
// При аварийном завершении печати вызываем // функцию AbortDoc, при нормальном - EndDoc if(fAbort) AbortDoc(hdc); else EndDoc(hdc);
// Активизируем главное окно приложения EnableWindow(hwnd, TRUE);
// Удаляем диалоговую панель DestroyWindow(hdlgAbort);
// Освобождаем ресурсы FreeProcInstance((FARPROC)lpAbortFunc); FreeProcInstance((FARPROC)lpAbortDlgFunc); DeleteDC(hdc);
return TRUE ; }
// ---------------------------------------------------- // Функция AbortDlgFunc // Функция диалога для диалоговой панели, // позволяющей прервать процесс печати // ---------------------------------------------------- #pragma argsused
BOOL CALLBACK _export AbortDlgFunc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { // Инициализируем флаги case WM_INITDIALOG: { fAbort = FALSE; hdlgAbort = hdlg; return TRUE; }
case WM_COMMAND: { // Устанавливаем флаг аварийного завершения печати if (wParam == IDOK wParam == IDCANCEL) { fAbort = TRUE; return TRUE; } return FALSE; }
case WM_DESTROY: { hdlgAbort = 0; return FALSE; } } return FALSE; }
// ---------------------------------------------------- // Функция AbortFunc // Обеспечивает возможность работы других // приложений во время печати // ---------------------------------------------------- #pragma argsused
BOOL CALLBACK _export AbortFunc(HDC hdc, int nCode) { MSG msg;
// Второй цикл обработки сообщений while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { if(!hdlgAbort !IsDialogMessage (hdlgAbort, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }
return(!fAbort); }
// ---------------------------------------------------- // Функция GetPrinterDC // Выводит на экран диалоговую панель "Print", // с помощью которой можно выбрать принтер. // Возвращает идентификатор контекста для // выбранного принтера // ----------------------------------------------------
HDC GetPrinterDC(HWND hwnd) { BOOL fResult;
// Инициализируем структуру PRINTDLG memset(&pd, 0, sizeof(PRINTDLG));
pd.lStructSize = sizeof(PRINTDLG); pd.hwndOwner = hwnd; pd.Flags = PD_RETURNDC;
// Отображаем диалоговую панель fResult = PrintDlg(&pd);
// При необходимости освобождаем память, полученную // функцией PrintDlg для структур DEVMODE и DEVNAMES if(pd.hDevMode != 0) GlobalFree (pd.hDevMode);
if(pd.hDevNames != 0) GlobalFree (pd.hDevNames);
// В случае успешного завершения возвращаем // контекст принтера if(fResult) return pd.hDC;
else return 0; }
Функция PrintFile выполняет печать файла, загруженного в текстовый редактор.
Она получает контекст печати, вызывая функцию GetPrinterDC, определенную в этом же файле. Функция GetPrinterDC выводит на экран стандартную диалоговую панель "Print", позволяющую выбрать принтер и задать параметры для выбранного принтера.
Далее определяется разрешение принтера по вертикали, метрики текста для шрифта, выбранного в контекст принтера, вычисляется высота шрифта. Вся эта информация необходима для правильного расположения строк текста на листе бумаги.
После этого функция PrintFile создает переходник для функции отмены печати и подключает последнюю, вызывая функцию SetAbortProc.
Далее создается переходник для функции диалога отмены печати, затем создается и выводится на экран диалоговая панель отмены печати. Шаблон диалоговой панели определен в файле ресурсов и содержит единственную кнопку с надписью "Cancel".
После отображения этой панели главное окно приложения переводится в неактивное состояние для передачи фокуса ввода диалоговой панели отмены печати.
Перед началом печати заполняется структура DOCINFO и вызывается функция StartDoc.
В цикле печати страниц документа выполняется проверка глобального флага отмены печати, а также флага завершения печати fDone.
Перед началом печати каждой страницы вызывается функция StartPage.
Далее выполняется построчное копирование текста из буфера текстового редактора в буфер abBuffer с последующим выводом содержимого этого буфера на принтер функцией TextOut.
Печать страницы выполняется после вызова функции EndPage.
При нормальном завершении процесса печати вызывается функция EndDoc, а при аварийном - AbortDoc.
После нормального или аварийного завершения печати документа активизируется главное окно приложения, а диалоговая панель отмены печати удаляется. Вслед за этим освобождаются созданные переходники и контекст принтера.
Идентификатор диалоговой панели определен в файле prnfile.hpp (листинг 6.3).
Листинг 6.3. Файл prnfile/prnfile.hpp
#define IDD_ABORT 25
Шаблон диалоговой панели отмены печати определен в файле описания ресурсов приложения (листинг 6.4).
Листинг 6.4. Файл prnfile/prnfile.rc
#include
IDD_ABORT DIALOG 50, 30, 89, 43 STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU CAPTION "Печать..." BEGIN CONTROL "Cancel", IDCANCEL, "BUTTON", WS_GROUP, 29, 23, 32, 14 CTEXT "Cancel - отмена печати", -1, -1, 8, 90, 8, WS_CHILD | WS_VISIBLE | WS_GROUP END
Файл определения модуля приведен в листинге 6.5.
Листинг 6.5. Файл prnfile/prnfile.def
; ============================= ; Файл определения модуля ; ============================= NAME PRNFILE DESCRIPTION 'Приложение PRNFILE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Приложение REGIONS
Для демонстрации использования комбинированных областей мы подготовили приложение REGIONS. Это приложение создает три области: прямоугольную и две эллиптические разного размера. Прямоугольная область объединяется с первой эллиптической областью. Из полученного результата вырезается вторая эллиптическая область (меньших размеров).Полученная комбинированная область используется в качестве маски, через которую в окно приложения выводится текст (рис. 2.27). Для большей наглядности границы области ограничения обведены зеленой кистью при помощи функции FrameRgn.
Рис. 2.27. Вывод текста с использованием области ограничения
Исходный текст приложения приведен в листинге 2.6.
Листинг 2.6. Файл regions/regions.cpp
// ---------------------------------------- // Приложение REGIONS // Демонстрация использования области ограничения // ----------------------------------------
#define STRICT #include
// Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
// Имя класса окна char const szClassName[] = "RegionsClass";
// Заголовок окна char const szWindowTitle[] = "Regions";
// Размеры внутренней области окна short cxClient, cyClient;
// Размеры символов int cxChar, cyChar;
// Текст для вывода в окне приложения char const szText[] = "В интерфейсе GDI есть средства, позволяющие приложениям" " создавать области достаточно сложной формы из" " прямоугольных, многоугольных и эллиптических областей." " Такие области можно закрашивать или использовать" " в качестве маски при выводе графического изображения. " "В последнем случае область называется областью" " ограничения. Она должна быть выбрана в контекст" " отображения.";
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;
// После успешной инициализации приложения создаем // главное окно приложения hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);
// Если создать окно не удалось, завершаем приложение if(!hwnd) return FALSE;
// Рисуем главное окно ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0); }
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static TEXTMETRIC tm; static HRGN hrgn1, hrgn2, hrgn3, hrgnTemp, hrgnClip; HBRUSH hbrush;
switch (msg) { case WM_CREATE: { // Определяем метрику шрифта hdc = GetDC(hwnd); GetTextMetrics(hdc, &tm); ReleaseDC(hwnd, hdc);
// Высота и средняя ширина букв cyChar = tm.tmHeight + tm.tmExternalLeading; cxChar = tm.tmAveCharWidth;
// Область ограничения не задана hrgnClip = NULL;
return 0; }
// При изменении размеров окна сохраняем // новые значения для ширины и высоты, // а также определяем область ограничения case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam);
// Если область ограничения была определена раньше, // удаляем ее if(hrgnClip) DeleteRgn(hrgnClip);
// Формируем область ограничения hrgnClip = CreateEllipticRgn(0, 0, cxClient, cyClient);
// Временная область ограничения hrgnTemp = CreateEllipticRgn(0, 0, cxClient, cyClient);
// Первая эллиптическая область hrgn1 = CreateEllipticRgn(0, 0, cxClient, cyClient);
// Вторая эллиптическая область hrgn2 = CreateEllipticRgn(cxClient/3, cyClient/3, 2*(cxClient/3), 2*(cyClient/3));
// Прямоугольная область hrgn3 = CreateRectRgn(cxClient/20, cyClient/20, 19*(cxClient/20), 19*(cyClient/20));
// Комбинируем области UnionRgn(hrgnTemp, hrgn1, hrgn3); SubtractRgn(hrgnClip, hrgnTemp, hrgn2);
// Удаляем временные области DeleteRgn(hrgn1); DeleteRgn(hrgn2); DeleteRgn(hrgn3); DeleteRgn(hrgnTemp); return 0; }
// Рисование в окне case WM_PAINT: { RECT rc;
// Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps);
// Выбираем встроенную кисть зеленого цвета hbrush = CreateSolidBrush(RGB(0, 0xff, 0));
// Обводим границы области FrameRgn(hdc, hrgnClip, hbrush, 2, 5);
// Выбираем область ограничения в контекст // отображения SelectClipRgn(hdc, hrgnClip);
// Определяем координаты прямоугольной // области для вывода текста rc.left = cxChar; rc.top = 0; rc.right = cxClient - cxChar; rc.bottom = cyClient;
// Вывод текста DrawText(hdc, szText, lstrlen(szText), &rc, DT_LEFT | DT_WORDBREAK);
// Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; }
case WM_DESTROY: { // удаляем область ограничения DeleteRgn(hrgnClip);
PostQuitMessage(0); return 0; }
default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
Во время создания окна обработчик сообщения WM_CREATE определяет метрику шрифта и записывает значение NULL в переменную hrgnClip.
Эта переменная будет использоваться для хранения идентификатора области ограничения.
Область ограничения формируется каждый раз заново при изменении размеров окна. Обработчик сообщения WM_SIZE сохраняет ширину и высоту окна в переменных cxClient и cyClient.
Затем он проверяет содержимое переменной hrgnClip. Если была задана область ограничения, она удаляется, так как при изменении размеров окна нужно сформировать новую область ограничения.
После этого приложение создает области ограничения и комбинирует их в одну область hrgnClip, используя макрокоманды UnionRgn и SubtractRgn.
Далее все области, кроме hrgnClip, удаляются, так как они больше не нужны.
Приложение REGIONS рисует в окне во время обработки сообщения WM_PAINT.
Для большей наглядности обработчик этого сообщения обводит контуры области ограничения, вызывая функцию FrameRgn:
hbrush = CreateSolidBrush(RGB(0, 0xff, 0)); FrameRgn(hdc, hrgnClip, hbrush, 2, 5);
Далее область hrgnClip выбирается в контекст отображения для использования в качестве маски при выводе текста:
SelectClipRgn(hdc, hrgnClip);
Вывод текста выполняется при помощи функции DrawText.
Перед завершением своей работы (при обработке сообщения WM_DESTROY) приложение удаляет область hrgnClip, вызывая макрокоманду DeleteRgn:
DeleteRgn(hrgnClip);
Файл определения модуля для приложения REGIONS приведен в листинге 2.7.
Листинг 2.7. Файл regions/regions.def
; ============================= ; Файл определения модуля ; ============================= NAME REGIONS DESCRIPTION 'Приложение REGIONS, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Приложение SYSPAL
В завершение третьей главы приведем исходные тексты приложения SYSPAL, предназначенного для просмотра системной палитры. Оно работает только тогда, когда драйвер видеоадаптера использует цветовые палитры, поэтому вы не сможете запустить его в режимах с низким или высоким цветовым разрешением.С помощью приложения SYSPAL вы сможете визуально проследить за изменениями системной палитры, например, при загрузке bmp-файлов в приложение Paint Brush, при запуске приложения PALETTE или других приложений, изменяющих системную палитру.
Это приложение во многом напоминает предыдущее, поэтому для экономии места мы сократили количество комментариев в его исходном тексте (листинг 3.5).
Листинг 3.5. Файл syspalet/syspal.cpp
// ---------------------------------------- // Приложение SYSPAL // Просмотр системной цветовой палитры // ----------------------------------------
#define STRICT #include
#define PALETTESIZE 256
BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); BOOL PaletteInfo(void);
char const szClassName[] = "SysPalClass"; char const szWindowTitle[] = "System Palette";
short cxClient, cyClient; HPALETTE hPal, hOldPalette; NPLOGPALETTE pLogPal;
// ===================================== // Функция WinMain // ===================================== #pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения
if(!hPrevInstance) if(!InitApp(hInstance)) return FALSE;
if(!PaletteInfo()) return FALSE;
hwnd = CreateWindow( szClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, // задаем размеры и расположение CW_USEDEFAULT, // окна, принятые по умолчанию CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);
if(!hwnd) return FALSE;
ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd);
while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); } return msg.wParam; }
// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // =====================================
BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszClassName = (LPSTR)szClassName;
// Регистрация класса aWndClass = RegisterClass(&wc);
return (aWndClass != 0); }
// ===================================== // Функция WndProc // =====================================
LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps;
switch (msg) { case WM_CREATE: { int i;
// Получаем память для палитры pLogPal = (NPLOGPALETTE) LocalAlloc(LMEM_FIXED, (sizeof (LOGPALETTE) + (sizeof (PALETTEENTRY) * (PALETTESIZE))));
// Заполняем заголовок палитры pLogPal->palVersion = 0x300; pLogPal->palNumEntries = PALETTESIZE;
// Младшее слово структуры PALETTEENTRY содержит поля // peRed и peGreen, а старшее - peBlue и peFlags. // Отмечаем все элементы палитры флагом PC_EXPLICIT for (i = 0; i < PALETTESIZE; i++) { pLogPal->palPalEntry[i].peBlue = 0; *((PWORD)(&pLogPal->palPalEntry[i].peRed)) = i; pLogPal->palPalEntry[i].peFlags = PC_EXPLICIT; }
// Создаем логическую палитру hPal = CreatePalette((LPLOGPALETTE) pLogPal); return 0; }
// При изменении размеров окна сохраняем // новые значения для ширины и высоты case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; }
// Рисование в окне case WM_PAINT: { RECT rc; int i, nWidth; HBRUSH hBrush;
hdc = BeginPaint(hwnd, &ps); hOldPalette = SelectPalette(hdc, hPal, FALSE); RealizePalette(hdc);
nWidth = 2; rc.left = rc.top = 0; rc.right = nWidth; rc.bottom = cyClient;
for (i=0; i < 256; i++) { hBrush = CreateSolidBrush (PALETTEINDEX (i)); FillRect (hdc, &rc, hBrush); rc.left = rc.right; rc.right += nWidth; DeleteBrush(hBrush); }
SelectPalette(hdc, hOldPalette, TRUE); EndPaint(hwnd, &ps); return 0; }
case WM_PALETTECHANGED: { if (hwnd == (HWND) wParam) break; }
case WM_QUERYNEWPALETTE: { HDC hdc; HPALETTE hOldPal; int nChanged;
hdc = GetDC(hwnd);
hOldPal = SelectPalette(hdc, hPal, (msg == WM_QUERYNEWPALETTE) ? FALSE : TRUE);
nChanged = RealizePalette(hdc); SelectPalette(hdc, hOldPal, TRUE);
ReleaseDC(hwnd, hdc);
if(nChanged) InvalidateRect(hwnd, NULL, TRUE);
return nChanged; }
case WM_DESTROY: { DeletePalette(hPal); LocalFree(pLogPal);
PostQuitMessage(0); return 0; }
default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
// -------------------------------------------------------- // Функция PaletteInfo // Проверка возможности работы с палитрами // --------------------------------------------------------
BOOL PaletteInfo(void) { HDC hdc; int iRasterCaps;
hdc = GetDC(NULL); iRasterCaps = GetDeviceCaps(hdc, RASTERCAPS); ReleaseDC(NULL, hdc);
// Проверяем, используется ли механизм палитр if(iRasterCaps & RC_PALETTE) return TRUE; else return FALSE; }
Так же, как и приложение PALETTE, приложение SYSPAL создает логическую палитру для 256 цветов. Однако палитра заполняется по другому:
for (i = 0; i < PALETTESIZE; i++) { pLogPal->palPalEntry[i].peBlue = 0; *((PWORD)(&pLogPal->palPalEntry[i].peRed)) = i; pLogPal->palPalEntry[i].peFlags = PC_EXPLICIT; } hPal = CreatePalette((LPLOGPALETTE) pLogPal);
Все элементы палитры отмечаются флагом PC_EXPLICIT. Это означает, что палитра содержит не цвета, а индексы цветов системной палитры. Точнее, младшее слово каждого элемента палитры содержит индекс цвета системной палитры.
Структура PALETTEENTRY описана в файле windows.h следующим образом:
typedef struct tagPALETTEENTRY { BYTE peRed; BYTE peGreen; BYTE peBlue; BYTE peFlags; } PALETTEENTRY;
При этом младшее слово структуры PALETTEENTRY содержит поля peRed и peGreen, а старшее - peBlue и peFlags. Мы пользуемся этим обстоятельством, записывая в младшее слово значения от 0 до 255 (индекс в системной табице цветов), в поле peBlue - нулевое значение, а в поле peFlags - значение PC_EXPLICIT.
Созданная таким образом палитра используется также, как и в приложении PALETTE.
Файл определения модуля приложения SYSPAL приведен в листинге 3.6.
Листинг 3.6. Файл syspalet/syspal.def
; ============================= ; Файл определения модуля ; ============================= NAME SYSPAL DESCRIPTION 'Приложение SYSPAL, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Прямоугольная область
Для создания прямоугольной области предназначены функции CreateRectRgn и CreateRectRgnIndirect :HRGN WINAPI CreateRectRgn( int nLeftRect, int nTopRect, int nRightRect, int nBottomRect); HRGN WINAPI CreateRectRgnIndirect(const RECT FAR* lprc);
Обе эти функции создают область прямоугольной формы, размеры которой заданы координатами верхнего левого и правого нижнего углов (для функции CreateRectRgn) или при помощи структуры типа RECT (для функции CreateRectRgnIndirect). Функции возвращают идентификатор созданной области.
Заметьте, что область создается вне всякой связи с контекстом отображения. Область - это объект, принадлежащий GDI, поэтому его следует удалить после использования. Лучше всего для этого воспользоваться макрокомандой DeleteRgn , определенной в файле windowsx.h следующим образом:
#define DeleteRgn(hrgn) DeleteObject((HGDIOBJ)(HRGN)(hrgn))
Вы можете создать область в виде прямоугольника со скругленными углами, если воспользуетесь функцией CreateRoundRectRgn :
HRGN WINAPI CreateRoundRectRgn( int nLeftRect, int nTopRect, int nRightRect, int nBottomRect, int nWidth, int nHeight);
Первые четыре параметра аналогичны параметрам функции CreateRectRgn. Они определяют координаты углов прямоугольной области. Последние два параметра, nWidth и nHeight, предназначены, соответственно, для определения размеров воображаемого эллипса, формирующего скругленные углы.
Проверка возможности использования палитры
Далеко не все устройства отображения способны работать с палитрами. Например, драйвер видеоадаптера VGA палитры не поддерживает, несмотря на то что аппаратура VGA может работать с палитрой. Последнее обстоятельство связано с тем, что размер палитры VGA составляет всего 64 ячейки, что явно недостаточно.Самый лучший способ убедиться в том, что драйвер видеоадаптера способен работать с палитрами, заключается в вызове функции GetDeviceCaps с параметром RASTERCAPS (мы обсуждали эту функцию в 11 томе "Библиотеки системного программиста"). Если в возвращенном слове установлен бит RC_PALETTE , приложение может использовать цветовые палитры.
Стандартный размер системной палитры равен 256 ячеек, однако вы можете уточнить это значение, вызвав функцию GetDeviceCaps с параметром SIZEPALETTE . Если драйвер видеоконтроллера не работает с палитрами, размер палитры, определенный с помощью функции GetDeviceCaps, может быть равным 0.
Для определения количества системных цветов используйте эту же функцию с параметром NUMRESERVED .
С помощью функции GetDeviceCaps можно также определить цветовое разрешение устройства вывода. При этом следует учитывать, что некоторые устройства работают с цветовыми плоскостями. Количество этих плоскостей можно определить, пользуясь значением PLANES . Отметим, что количество цветов, которые могут быть представлены устройством с цветовыми плоскостями, равно 2n, где n - количество цветовых плоскостей.
Если устройство работает с цветовыми плоскостями и использует несколько бит для представления цвета одного пиксела, количество одновременно отображаемых цветов можно определить по формуле:
nColors = 2(nPixel * nPlanes),
где nPixel - количество битов, используемых для представления цвета пиксела (значение BITSPIXEL ); nPlanes - количество цветовых плоскостей (значение PLANES).
Значение NUMCOLORS равно количеству статических цветов. Так как палитра может быть изменена (перезагружена), фактически вы можете использовать больше цветов, чем указано в NUMCOLORS.
Работа с функцией PrintDlg
Перед вызовом функции PrintDlg следует проинициализировать нужные поля, записав в остальные поля нулевые значения:memset(&pd, 0, sizeof(PRINTDLG)); pd.lStructSize = sizeof(PRINTDLG); pd.hwndOwner = hwnd; pd.Flags = PD_RETURNDC; fResult = PrintDlg(&pd);
Если перед вызовом функции PrintDlg в полях hDevMode и hDevNames было значение NULL, функция заказывает глобальные блоки памяти для структур DEVMODE и DEVNAMES, заполняя их перед возвратом управления.
Структура DEVMODE определена в файле print.h, который находится в каталоге include системы разработки Borland Turbo C++ for Windows:
typedef struct tagDEVMODE { char dmDeviceName[CCHDEVICENAME]; UINT dmSpecVersion; UINT dmDriverVersion; UINT dmSize; UINT dmDriverExtra; DWORD dmFields; int dmOrientation; int dmPaperSize; int dmPaperLength; int dmPaperWidth; int dmScale; int dmCopies; int dmDefaultSource; int dmPrintQuality; int dmColor; int dmDuplex; int dmYResolution; int dmTTOption; } DEVMODE; typedef DEVMODE* PDEVMODE, NEAR* NPDEVMODE, FAR* LPDEVMODE;
Эта структура содержит разнообразную информацию о конфигурации и параметрах принтера. Она подробно описана в документации, которая поставляется вместе с SDK. Из за ограниченного объема книги мы приведем краткое описание полей этой структуры.
| Поле | Описание |
| dmDeviceName | Имя драйвера принтера |
| dmSpecVersion | Номер версии структуры DEVMODE. Для Windows версии 3.1 это поле содержит значение 0x30a |
| dmDriverVersion | Версия драйвера |
| dmSize | Размер структуры DEVMODE в байтах |
| dmDriverExtra | Размер в байтах дополнительной структуры данных, которая может находиться в памяти сразу за структурой DEVMODE |
| dmFields | Набор флагов, каждый из которых отвечает за свое поле структуры DEVMODE. Если флаг установлен, соответствующее поле инициализируется. возможны следующие значения: DM_ORIENTATION , DM_PAPERSIZE , DM_PAPERLENGTH , DM_PAPERWIDTH , DM_SCALE , DM_COPIES , DM_DEFAULTSOURCE , DM_PRINTQUALITY , DM_COLOR , DM_DUPLEX , DM_YRESOLUTION , DM_TTOPTION |
| dmOrientation | Ориентация бумаги. Возможные значения:DMORIENT_PORTRAIT , DMORIENT_LANDSCAPE |
| dmPaperSize | Код размера бумаги. Например, для бумаги формата A4 используется константа DMPAPIER_A4 |
| dmPaperLength | Длина листа бумаги в десятых долях миллиметра |
| dmPaperWidth | Ширина листа бумаги в десятых долях миллиметра |
| dmScale | Коэффициент масштабирования для печати |
| dmCopies | Количество печатаемых копий |
| dmDefaultSource | Код устройства подачи бумаги, используемого по умолчанию. |
| dmPrintQuality | Код разрешения принтера: DMRES_HIGH , DMRES_LOW , DMRES_MEDIUM , DMRES_DRAFT или положительное число, равное количеству точек на дюйм |
| dmColor | Режим печати для цветного принтера: DMCOLOR_COLOR - цветная печать, DMCOLOR_MONOCHROME - монохромная печать |
| dmDuplex | Возможность печати с двух сторон бумажного листа |
| dmYResolution | Разрешение принтера по вертикали в точках на дюйм |
| dmTTOption | Способ печати шрифтов True Type:DMTT_BITMAP - печать в графическом режиме, обычно используется для матричных принтеров;DMTT_DOWNLOAD - загрузка шрифтов True Type в память принтера, используется для лазерных принтеров, совместимых с принтерами HP LaserJet;DMTT_SUBDEV - замена шрифтов на принтерные шрифты, используется для PostScript-принтеров |
Структура DEVNAMES , как мы уже говорили, содержит имя драйвера, имя принтера и имя порта вывода, к которому подключен принтер:
typedef struct tagDEVNAMES { UINT wDriverOffset; UINT wDeviceOffset; UINT wOutputOffset; UINT wDefault; } DEVNAMES; typedef DEVNAMES FAR* LPDEVNAMES;
Первые три слова структуры содержат смещения текстовых строк с именами, соответственно, драйвера, принтера и порта вывода. Строки расположены в памяти непосредственно за структурой DEVNAMES. Поле wDefault может содержать флаг DN_DEFAULTPRN , в этом случае все три строки описывают принтер, выбранный по умолчанию.
Вы можете подготовить свои значения для двух описанных выше структур, заказать глобальные блоки памяти и передать их идентификаторы функции PrintDlg, записав в соответствующие поля структуры PRINTDLG.
После возврата из функции PrintDlg необходимо освободить эти блоки памяти, взяв их идентификаторы из полей hDevMode и hDevNames структуры PRINTDLG. Учтите, что функция PrintDlg может изменить значения последних двух полей, поэтому надо освобождать блоки памяти с идентификаторами, взятыми из структуры PRINTDLG после возврата из функции PrintDlg:
if(pd.hDevMode != 0) GlobalFree (pd.hDevMode); if(pd.hDevNames != 0) GlobalFree (pd.hDevNames);
Если перед вызовом функции PrintDlg вы указали флаги PD_RETURNDC или PD_RETURNIC, после возврата поле hDC будет содержать, соответственно, идентификатор контекста устройства или идентификатор информационного контекста:
if(fResult) return pd.hDC; else return NULL;
Работа с контекстом отображения
2.1.2.2.
2.3.
2.4.
2.5.
2.6.
2.7.
2.8.
В этой главе вы научитесь получать контекст отображения и контекст устройства , изменять его атрибуты и рисовать простейшие графические изображения на экране монитора. Вопросы, связанные с использованием цвета, цветовых палитр, вывода текста и графических изображений bitmap, а также печати на принтере будут рассмотрены в следующих главах нашей книги.
Как правило, приложения выполняют всю работу по рисованию во время обработки сообщения WM_PAINT , хотя часто требуется рисовать и во время обработки других сообщений. В любом случае приложение должно придерживаться следующей последовательности действий:
Последнее действие (освобождение или удаление контекста отображения) должно быть обязательно выполнено. Самый простой способ полностью нарушить работоспособность Windows - забыть освободить полученный контекст отображения или удалить созданный контекст отображения или устройства.
Так как контекст отображения - критический ресурс, его необходимо освобождать сразу, как только в нем отпадет необходимость. Операционная система Windows выполняет кеширование обычного контекста отображения (есть и необычные контексты отображения, но об этом позже), причем кешируются только пять контекстов. Если Windows не может удовлетворить запрос какого-либо приложения на выделение контекста отображения, вся операционная система окажется в критическом состоянии, единственным выходом из которого будет полный перезапуск Windows.
Среди всех атрибутов контекста отображения, описанных в первой главе, особое место занимает режим отображения, влияющий на систему координат и, соответственно, на работу практически всех функций рисования. Поэтому необходимо уделить особое внимание вопросам использования режимов отображения и систем координат.
Однако прежде всего необходимо научиться получать и освобождать контекст отображения.
Работа с принтером
6.1.6.2.
6.3.
6.4.
Приложения Windows работают с принтером совсем не так, как программы MS-DOS. Последние используют для печати BIOS (прерывание INT17h ) или функцию 05h прерывания MS-DOS INT 21h . Некоторые программы даже используют порты ввода/вывода параллельного или последовательного интерфейса, к которому подключен принтер. Мы описали все эти средства в первой части второго тома "Библиотеки системного программиста" (стр. 156).
Заметим, что создание программы MS-DOS, способной работать с любым принтером в символьном и графическом режимах, - далеко не простая задача. В мире созданы десятки различных моделей принтеров, каждая из которых имеет свою систему команд и другие особенности. Практически приложение должно иметь собственный набор драйверов для обеспечения возможности работы с любыми моделями принтеров. Текстовый процессор Microsoft Word for DOS версий 4.0 - 6.0 может работать со многими типами принтеров благодаря богатому набору принтерных драйверов, поставляющихся вместе с ним. Тем не менее этого набора иногда оказывается недостаточно.
Ситуация с принтерами в MS-DOS напоминает ситуацию с видеоконтроллерами - так как MS-DOS не имеет в своем составе драйверы видеоконтроллеров, каждая программа MS-DOS должна иметь собственные драйверы.
Разработчики приложений Windows находятся в лучшем положении, так как в этой операционной системе есть сильная поддержка принтеров. Набор драйверов, поставляемых вместе с Windows, обеспечивает возможность работы практически с любой моделью принтера для всех приложений. Разработчики приложений не должны учитывать аппаратные особенности моделей принтеров, так как эта работа выполняется принтерными драйверами.
Однако поддержка принтеров не ограничивается учетом аппаратных особенностей и набора команд. Приложения могут использовать для вывода на принтер почти все функции GDI, рассмотренные нами в этом томе. Для этого им достаточно получить контекст отображения, связанный с принтером. Передавая идентификатор контекста отображения в качестве первого параметра функциям GDI, приложения могут рисовать на бумаге текст, вызывая функцию TextOut, или любые геометрические фигуры, вызывая такие функции, как Ellipse, Rectangle и т.
п.
Специальное приложение Print Manager позволяет организовать очередь печати. Разные приложения могут помещать в эту очередь свои данные (задания на печать), которые будут выводиться на принтер в фоновом режиме в порядке поступления в очередь (пользователь может изменять расположение заданий на печать в очереди). Это приложение описано в документации пользователя операционной системы Windows. Вы также можете найти его описание во втором томе нашей серии книг "Персональный компьютер. Шаг за шагом", посвященный Windows.
Вывод на принтер в операционной системе Windows всегда буферизован, причем под буферизацией понимается не просто использование буфера временного хранения данных, предназначенных для вывода на принтер. Когда приложение вызывает функции рисования GDI, указывая идентификатор контекста принтера, соответствующие команды GDI не выполняются сразу, а накапливаются в специально созданном метафайле. После того как приложение завершит рисование одной страницы документа, созданный метафайл проигрывается в контексте принтера. Именно в этот момент и происходит печать.
Далеко не все принтеры способны рисовать сразу на всем листе бумаги, как на экране видеомонитора. На это способны только лазерные принтеры, которые готовят в своей памяти образ целой страницы и затем печатают эту страницу. Матричные и струйные принтеры могут печатать только в построчном режиме, поэтому проигрывание метафайла на таких принтерах выполняется несколько раз для каждой строки. Всякий раз при проигрывании метафайла в контексте принтера задается область ограничения, соответствующая одной строке.
Механизм построчной печати скрыт от приложений. Поэтому они могут рисовать изображения и писать текст на листе бумаги аналогично тому, как они делают это в окнах приложения. Единственное что требуется, это сообщить GDI о начале печати на новом листе бумаги и о завершении печати листа бумаги (или рисования листа бумаги, если угодно), вызвав соответствующие функции GDI. В дальнейшем мы рассмотрим этот процесс более подробно.
Расстояние между буквами
По умолчанию при выводе текста расстояние между буквами (intercharacter spacing ), заданное в контексте отображения, равно 0. Для вывода текста "вразрядку" приложение может вызвать функцию SetTextCharacterExtra , установив дополнительное расстояние между буквами.Реализация палитры
Процедура реализации палитры заключается в вызове функции RealizePalette :UINT WINAPI RealizePalette(HDC hdc);
Возвращаемое значение равно количеству цветов логической палитры, которое удалось отобразить в системную палитру.
Режим фона
Вы можете установить два режима фона (background mode ) - непрозрачный (OPAQUE ) и прозрачный (TRANSPARENT ), вызвав функцию SetBkMode. По умолчанию выбран режим непрозрачного отображения, при котором в процессе вывода цвет фона удаляется.Например, приложение создало окно с синим фоном и выводит в нем строку текста черного цвета. В этом случае в режиме OPAQUE вы увидите черные буквы внутри горизонтальной полосы белого цвета, имеющей высоту, равную высоте букв. Если в этом режиме нарисовать пунктирную линию черного цвета на синем фоне, то цвет промежутка между штрихами линии будет не синий, а белый.
В прозрачном режиме TRANSPARENT аналогия с листом бумаги синего цвета и черным карандашом будет полная. При выводе текста в описанной выше ситуации вы увидите черные буквы на синем фоне.
Режим MM_TEXT
Режим отображения MM_TEXT устанавливается в контексте отображения по умолчанию. Для этого режима формулы преобразования координат упрощаются:xViewport = (xWindow - xWinOrg) + xViewOrg yViewport = (yWindow - yWinOrg) + yViewOrg
Соответствующая система координат представлена на рис. 2.3 (начало системы координат расположено точно в левом верхнем углу внутренней области окна, рисунок иллюстрирует только направление координатных осей).
Рис. 2.3. Система координат в режиме отображения MM_TEXT
Так как в формуле преобразования не присутствуют переменные xViewExt, yViewExt, xWinExt и yWinExt, в данном режиме преобразования невозможно изменить масштаб осей координат. Поэтому логическая единица длины в режиме отображения MM_TEXT равна физической, т. е. одному пикселу.
Тем не менее, приложение может изменить смещение физической или логической системы координат, изменив, соответственно, значение пар переменных (xViewOrg, yViewOrg) и (xWinOrg,yWinOrg). Для установки смещения можно использовать функции SetViewportOrg и SetWindowOrg.
Функция SetViewportOrg устанавливает смещение физической системы координат:
DWORD WINAPI SetViewportOrg( HDC hdc, // контекст отображения int nXOrigin, // новое значение для xViewOrg int nYOrigin); // новое значение для yViewOrg
Для контекста отображения hdc эта функция устанавливает новое расположение начала физической системы координат. Младшее и старшее слово возвращаемого значения содержат, соответственно, предыдущие x- и y-координаты начала физической системы координат.
С помощью функции SetWindowOrg вы можете установить начало логической системы координат:
DWORD WINAPI SetWindowOrg( HDC hdc, // контекст отображения int nXOrigin, // новое значение для xWinOrg int nYOrigin); // новое значение для yWinOrg
По своему смыслу параметры nXOrigin и nYOrigin являются логическими x- и y-координатами верхнего угла окна, используемого для отображения (так как в формуле они используются со знаком минус).
Как правило, приложения изменяют либо начало физических координат, вызывая функцию SetViewportOrg, либо начало логических координат, вызывая функцию SetWindowOrg, хотя, в принципе, вы можете изменить и то, и другое (если не боитесь запутаться в координатных "сетях").
В программном интерфейсе Windows версии 3.1 есть новые варианты описанных выше двух функций, которые называются SetViewportOrgEx и SetWindowOrgEx . Они отличаются более удобным способом передачи старых координат начала соответствующей системы координат:
BOOL WINAPI SetViewportOrgEx( HDC hdc, // контекст отображения int nXOrigin, // новое значение для xWinOrg int nYOrigin, // новое значение для yWinOrg POINT FAR* lppt); // указатель на структуру POINT
BOOL WINAPI SetWindowOrgEx( HDC hdc, // контекст отображения int nXOrigin, // новое значение для xWinOrg int nYOrigin, // новое значение для yWinOrg POINT FAR* lppt); // указатель на структуру POINT
В структуру, адрес которой передается через параметр lppt, записываются старые координаты начала системы координат. Обе функции возвращают TRUE в случае успеха и FALSE при возникновении ошибки.
В любой момент времени вы можете определить расположение начала физических или логических координат, если воспользуетесь функциями GetViewportOrg и GetWindowOrg (или их более новыми аналогами - GetViewportOrgEx и GetWindowOrgEx).
Функция GetViewportOrg возвращает x- и y-координаты начала физической системы координат для контекста отображения hdc:
DWORD WINAPI GetViewportOrg(HDC hdc);
Младшее и старшее слово возвращаемого значения содержат, соответственно, предыдущие x- и y-координаты начала физической системы координат.
Функция GetWindowOrg возвращает x- и y-координаты начала логической системы координат:
DWORD WINAPI GetWindowOrg(HDC hdc);
Новые функции, появившиеся в Windows версии 3.1, с именами GetViewportOrgEx и GetWindowExtEx записывают координаты начала координат в структуру, адрес которой передается через параметры lppt и lpSize:
BOOL WINAPI GetViewportOrgEx(HDC hdc, POINT FAR* lppt); BOOL WINAPI GetWindowExtEx(HDC hdc, SIZE FAR* lpSize);
Структура SIZE определена для Windows версии 3.1 и описана в файле windows.h следующим образом:
typedef struct tagSIZE { int cx; int cy; } SIZE;
Определены также разнообразные указатели на структуру SIZE:
typedef SIZE* PSIZE; typedef SIZE NEAR* NPSIZE; typedef SIZE FAR* LPSIZE;
Режим отображения
Режим отображения, установленный в контексте отображения, влияет на систему координат. Устанавливая различные режимы отображения , приложение может изменять направление и масштаб координатных осей.По умолчанию в контексте отображения установлен режим отображения MM_TEXT . Для этого режима начало системы координат находится в верхнем левом углу внутренней области окна. Ось x направлена вправо, ось y - вниз. В качестве единицы измерения используется пиксел.
Такой режим отображения удобен для вывода текста. В самом деле, мы читаем текст слева направо и сверху вниз (хотя в некоторых странах текст читают справа налево и снизу вверх).
Иногда удобнее использовать обычную систему координат, в которой ось x направлена слева направо, а ось y - снизу вверх. Вы можете выбрать один из нескольких режимов отображения с таким направлением осей. В качестве единицы измерения можно использовать сотые и тысячные доли дюйма, сотые и десятые доли миллиметра и другие величины.
С помощью функции SetMapMode приложение может установить в контексте режим отображения, удобный для решения той или иной задачи.
В операционной системе Windows NT можно выбрать и другие системы координат, например, с наклонным расположением осей.
Режим растяжения
Приложение Windows может копировать прямоугольные участки изображений bitmap, выполняя при этом масштабирование, т. е. сжатие или растяжение. Для такого копирования может быть использована, например, функция StretchBlt, которую мы рассмотрим в главе, посвященной изображениям bitmap.Режим растяжения (stretching mode ) влияет на способ, с помощью которого происходит масштабирование изображения bitmap. По умолчанию используется режим BLACKONWHITE , при котором два или большее количество соседних пикселов преобразуются в один пиксел при помощи логической операции "ИЛИ". В результате такого преобразования получается черный пиксел, если в исходном изображении любой пиксел из преобразуемой группы имеет черный цвет. Для того чтобы получился белый пиксел, все пикселы исходного изображения в преобразуемой группе пикселов должны быть белого цвета. В режиме BLACKONWHITE черный цвет преобладает над белым цветом.
Вы можете изменить режим растяжения, вызвав функцию SetStretchBltMode . При использовании режима WHITEONBLACK пикселы объединяются при помощи логической операции "ИЛИ", при этом в полученном изображении будет преобладать белый цвет. В режиме COLORONCOLOR в процессе преобразования могут быть полностью удалены отдельные строки или столбцы пикселов, что иногда дает хорошие результаты.
Однако, как правило, масштабирование изображений bitmap приводит к существенному ухудшению качества при любом режиме растяжения.
Режим рисования
Когда вы рисуете что-нибудь на бумаге обычным карандашом или фломастером, цвет получившегося изображения соответствует цвету выбранного вами карандаша или фломастера. Иными словами, цвет копируется из инструмента, выбранного для рисования. Именно такой режим рисования (drawing mode) выбран по умолчанию в контекст отображения. При этом новое изображение полностью замещает (закрашивает) то, что находится под ним.Приложение Windows может выбрать и другие режимы рисования, например, рисование инвертированием цвета фона, рисование черным или белым цветом, рисование с использованием логической операции "ИСКЛЮЧАЮЩЕЕ ИЛИ" или выбрать десятки других вариантов. Например, при выделении участка графического изображения (которое может быть любого цвета) приложение может рисовать рамку, размеры которой определяются перемещениями мыши. Для того чтобы рамка была видна вне зависимости от цвета изображения, при рисовании рамки можно использовать инвертирование цвета фона или операцию "ИСКЛЮЧАЮЩЕЕ ИЛИ".
Для того чтобы выбрать режим рисования , приложение должно использовать функцию SetROP2 .
Режим закрашивания многоугольников
Существует два режима закрашивания сложных самопересекающихся многоугольников (polygon-filling mode ): альтернативный (ALTERNATE ), выбранный в контекст отображения по умолчанию, и режим заполнения (WINDING ). В режиме заполнения область самопересечения закрашивается. В альтернативном режиме закрашиваются только области между нечетными и четными сторонами многоугольника, в результате чего область пересечения может оказаться либо закрашенной, либо незакрашенной.Для изменения режима закрашивания многоугольников предназначена функция SetPolyFillMode .
Режимы MM_ISOTROPIC и MM_ANISOTROPIC
Режимы отображения MM_ISOTROPIC (изотропный) и MM_ANISOTROPIC (анизотропный) допускают изменение направления осей X и Y, а также изменение масштаба осей координат. В изотропном режиме отображения MM_ISOTROPIC масштаб вдоль осей X и Y всегда одинаковый (т. е. для обоих осей используются одинаковые логические единицы длины). Анизотропный режим MM_ANISOTROPIC предполагает использование разных масштабов для разных осей (хотя можно использовать и одинаковые масштабы).Для изменения ориентации и масштаба осей предназначены функции SetViewportExt, SetViewportExtEx, SetWindowExt и SetWindowExtEx.
Функция SetWindowExt устанавливает для формулы преобразования координат значения переменных xWinExt и yWinExt:
DWORD WINAPI SetWindowExt( HDC hdc, // идентификатор контекста отображения int nXExtent, // значение для xWinExt int nYExtent); // значение для yWinExt
Функция SetViewportExt должна использоваться после функции SetWindowExt. Она устанавливает для формулы преобразования координат значения переменных xViewExt и yViewExt:
DWORD WINAPI SetViewportExt( HDC hdc, // идентификатор контекста отображения int nXExtent, // значение для xViewExt int nYExtent); // значение для yViewExt
Обе функции возвращают в младшем и старшем слове предыдущие значения соответствующих переменных для оси X и Y.
Приведенные выше формулы можно использовать для установки отношений xViewExt/xWinExt и yViewExt/yWinExt, определяющих масштаб и направление осей координат (направление осей координат зависит от знака этих отношений).
Функции SetWindowExt передаются значения, соответствующие логическому размеру логического окна, в которое будет выполняться вывод, а функции SetViewportExt - реальные ширина и высота реального окна.
Например, нам надо создать систему координат, в которой начало отсчета расположено в левом нижнем углу окна, ось X направлена слева направо, а ось Y - снизу вверх. Высота и ширина должны изменяться от 0 до 32767 (максимально возможное значение, так как для координат используются 16-разрядные числа).
Если требуется получить одинаковый масштаб по осям X и Y, нужно использовать изотропный режим отображения MM_ISOTROPIC.
Приведем фрагмент кода, создающий необходимый режим отображения.
SetMapMode(hdc, MM_ISOTROPIC); SetWindowExt(hdc, 32767, 32767); SetViewportExt(hdc, cxClient, -cyClient); SetViewportOrg(hdc, 0, cyClient);
В изотропном режиме отображения при изменении размеров окна Windows настроит систему координат таким образом, чтобы масштаб по осям X и Y был одинаковый.
Если ширина окна больше высоты, масштаб по горизонтальной оси настраивается таким образом, что логическое окно будет расположено в левой части внутренней области окна (рис. 2.7).
Рис. 2.7. Изменение масштаба по горизонтали при увеличении ширины окна в изотропном режиме
Если же высота окна больше его ширины, при использовании изотропного режима отображения логическое окно окажется в нижней части внутренней области окна (рис. 2.8).
Рис. 2.8. Изменение масштаба по горизонтали при увеличении высоты окна в изотропном режиме
При использовании анизотропного режима отображения MM_ANISOTROPIC настройка масштаба не выполняется, поэтому логическое окно будет занимать всю внутреннюю поверхность окна при любом изменении размеров этого окна (рис. 2.9 и 2.10).
Рис. 2.9. Изменение масштаба по горизонтали при увеличении ширины окна в анизотропном режиме
Рис. 2.10. Изменение масштаба по горизонтали при увеличении высоты окна в анизотропном режиме
В программном интерфейсе Windows версии 3.1 есть новые функции, предназначенные для изменения масштабов осей. Это функции SetViewportExtEx и SetWindowExtEx :
BOOL WINAPI SetViewportExtEx( HDC hdc, // идентификатор контекста отображения int nXExtent, // значение для xViewExt int nYExtent, // значение для yViewExt SIZE FAR* lpSize); // указатель на структуру SIZE
BOOL WINAPI SetWindowExtEx( HDC hdc, // идентификатор контекста отображения int nXExtent, // значение для xWinExt int nYExtent, // значение для yWinExt SIZE FAR* lpSize); // указатель на структуру SIZE
От функций SetViewportExt и SetWindowExt эти функции отличаются тем, что старые значения переменных, определяющих масштаб преобразования, записываются в структуру SIZE, указатель на которую передается через параметр lpSize.
Изотропный режим отображения удобно использовать в тех случаях, когда надо сохранить установленное отношение масштабов осей X и Y при любом изменении размеров окна, в которое выводится изображение (рис. 2.7 и 2.8).
Анизотропный режим удобен в тех случаях, когда изображение должно занимать всю внутреннюю поверхность окна при любом изменении размеров окна. Соотношение масштабов при этом не сохраняется (рис. 2.9 и 2.10).
Режимы отображения
Теперь, после того как мы рассказали о физических и логических координатах, а также о преобразованиях координат, займемся подробным описанием каждого режима отображения .Рисование DIB
Если отображаемый bmp-файл содержит таблицу цветов, и на предыдущем этапе была создана палитра, ее следует выбрать в контекст отображения и реализовать:hOldPal = SelectPalette(hdc, hPal, FALSE); RealizePalette(hdc);
После этого вы можете нарисовать DIB одним из двух способов.
Первый способ рисования заключается в предварительном преобразовании изображения DIB в изображение DDB с последующим рисованием изображения DDB. Вы уже умеете рисовать изображение DDB, для этого его следует выбрать в специально созданный контекст памяти и затем отобразить функцией BitBlt или StretchBlt.
Для преобразования DIB в DDB вы должны использовать функцию SetDIBits :
int WINAPI SetDIBits( HDC hdc, // контекст отображения HBITMAP hbmp, // изображение DDB UINT uStartScan, // номер первой строки UINT uScanLines, // количество строк const void FAR* lpvBits, // биты изображения BITMAPINFO FAR* lpbmi, // заголовок изображения UINT fuColorUse); // содержимое таблицы цветов
Параметр hdc должен содержать идентификатор контекста отображения, в котором будет отображаться полученное изображение DDB.
Через параметр hbmp следует передать идентификатор битового изображения DDB, совместимого с контекстом hdc. Его можно создать при помощи функции CreateCompatibleBitmap. После преобразования это изображение можно будет использовать для рисования функциями BitBlt или StretchBlt.
Параметр uStartScan задает номер строки сканирования битового изображения, начиная с которого будет выполняться преобразование. Если вам нужно нарисовать все изображение целиком, для этого параметра следует задать значение0.
Параметр uScanLines определяет количество строк сканирования, участвующих в преобразовании. Если нужно преобразовать все изображение, для этого параметра следует указать высоту изображения, взятую из заголовка BITMAPINFOHEADER.
Через параметр lpvBits следует передать указатель на область памяти, содержащую биты изображения в формате DIB.
В процессе преобразования функция SetDIBits использует заголовок bmp-файла BITMAPINFO, указатель на который следует передать через параметр lpbmi.
Последний параметр fuColorUse указывает функции на содержимое таблицы цветов, которая расположена сразу после структуры BITMAPINFOHEADER. Возможны два значения - DIB_RGB_COLORS и DIB_PAL_COLORS.
Если указано значение DIB_RGB_COLORS , таблица цветов содержит RGB-цвета, которые можно использовать для создания палитры. Если же указано значение DIB_PAL_COLORS , таблица цветов содержит 16-битовые ссылки на элементы системной палитры.
Если вы загрузили bmp-файл в память, таблица цветов обычно содержит именно RGB-цвета, поэтому для преобразования и последующего рисования изображения вы должны указать значение DIB_RGB_COLORS.
Возвращаемое функцией SetDIBits значение равно количеству преобразованных строк сканирования или нулю при ошибке.
Поясним процесс рисования на простом примере.
Пусть мы загрузили изображение DIB с шириной wWidth и высотой wHeight. Создаем изображение DDB, совместимое с контекстом отображения hdc и имеющее те же размеры. Для этого воспользуемся функцией CreateCompatibleBitmap :
hbmp = CreateCompatibleBitmap(hdc, wWidth, wHeight);
Создадим также контекст памяти, совместимый с контекстом отображения:
hMemDC = CreateCompatibleDC(hdc);
Далее вызываем функцию SetDIBits, которая преобразует биты изображения DIB и запишет их в созданное нами изображение DDB с идентификатором hbmp:
SetDIBits(hdc, hbmp, 0, wHeight, lpDibBits, (LPBITMAPINFO)lpih, DIB_RGB_COLORS);
Теперь нам нужно нарисовать полученное изображение DDB. Для этого выбираем его в контекст памяти и переносим в контекст отображения, например, функцией BitBlt:
hbmp = (HBITMAP)SelectObject(hMemDC, hbmp); BitBlt(hdc, x, y, wWidth, wHeight, hMemDC, 0, 0, SRCCOPY);
Все! Изображение нарисовано. Теперь можно удалить контекст памяти, не забыв перед этим выбрать в него старое битовое изображение (размером 1х1 пиксел):
DeleteObject(SelectObject(hMemDC, hbmp)); DeleteDC(hMemDC);
Второй способ нарисовать DIB немного проще:
StretchDIBits(hdc, x, y, wWidth, wHeight, 0, 0, wWidth, wHeight, lpDibBits, (LPBITMAPINFO)lpih, DIB_RGB_COLORS, SRCCOPY);
Прототип функции StretchDIBits выглядит несколько громоздко, однако эта функция дополнительно позволяет масштабировать рисуемое изображение. Функция имеет параметры, аналогичные параметрам функций StretchBlt и SetDIBits:
BOOL WINAPI StretchDIBits( HDC hdc, // контекст для рисования int nXDest, // x-координата верхнего левого угла // области рисования int nYDest, // y-координата верхнего левого угла // области рисования int nWidthDest, // новая ширина изображения int nHeightDest, // новая высота изображения int nXSrc, // x-координата верхнего левого угла // исходной области int nYSrc, // y-координата верхнего левого угла // исходной области int nWidthSrc, // ширина исходного изображения int nHeightSrc, // высота исходного изображения const void FAR* lpvBits, // биты изображения BITMAPINFO FAR* lpbmi, // заголовок изображения UINT fuColorUse, // содержимое таблицы цветов DWORD dwRop); // код растровой операции
Возвращаемое значение равно количеству преобразованных строк сканирования или нулю при ошибке.
Рисование дуги эллипса
К сожалению, возможности рисования кривых линий при помощи функций GDI ограничены - единственная функция Arc позволяет нарисовать дугу эллипса или окружности:BOOL WINAPI Arc( HDC hdc, // идентификатор контекста отображения int nxLeft, int nyTop, // верхий левый угол int nxRight, int nyBottom, // правый нижний угол int nxStart, int nyStart, // начало дуги int nxEnd, int nyEnd); // конец дуги
Первый параметр этой функции определяет контекст отображения, в котором будет нарисована дуга. Для объяснения назначения остальных параметров обратимся к рис. 2.13.
Рис. 2.13. Рисование дуги эллипса
Параметры (nxLeft,nyTop) и (nxRight,nyBottom) задают координаты, соответственно, верхнего левого и правого нижнего углов воображаемого прямоугольника, в который вписан эллипс.
Начало дуги эллипса определяется пересечением эллипса с воображаемой прямой линией, проведенной из центра эллипса (xC,yC) в точку (xStart,yStart).
Конец дуги определяется аналогично - как пересечение эллипса с воображаемой прямой линии, проведенной из центра эллипса в точку (xEnd,yEnd).
Дуга рисуется в направлении против часовой стрелки.
Координаты центра эллипса (если это потребуется) можно вычислить следующим образом:
xC = (nxLeft + nxRight) / 2; yC = (nyTop + nyBottom) / 2;
Рисование геометрических фигур
В этом разделе мы расскажем вам об использовании функций, предназначенных для рисования точек, линий, окружностей и других геометрических фигур.Несмотря на то, что в программном интерфейсе 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
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. Рисование эллипса
Рисование линий произвольного стиля
Как мы уже говорили, вы не можете создать перо для рисования пунктирных, штрих-пунктирных или штриховых линий толщиной больше одного пиксела. Однако в некоторых случаях у вас может возникнуть необходимость в рисовании таких линий.В программном интерфейсе 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".
Рисование линий
Приложения Windows могут рисовать прямые и ломаные линии, а также дуги эллипса (и окружности, как частного случая эллипса). Параметры этих линий определяются несколькими атрибутами контекста отображения. Это режим отображения, влияющий на используемую систему координат, цвет фона, режим фона (прозрачный или непрозрачный), режим рисования, цветовая палитра (в режимах, использующих цветовую палитру), перо (может иметь различный цвет, толщину и стиль).Рисование ломаной линии
Функции 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.
Выбор цвета без использования палитры
Приложения, которые не хотят ничего знать про палитры, могут указывать логический цвет изображений, составляя его из 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);
Программирование: Языки - Технологии - Разработка
- Программирование
- Технологии программирования
- Разработка программ
- Работа с данными
- Методы программирования
- IDE интерфейс
- Графический интерфейс
- Программирование интерфейсов
- Отладка программ
- Тестирование программ
- Программирование на Delphi
- Программирование в ActionScript
- Assembler
- Basic
- Pascal
- Perl
- VBA
- VRML
- XML
- Ada
- Lisp
- Python
- UML
- Форт
- Языки программирования