В этой главе мы расскажем вам об использовании в приложениях Windows битовых изображений bitmap . Работа с битовыми изображениями, особенно в аппаратно-независимом формате, не самое простое занятие. Программист должен учитывать многочисленные нюансы, уметь работать с цветовыми палитрами и хорошо ориентироваться в контекстах отображения.
В операционной системе Windows используются два формата битовых изображений - аппаратно-зависимый DDB (device-dependent bitmap ) и аппаратно-независимый DIB (device-independent bitmap ).
Согласно определению, данному в документации к SDK, битовое изображение DDB есть набор бит в оперативной памяти, который может быть отображен на устройстве вывода (например, выведен на экран видеомонитора или распечатан на принтере). Внутренняя структура изображения DDB жестко привязана к аппаратным особенностям устройства вывода. Поэтому представление изображения DDB в оперативной памяти полностью зависит от устройства вывода.
Иногда битовые изображения называют растровыми изображениями, подчеркивая тот факт, что его можно рассматривать как совокупность строк растра (горизонтальных линий развертки).
Если бы в Windows можно было работать только с изображениями DDB, было бы необходимо иметь отдельные наборы изображений для каждого типа видеоконтроллера и каждого видеорежима, что, очевидно, крайне неудобно.
Аппаратно-независимое битовое изображение DIB содержит описание цвета пикселов изображения, которое не зависит от особенностей устройства отображения. Операционная система Windows после соответствующего преобразования может отобразить такое изображение на любом устройстве вывода. Несмотря на некоторое замедление процесса вывода по сравнению с выводом изображений DDB, универсальность изображений DIB делает их весьма привлекательными для хранения изображений.
Вместе с Windows версии 3.0 и 3.1 поставляется несколько файлов, имеющих расширение имени bmp. Эти файлы содержат битовые изображения в формате DIB. Версии 2.х операционной системы Windows могли работать только с аппаратно-зависимыми изображениями DDB, что создавало определенные трудности при необходимости отображения последних на различных устройствах вывода.
Создавая приложение, которое должно работать с 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. Векторные изображения можно хранить в виде метафайлов.
Как мы уже говорили, битовые изображения в формате DDB являются аппаратно-зависимыми. Поэтому структура изображения в оперативной памяти зависит от особенностей аппаратуры.
В предыдущих томах "Библиотеки системного программиста" мы использовали изображения DDB и даже приводили исходный текст функции DrawBitmap, с помощью которой можно нарисовать такое изображение на экране.
Как правило, изображения DDB либо загружаются из ресурсов приложения, либо создаются непосредственно в оперативной памяти. Для вывода изображений DDB на экран используются такие функции, как BitBlt и StretchBlt.
Изображения DIB, в отличие от изображений DDB, являются аппаратно-независимыми, поэтому без дополнительного преобразования их нельзя отображать на экране с помощью функций BitBlt и StretchBlt. В операционной системе Windows версий 3.х битовые изображения хранятся в файлах с расширением имени bmp, при этом используется аппаратно-независимый формат DIB.
Самый простой способ использования битовых изображений в приложениях 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, т. е. система координат, принятая для устройства отображения.
Итак, мы загрузили битовое изображение в память и определили его параметры. Теперь наша задача заключается в том, чтобы нарисовать загруженное изображение в окне приложения.
Как мы уже говорили, в программном интерфейсе Windows (а точнее, в программном интерфейсе GDI) нет функции, предназначенной для рисования битовых изображений. Как же быть?
Используется следующая последовательность действий.
Прежде всего надо создать контекст памяти, совместимый с контекстом отображения реального устройства вывода. Для этого следует воспользоваться функцией CreateCompatibleDC.
Далее необходимо выбрать предварительно загруженное битовое изображение в контекст памяти с помощью функции SelectObject, указав ей в качестве параметров идентификатор контекста памяти и идентификатор загруженного изображения.
Затем нужно скопировать биты изображения из контекста памяти в контекст отображения, вызвав функцию BitBlt или StretchBlt. При этом изображение будет нарисовано на устройстве вывода, которое соответствует контексту отображения.
Рассмотрим реализацию этой последовательности действий на примере функции DrawBitmap, которую мы использовали в приложениях, описанных в предыдущих томах "Библиотеки системного программиста":
// ====================================================== // Рисование изображения типа bitmap // ====================================================== #define STRICT #include <windows.h> void DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap) { HBITMAP hbm, hOldbm; HDC hMemDC; BITMAP bm; POINT ptSize, ptOrg; // Создаем контекст памяти, совместимый // с контекстом отображения hMemDC = CreateCompatibleDC(hDC); // Выбираем изображение bitmap в контекст памяти hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap); // Если не было ошибок, продолжаем работу if (hOldbm) { // Для контекста памяти устанавливаем тот же // режим отображения, что используется в // контексте отображения SetMapMode(hMemDC, GetMapMode(hDC)); // Определяем размеры изображения GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&bm); ptSize.x = bm.bmWidth; // ширина ptSize.y = bm.bmHeight; // высота // Преобразуем координаты устройства в логические // для устройства вывода DPtoLP(hDC, &ptSize, 1); ptOrg.x = 0; ptOrg.y = 0; // Преобразуем координаты устройства в логические // для контекста памяти DPtoLP(hMemDC, &ptOrg, 1); // Рисуем изображение bitmap BitBlt(hDC, x, y, ptSize.x, ptSize.y, hMemDC, ptOrg.x, ptOrg.y, SRCCOPY); // Восстанавливаем контекст памяти SelectObject(hMemDC, hOldbm); } // Удаляем контекст памяти DeleteDC(hMemDC); }
В качестве параметров этой функции передается идентификатор контекста отображения hDC, в котором необходимо нарисовать изображение, координаты x и y верхнего левого угла прямоугольной области, в которой будет нарисовано изображение, а также идентификатор самого изображения hBitmap.
Прежде всего функция DrawBitmap создает контекст памяти, совместимый с контекстом отображения, передаваемого через параметр hDC:
hMemDC = CreateCompatibleDC(hDC);
В качестве параметра для этой функции можно использовать значение NULL. В этом случае будет создан контекст памяти, совместимый с экраном видеомонитора. Функция возвращает идентификатор созданного контекста или NULL при ошибке.
Далее функция DrawBitmap выбирает изображение в созданный контекст памяти:
hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap);
Функция SelectObject возвращает идентификатор битового изображения, которое было выбрано в контекст памяти раньше. Так как мы только что создали контекст памяти, мы получим идентификатор битового изображения, выбранного в контекст памяти по умолчанию. Это монохромное изображение, состоящее из одного пиксела. Контекст памяти необходимо удалить после использования. Перед удалением мы должны выбрать в него изображение, которое было выбрано при создании, т. е. изображение с идентификатором hOldbm.
Теперь мы выбрали наше изображение в контекст памяти и готовы выполнить копирование в контекст отображения. Однако перед этим необходимы некоторые подготовительные действия.
Прежде всего нужно сделать так, чтобы в контексте памяти использовался тот же режим отображения, что и в контексте отображения. По умолчанию при создании контекста памяти (как и любого другого контекста) устанавливается режим отображения MM_TEXT. Однако в контексте отображения, идентификатор которого передается функции DrawBitmap, может быть установлен любой режим отображения, например, метрический. Для обеспечения соответствия режимов отображения удобно использовать функции GetMapMode и SetMapMode :
SetMapMode(hMemDC, GetMapMode(hDC));
Функция GetMapMode возвращает код режима отображения, установленного в контексте отображения. Этот код передается в качестве второго параметра функции SetMapMode, которая устанавливает такой же режим отображения в контексте памяти.
Так как функции BitBlt, копирующей биты изображения, необходимо указать координаты прямоугольной области, занимаемой изображением в контексте памяти, следует определить размеры изображения. Это можно сделать с помощью функции GetObject, которую мы только что описали:
GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm);
В данном случае нас интересуют ширина и высота изображения в пикселах:
ptSize.x = bm.bmWidth; // ширина ptSize.y = bm.bmHeight; // высота
Так как в контексте отображения может быть выбран любой режим отображения, размеры изображения необходимо преобразовать в логические, вызвав функцию DPtoLP:
DPtoLP(hDC, &ptSize, 1);
Об этой функции мы рассказывали в разделе, посвященной режимам отображения.
Для копирования битов изображения из контекста памяти в контекст отображения функция DrawBitmap использует функцию BitBlt (читается как "бит-блит"):
BOOL WINAPI BitBlt( HDC hdcDest, // контекст для рисования int nXDest, // x-координата верхнего левого угла // области рисования int nYDest, // y-координата верхнего левого угла // области рисования int nWidth, // ширина изображения int nHeight, // высота изображения HDC hdcSrc, // идентификатор исходного контекста int nXSrc, // x-координата верхнего левого угла // исходной области int nYSrc, // y-координата верхнего левого угла // исходной области DWORD dwRop); // код растровой операции
Функция копирует битовое изображение из исходного контекста hdcSrc в контекст отображения hdcDest. Возвращаемое значение равно TRUE при успешном завершении или FALSE при ошибке.
Размеры копируемого изображения задаются парамерами nWidth и nHeight. Координаты левого верхнего угла изображения в исходном контексте определяются параметрами nXSrc и nYSrc, а в контексте, куда копируется изображение, параметрами nXDest и nYDest.
Последний параметр dwRop определяет растровую операцию, используемую для копирования.
Отметим, что размеры и координаты необходимо задавать в логических единицах, соответствующих выбранному режиму отображения.
В нашем случае изображение копируется из точки (0,0) в физической системе координат в точку (x,y) в логической системе координат. Поэтому перед копированием изображения необходимо выполнить преобразование физических координат (0,0) в логические, вызвав функцию DPtoLP:
ptOrg.x = 0; ptOrg.y = 0; DPtoLP(hMemDC, &ptOrg, 1);
После этого можно вызывать функцию BitBlt:
BitBlt(hDC, x, y, ptSize.x, ptSize.y, hMemDC, ptOrg.x, ptOrg.y, SRCCOPY);
Эта функция копирует битовое изображение, имеющее размеры ptSize, из контекста памяти hMemDC в контекст отображения hDC. При этом логические координаты верхнего левого угла изображения в контексте памяти находятся в структуре ptOrg. Координаты верхнего левого угла прямоугольной области в контексте отображения, куда будет копироваться изображение, передаются через параметры x и y.
После рисования битового изображения функция DrawBitmap выбирает в контекст памяти первоначально выбранное при его создании изображение, состоящее из одного монохромного пиксела, и затем удаляет контекст памяти:
SelectObject(hMemDC, hOldbm); DeleteDC(hMemDC);
В качестве кода растровой операции используется константа SRCCOPY. При этом цвет пикселов копируемого изображения полностью замещает цвет соответствующих пикселов контекста отображения.
Мы уже говорили об использовании растровых операций для рисования линий и закрашивания областей. При рисовании битовых изображений вы также можете использовать растровые операции, причем цвет полученного изображения в зависимости от выбранной растровой операции может определяться цветом исходного изображения, цветом поверхности, на которой выполняется рисование, и цветом кисти, выбранной в контекст отображения.
Чаще всего используется код растровой операции SRCCOPY. В этом случае цвет кисти, выбранной в контекст отображения, не имеет значения, так как ни цвет кисти, ни цвет фона не влияют на цвет нарисованного изображения.
Однако вы можете использовать и другие коды растровых операций (всего их 256). В этом случае для вычисления цвета полученного после рисования пиксела можно выбрать практически любое логическое выражение, учитывающее цвет фона, цвет кисти и цвет пиксела изображения.
В файле windows.h описаны константы для наиболее полезных кодов растровых операций. Мы опишем эти константы вместе с соответствующими логическими выражениями. При этом символом S мы будем обозначать цвет исходного изображения, символом D - цвет фона на котором выполняется рисование, и P - цвет кисти, выбранной в контекст отображения.
Код растровой операции |
Логическое выражение |
Описание |
SRCCOPY |
S |
Исходное изображение копируется в
контекст отображения |
SRCPAINT |
S | D |
Цвет полученного изображения
определяется при помощи логической операции ИЛИ
над цветом изображения и цветом фона |
SRCAND |
S & D |
Цвет полученного изображения
определяется при помощи логической операции И
над цветом изображения и цветом фона |
SRCINVERT |
S ^ D |
Цвет полученного изображения
определяется при помощи логической операции
ИСКЛЮЧАЮЩЕЕ ИЛИ над цветом изображения и цветом
фона |
SRCERASE |
S & ~D |
Цвет фона инвертируется, затем
выполняется операция И над результатом и цветом
исходного изображения |
NOTSRCCOPY |
~S |
После рисования цвет изображения
получается инвертированием цвета исходного
изображения |
NOTSRCERASE |
~(S | D) |
Цвет полученного изображения
получается инвертированием результата
логической операции ИЛИ над цветом изображения и
цветом фона |
MERGECOPY |
P & S |
Выполняется логическая операции И над
цветом исходного изображения и цветом кисти |
MERGEPAINT |
~S | D |
Выполняется логическая операции ИЛИ над
инвертированным цветом исходного изображения и
цветом фона |
PATCOPY |
P |
Выполняется копирование цвета кисти |
PATPAINT |
P | ~S | D |
Цвет кисти комбинируется с
инвертированным цветом исходного изображения,
при этом используется логическая операция ИЛИ.
Полученный результат комбинируется с цветом
фона, также с помощью логической операции ИЛИ |
PATINVERT |
P ^ D |
Цвет полученного изображения
определяется при помощи логической операции
ИСКЛЮЧАЮЩЕЕ ИЛИ над цветом кисти и цветом фона |
DSTINVERT |
~D |
Инвертируется цвет фона |
BLACKNESS |
0 |
Область закрашивается черным цветом |
WHITENESS |
1 |
Область закрашивается белым цветом |
Остальные коды приведены в документации, которая поставляется вместе с SDK. В более удобном виде все коды растровых операций приведены в приложении к книге "Developing Windows 3.1 Application whit Microsoft C/C++" (автором которой является Brent Rector). Мы не будем воспроизводить полную таблицу для кодов растровых операций, так как во-первых, эти операции редко используются, а во-вторых, таблица занимает много места.
Для рисования битовых изображений можно использовать вместо функции BitBlt функцию StretchBlt, с помощью которой можно выполнить масштабирование (сжатие или растяжение) битовых изображений:
BOOL WINAPI StretchBlt( HDC hdcDest, // контекст для рисования int nXDest, // x-координата верхнего левого угла // области рисования int nYDest, // y-координата верхнего левого угла // области рисования int nWidthDest, // новая ширина изображения int nHeightDest, // новая высота изображения HDC hdcSrc, // идентификатор исходного контекста int nXSrc, // x-координата верхнего левого угла // исходной области int nYSrc, // y-координата верхнего левого угла // исходной области int nWidthSrc, // ширина исходного изображения int nHeightSrc, // высота исходного изображения DWORD dwRop); // код растровой операции
Параметры этой функции аналогичны параметрам функции BitBlt, за исключением того, что ширина и высота исходного и полученного изображения должна определяться отдельно. Размеры исходного изображения (логические) задаются параметрами nWidthSrc и nHeightSrc, размеры нарисованного изображения задаются параметрами nWidthDest и nHeightDest.
Возвращаемое значение равно TRUE при успешном завершении или FALSE при ошибке.
Следует упомянуть также еще одну функцию, которая сама по себе не может рисовать битовые изображения, но часто используется для закраски прямоугольных областей экрана. Эта функция имеет имя PatBlt :
BOOL WINAPI PatBlt( HDC hdc, // контекст для рисования int nX, // x-координата верхнего левого угла // закрашиваемой области int nY, // y-координата верхнего левого угла // закрашиваемой области int nWidth, // ширина области int nHeight, // высота области DWORD dwRop); // код растровой операции
При использовании этой функции вы можете закрашивать области экрана с использованием следующих кодов растровых операций: PATCOPY, PATINVERT, PATPAINT, DSTINVERT, BLACKNESS, WHITENESS.
Возвращаемое функцией PatBlt значение равно TRUE при успешном завершении или FALSE при ошибке.
Другой способ работы с изображениями в формате 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 при ошибке.
Если вы создали изображение 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, после чего создать его заново.
Приведем пример приложения BMPLOGO, которое демонстрирует работу с битовыми изображениями в формате DDB. Это приложение создает одно битовое изображение в памяти и рисует его в верхнем левом углу окна, а также (что на наш взгляд, самое интересное), рисует текстовую строку с оттенением (рис. 4.3).
Рис. 4.3. Приложение BMPLOGO
Заметим, что для рисования слова "Bitmap" мы не пользовались какими-либо особенностями шрифтов True Type. Эффект теней был получен при помощи двухкратного вывода монохромного битового изображения со сдвигом, причем каждый раз мы использовали различные растровые операции.
Исходный текст главного модуля приложения представлен в листинге 4.1.
Листинг 4.1. Файл bmplogo/bmplogo.cpp
// ---------------------------------------- // Приложение BMPLOGO // Демонстрация различных способов рисования // битовых изображений DDB // ---------------------------------------- #define STRICT #include <windows.h> #include <windowsx.h> #include <mem.h> // Прототипы функций 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 <windows.h> 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
Как мы уже говорили, битовые изображения DDB имеют один существенный недостаток - они "привязаны" к конкретному типу устройства вывода. В графической оболочке Presentation Manager операционной системы OS/2 впервые был использован аппаратно-независимый формат для хранения изображений, который называется DIB.
В операционной системе Windows версии 3.0 этот формат получил свое дальнейшее развитие. В частности, была добавлена возможность хранения изображения в компрессованном виде. К сожалению, использованный алгоритм компрессии дает хорошие результаты только для таких изображений, которые содержат большие одинаково закрашенные области. Операционная система Windows NT позволяет использовать новые форматы изображений DIB и произвольные методы компрессии, такие как, например, JPEG (очень эффективный метод компрессии графических изображений, при котором можно ценой потери качества получить практически любую степень сжатия).
В программном интерфейсе Windows нет функции, специально предназначенной для рисования битовых изображений DIB. Поэтому если вы создаете приложение, которое должно отображать bmp-файлы на экране или печатать их на принтере, вам придется иметь дело с внутренней структурой этих файлов. Мы познакомим вас с форматом всех необходимых структур данных, имеющих отношение к bmp-файлам.
Если рассматривать структуру bmp-файлов в общем, для нас интересны два формата. Первый формат используется в Windows версий 3.х, второй - в графической оболочке Presentation Manager операционной системы OS/2 версий 1.х. Первый из этих форматов является естественным для приложений Windows. Что же касается второго формата, такие приложения, как Paint Brush, способны конвертировать его в стандартный формат Windows.
Ваше приложение может либо полностью проигнорировать формат bmp-файлов оболочки Presentation Manager, либо (что лучше) автоматически преобразовывать его к формату Windows, как это делают стандартные приложения 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 |
не используется |
Если содержимое поля biClrUsed отлично от нуля, используется таблица цветов уменьшенного размера. В ней описаны только те цвета, которые содержатся в изображении.
Оболочка 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-файлов 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 бита.
Процесс рисования изображений 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. Для обработки этих сообщений можно использовать алгоритм, описанный в главе "Цвет и цветовые палитры".
Вы можете загрузить в оперативную память весь 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-файлы.
Процесс создания цветовой палитры несложен. Вначале надо убедиться в том, что 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);
Если отображаемый 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); // код растровой операции
Возвращаемое значение равно количеству преобразованных строк сканирования или нулю при ошибке.
Если перед вами встанет задача преобразования 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.
После всего сказанного выше у вас могло сложиться впечатление, что процедура рисования изображений 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 <windows.h> #include <windowsx.h> #include <commdlg.h> #include <mem.h> #pragma hdrstop #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 <windows.h> #include <windowsx.h> #include <commdlg.h> #include <mem.h> #pragma hdrstop #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.
Эта функция выводит на экран стандартную диалоговую панель "Open", с помощью которой можно выбрать bmp-файл.
Функция возвращает идентификатор открытого файла или NULL, если пользователь отказался от выбора.
С помощью этой функции открытый файл читается в глобальный блок памяти. Этот блок заказывается непосредственно перед чтением, причем его размер соответствует размеру файла.
В случае успеха функция возвращает идентификатор блока памяти, содержащий файл, причем этот блок является перемещаемым.
Во время чтения курсор мыши принимает форму песочных часов, так как процедура чтения может быть достаточно длительной.
Так как размер bmp-файлов обычно больше 64 Кбайт, для чтения используется функция _hread.
Функция выводит информацию о загруженном изображении DIB. Идентификатор блока памяти, содержащего файл изображения, передается этой функции в качестве первого параметра. Через второй параметр передается размер файла, определенный функцией чтения файла DIBReadFile.
Функция получает в качестве параметра идентификатор блока памяти, содержащего загруженный файл изображения DIB. Она определяет его формат и выполняет проверку полей структур заголовка.
Если в качестве параметра этой функции было передано значение NULL, она возвращает код ошибки -2. Если не удалось зафиксировать блок памяти, возвращается код ошибки -1.
Функция проверяет первые два байта bmp-файла. Если они не содержат значения 0x4d42, возвращается код ошибки 0.
Далее функция определяет формат заголовка. Для изображения в стандарте Windows проверяются поля biPlanes, biBitCount и biCompression структуры BITMAPINFOHEADER, а для изображения в стандарте Presentation Manager - поля biPlanes и biBitCount структуры BITMAPCOREHEADER.
Эта функция определяет размер таблицы цветов. Если изображение DIB содержит таблицу цветов уменьшенного размера, возвращается размер таблицы из поля biClrUsed структуры BITMAPINFOHEADER. В противном случае размер таблицы цветов определяется исходя из содержимого поля biBitCount структуры BITMAPINFOHEADER (количество бит памяти, определяющих цвет пиксела).
Функция возвращает высоту изображения DIB в пикселах как значение поля biHeight структуры BITMAPINFOHEADER.
Функция возвращает ширину изображения DIB в пикселах как значение поля biWidth структуры BITMAPINFOHEADER.
Эта функция возвращает адрес массива бит изображения. В качестве параметра ей необходимо передать адрес зафиксированного блока памяти, содержащего загруженный файл изображения.
Эта функция рисует изображение DIB. Первый параметр определяет контекст отображения. Второй и третий - координаты верхнего левого угла прямоугольной области, в которой будет нарисовано изображение. Через последний параметр передается идентификатор блока памяти, содержащего загруженное изображение DIB.
Рисование выполняется при помощи функции StretchDIBits.
Функция аналогична предыдущей, однако перед рисованием выполняется преобразование DIB в DDB. Далее преобразованное изображение выводится через контекст памяти в контекст отображения при помощи функции BitBlt.
Функция создает логическую палитру на базе таблицы цветов изображения 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