Базы данныхИнтернетКомпьютерыОперационные системыПрограммированиеСетиСвязьРазное
Поиск по сайту:
Подпишись на рассылку:

Назад в раздел

Untitled

4. Битовые изображения

4.1. Битовые изображения в формате DDB

4.2. Приложение BMPLOGO

4.3. Битовые изображения в формате DIB

4.4. Рисование изображений DIB

4.5. Приложение BMPINFO

В этой главе мы расскажем вам об использовании в приложениях 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. Векторные изображения можно хранить в виде метафайлов.

4.1. Битовые изображения в формате DDB

Как мы уже говорили, битовые изображения в формате 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, т. е. система координат, принятая для устройства отображения.

Рисование изображения DDB

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

Как мы уже говорили, в программном интерфейсе Windows (а точнее, в программном интерфейсе GDI) нет функции, предназначенной для рисования битовых изображений. Как же быть?

Используется следующая последовательность действий.

Прежде всего надо создать контекст памяти, совместимый с контекстом отображения реального устройства вывода. Для этого следует воспользоваться функцией CreateCompatibleDC.

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

Затем нужно скопировать биты изображения из контекста памяти в контекст отображения, вызвав функцию BitBlt или StretchBlt. При этом изображение будет нарисовано на устройстве вывода, которое соответствует контексту отображения.

Рассмотрим реализацию этой последовательности действий на примере функции DrawBitmap , которую мы использовали в приложениях, описанных в предыдущих томах "Библиотеки системного программиста":

// ======================================================
// Рисование изображения типа bitmap
// ======================================================
#define STRICT
#include <windows.h>

void DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap)
{
  HBITMAP hbm, hOldbm;
  HDC hMemDC;
  BITMAP bm;
  POINT  ptSize, ptOrg;

  // Создаем контекст памяти, совместимый
  // с контекстом отображения
  hMemDC = CreateCompatibleDC(hDC);

  // Выбираем изображение bitmap в контекст памяти
  hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap);

  // Если не было ошибок, продолжаем работу
  if (hOldbm)
  {
    // Для контекста памяти устанавливаем тот же
    // режим отображения, что используется в
    // контексте отображения
    SetMapMode(hMemDC, GetMapMode(hDC));

    // Определяем размеры изображения
    GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&bm);

    ptSize.x = bm.bmWidth;   // ширина
    ptSize.y = bm.bmHeight;  // высота

    // Преобразуем координаты устройства в логические
    // для устройства вывода
    DPtoLP(hDC, &ptSize, 1);

    ptOrg.x = 0;
    ptOrg.y = 0;

    // Преобразуем координаты устройства в логические
    // для контекста памяти
    DPtoLP(hMemDC, &ptOrg, 1);

    // Рисуем изображение bitmap
    BitBlt(hDC, x, y, ptSize.x, ptSize.y,
           hMemDC, ptOrg.x, ptOrg.y, SRCCOPY);

    // Восстанавливаем контекст памяти
    SelectObject(hMemDC, hOldbm);
  }

  // Удаляем контекст памяти
  DeleteDC(hMemDC);
}

В качестве параметров этой функции передается идентификатор контекста отображения hDC, в котором необходимо нарисовать изображение, координаты x и y верхнего левого угла прямоугольной области, в которой будет нарисовано изображение, а также идентификатор самого изображения hBitmap.

Прежде всего функция DrawBitmap создает контекст памяти, совместимый с контекстом отображения, передаваемого через параметр hDC:

hMemDC = CreateCompatibleDC(hDC);

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

Далее функция DrawBitmap выбирает изображение в созданный контекст памяти:

hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap);

Функция SelectObject возвращает идентификатор битового изображения, которое было выбрано в контекст памяти раньше. Так как мы только что создали контекст памяти, мы получим идентификатор битового изображения, выбранного в контекст памяти по умолчанию. Это монохромное изображение, состоящее из одного пиксела. Контекст памяти необходимо удалить после использования. Перед удалением мы должны выбрать в него изображение, которое было выбрано при создании, т. е. изображение с идентификатором hOldbm.

Теперь мы выбрали наше изображение в контекст памяти и готовы выполнить копирование в контекст отображения. Однако перед этим необходимы некоторые подготовительные действия.

Прежде всего нужно сделать так, чтобы в контексте памяти использовался тот же режим отображения, что и в контексте отображения. По умолчанию при создании контекста памяти (как и любого другого контекста) устанавливается режим отображения MM_TEXT. Однако в контексте отображения, идентификатор которого передается функции DrawBitmap, может быть установлен любой режим отображения, например, метрический. Для обеспечения соответствия режимов отображения удобно использовать функции GetMapMode и SetMapMode :

SetMapMode(hMemDC, GetMapMode(hDC));

Функция GetMapMode возвращает код режима отображения, установленного в контексте отображения. Этот код передается в качестве второго параметра функции SetMapMode, которая устанавливает такой же режим отображения в контексте памяти.

Так как функции BitBlt , копирующей биты изображения, необходимо указать координаты прямоугольной области, занимаемой изображением в контексте памяти, следует определить размеры изображения. Это можно сделать с помощью функции GetObject, которую мы только что описали:

GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm);

В данном случае нас интересуют ширина и высота изображения в пикселах:

ptSize.x = bm.bmWidth;   // ширина
ptSize.y = bm.bmHeight;  // высота

Так как в контексте отображения может быть выбран любой режим отображения, размеры изображения необходимо преобразовать в логические, вызвав функцию DPtoLP:

DPtoLP(hDC, &ptSize, 1);

Об этой функции мы рассказывали в разделе, посвященной режимам отображения.

Для копирования битов изображения из контекста памяти в контекст отображения функция DrawBitmap использует функцию BitBlt (читается как "бит-блит"):

BOOL WINAPI BitBlt(
  HDC hdcDest,  // контекст для рисования
  int nXDest,   // x-координата верхнего левого угла
                //    области рисования
  int nYDest,   // y-координата верхнего левого угла
                //    области рисования
  int nWidth,   // ширина изображения
  int nHeight,  // высота изображения
  HDC hdcSrc,   // идентификатор исходного контекста
  int nXSrc,    // x-координата верхнего левого угла
                //    исходной области
  int nYSrc,    // y-координата верхнего левого угла
                //    исходной области
  DWORD dwRop); // код растровой операции

Функция копирует битовое изображение из исходного контекста hdcSrc в контекст отображения hdcDest. Возвращаемое значение равно TRUE при успешном завершении или FALSE при ошибке.

Размеры копируемого изображения задаются парамерами nWidth и nHeight. Координаты левого верхнего угла изображения в исходном контексте определяются параметрами nXSrc и nYSrc, а в контексте, куда копируется изображение, параметрами nXDest и nYDest.

Последний параметр dwRop определяет растровую операцию, используемую для копирования.

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

В нашем случае изображение копируется из точки (0,0) в физической системе координат в точку (x,y) в логической системе координат. Поэтому перед копированием изображения необходимо выполнить преобразование физических координат (0,0) в логические, вызвав функцию DPtoLP:

ptOrg.x = 0;
ptOrg.y = 0;
DPtoLP(hMemDC, &ptOrg, 1);

После этого можно вызывать функцию BitBlt:

BitBlt(hDC, x, y, ptSize.x, ptSize.y,
   hMemDC, ptOrg.x, ptOrg.y, SRCCOPY);

Эта функция копирует битовое изображение, имеющее размеры ptSize, из контекста памяти hMemDC в контекст отображения hDC. При этом логические координаты верхнего левого угла изображения в контексте памяти находятся в структуре ptOrg. Координаты верхнего левого угла прямоугольной области в контексте отображения, куда будет копироваться изображение, передаются через параметры x и y.

После рисования битового изображения функция DrawBitmap выбирает в контекст памяти первоначально выбранное при его создании изображение, состоящее из одного монохромного пиксела, и затем удаляет контекст памяти:

SelectObject(hMemDC, hOldbm);
DeleteDC(hMemDC);

В качестве кода растровой операции используется константа SRCCOPY. При этом цвет пикселов копируемого изображения полностью замещает цвет соответствующих пикселов контекста отображения.

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

Чаще всего используется код растровой операции SRCCOPY. В этом случае цвет кисти, выбранной в контекст отображения, не имеет значения, так как ни цвет кисти, ни цвет фона не влияют на цвет нарисованного изображения.

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

В файле windows.h описаны константы для наиболее полезных кодов растровых операций. Мы опишем эти константы вместе с соответствующими логическими выражениями. При этом символом S мы будем обозначать цвет исходного изображения, символом D - цвет фона на котором выполняется рисование, и P - цвет кисти, выбранной в контекст отображения.

Код растровой операцииЛогическое выражение Описание
SRCCOPY SИсходное изображение копируется в контекст отображения 
SRCPAINT S | DЦвет полученного изображения определяется при помощи логической операции ИЛИ над цветом изображения и цветом фона
SRCAND S & DЦвет полученного изображения определяется при помощи логической операции И над цветом изображения и цветом фона
SRCINVERT S ^ DЦвет полученного изображения определяется при помощи логической операции ИСКЛЮЧАЮЩЕЕ ИЛИ над цветом изображения и цветом фона
SRCERASE S & ~D Цвет фона инвертируется, затем выполняется операция И над результатом и цветом исходного изображения
NOTSRCCOPY ~SПосле рисования цвет изображения получается инвертированием цвета исходного изображения
NOTSRCERASE ~(S | D) Цвет полученного изображения получается инвертированием результата логической операции ИЛИ над цветом изображения и цветом фона
MERGECOPY P & S Выполняется логическая операции И над цветом исходного изображения и цветом кисти
MERGEPAINT ~S | DВыполняется логическая операции ИЛИ над инвертированным цветом исходного изображения и цветом фона
PATCOPY PВыполняется копирование цвета кисти
PATPAINT P | ~S | D Цвет кисти комбинируется с инвертированным цветом исходного изображения, при этом используется логическая операция ИЛИ. Полученный результат комбинируется с цветом фона, также с помощью логической операции ИЛИ
PATINVERT P ^ DЦвет полученного изображения определяется при помощи логической операции ИСКЛЮЧАЮЩЕЕ ИЛИ над цветом кисти и цветом фона
DSTINVERT ~DИнвертируется цвет фона
BLACKNESS 0Область закрашивается черным цветом
WHITENESS 1Область закрашивается белым цветом

Остальные коды приведены в документации, которая поставляется вместе с SDK. В более удобном виде все коды растровых операций приведены в приложении к книге "Developing Windows 3.1 Application whit Microsoft C/C++" (автором которой является Brent Rector). Мы не будем воспроизводить полную таблицу для кодов растровых операций, так как во-первых, эти операции редко используются, а во-вторых, таблица занимает много места.

Для рисования битовых изображений можно использовать вместо функции BitBlt функцию StretchBlt , с помощью которой можно выполнить масштабирование (сжатие или растяжение) битовых изображений:

BOOL WINAPI StretchBlt(
  HDC hdcDest,      // контекст для рисования
  int nXDest,       // x-координата верхнего левого угла
                    //    области рисования
  int nYDest,       // y-координата верхнего левого угла
                    //    области рисования
  int nWidthDest,   // новая ширина изображения
  int nHeightDest,  // новая высота изображения
  HDC hdcSrc,       // идентификатор исходного контекста
  int nXSrc,        // x-координата верхнего левого угла
                    //    исходной области
  int nYSrc,        // y-координата верхнего левого угла
                    //    исходной области
  int nWidthSrc,    // ширина исходного изображения
  int nHeightSrc,   // высота исходного изображения
  DWORD dwRop);     // код растровой операции

Параметры этой функции аналогичны параметрам функции BitBlt, за исключением того, что ширина и высота исходного и полученного изображения должна определяться отдельно. Размеры исходного изображения (логические) задаются параметрами nWidthSrc и nHeightSrc, размеры нарисованного изображения задаются параметрами nWidthDest и nHeightDest.

Возвращаемое значение равно TRUE при успешном завершении или FALSE при ошибке.

Следует упомянуть также еще одну функцию, которая сама по себе не может рисовать битовые изображения, но часто используется для закраски прямоугольных областей экрана. Эта функция имеет имя PatBlt :

BOOL WINAPI PatBlt(
  HDC hdc,      // контекст для рисования
  int nX,       // x-координата верхнего левого угла
                //    закрашиваемой области
  int nY,       // y-координата верхнего левого угла
                //    закрашиваемой области
  int nWidth,   // ширина области
  int nHeight,  // высота области
  DWORD dwRop); // код растровой операции

При использовании этой функции вы можете закрашивать области экрана с использованием следующих кодов растровых операций: PATCOPY, PATINVERT, PATPAINT, DSTINVERT, BLACKNESS, WHITENESS.

Возвращаемое функцией PatBlt значение равно TRUE при успешном завершении или FALSE при ошибке.

Создание изображений в памяти

Другой способ работы с изображениями в формате 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

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

4.2. Приложение BMPLOGO

Приведем пример приложения 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

4.3. Битовые изображения в формате DIB

Как мы уже говорили, битовые изображения 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

Формат 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 Размер таблицы цветов
12
416
8256
24не используется

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

Формат bmp-файлов Presentation Manager

Оболочка Presentation Manager операционной системы OS/2 использует другой формат bmp-файла (рис. 4.5).

Рис. 4.5. Формат bmp-файла для Presentaton Manager операционной системы OS/2 версии 1.х

В самом начале файла расположена структура BITMAPFILEHEADER. Ее формат полностью соответствует формату аналогичной структуры bmp-файлов операционной системы Windows. В этой структуре, в частности, есть информация о смещении области битов изображения относительно начала файла.

После структуры BITMAPFILEHEADER в файле располагается структура BITMAPCOREINFO, содержащая структуру BITMAPCOREHEADER и таблицу цветов в виде массива структур RGBTRIPLE :

typedef struct tagBITMAPCOREINFO
{
   BITMAPCOREHEADER bmciHeader;
   RGBTRIPLE        bmciColors[1];
} BITMAPCOREINFO;
typedef BITMAPCOREINFO*      PBITMAPCOREINFO;
typedef BITMAPCOREINFO FAR* LPBITMAPCOREINFO;

Формат структуры BITMAPCOREHEADER приведен ниже:

typedef struct tagBITMAPCOREHEADER
{
   DWORD bcSize;
   short bcWidth;
   short bcHeight;
   WORD  bcPlanes;
   WORD  bcBitCount;
} BITMAPCOREHEADER;
typedef BITMAPCOREHEADER*      PBITMAPCOREHEADER;
typedef BITMAPCOREHEADER FAR* LPBITMAPCOREHEADER;

Приведем описание отдельных полей этой структуры.

ПолеОписание
bcSizeРазмер структуры BITMAPCOREHEADER в байтах
bcWidthШирина битового изображения в пикселах
bcHeightВысота битового изображения в пикселах
bcPlanesКоличество плоскостей в битовом изображении. Содержимое этого поля должно быть равно 1
bcBitCountКоличество битов на один пиксел. Может быть равно 1, 4, 8 или 24

Таблица цветов в bmp-файле в формате Presentation Manager расположена после структуры BITMAPCOREHEADER и представляет собой массив структур RGBTRIPLE , содержащих RGB-компоненты цвета:

typedef struct tagRGBTRIPLE
{
   BYTE  rgbtBlue;
   BYTE  rgbtGreen;
   BYTE  rgbtRed;
} RGBTRIPLE;
typedef RGBTRIPLE FAR* LPRGBTRIPLE;

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

wClrUsed = 1 << bcBitCount;

Биты изображения

Формат области битов изображения одинаковый для некомпрессованых bmp-файлов Windows и bmp-файлов 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 бита.

4.4. Рисование изображений DIB

Процесс рисования изображений DIB включает в себя несколько этапов.

Сначала необходимо загрузить bmp-файл в оперативную память и убедиться в том, что этот файл действительно содержит изображение DIB. Ваше приложение может полностью проигнорировать bmp-файлы в формате Presentation Manager (как это делает, например, приложение Paintbrush в Windows версии 3.1) или выполнить их преобразование в формат Windows, что намного лучше. Следует также проверить формат заголовка BITMAPINFOHEADER.

Затем нужно определить размер таблицы цветов (если она есть). Если в DIB есть таблица цветов, ее следует преобразовать в палитру. Непосредственно перед рисованием изображения DIB созданная палитра должна быть выбрана в контекст отображения и реализована. Если bmp-файл содержит изображение с высоким цветовым разрешением, в файле нет таблицы цветов. В этом случае нет необходимости создавать палитру.

После создания палитры следует определить адрес битов изображения. Напомним, что смещение битов изображения находится в поле bfOffBits структуры BITMAPFILEHEADER. Если содержимое этого поля равно нулю, можно вычислить адрес битов изображения исходя из размера заголовков и размера таблицы цветов.

В заключение считанное и проверенное изображение DIB можно нарисовать, использовав один из двух способов.

Первый способ заключается в преобразовании изображения DIB в изображение DDB с помощью функции SetDIBits. Полученное таким образом изображение DDB может быть выбрано в контекст памяти и нарисовано обычным способом при помощи функции BitBlt или StretchBlt.

Второй способ основан на использовании функции StretchDIBits, которая сама выполняет необходимые преобразования, однако в некоторых случаях работает медленнее функции BitBlt.

Если изображение DIB содержит таблицу цветов и устройство вывода способно работать с цветовыми палитрами, ваше приложение должно обрабатывать сообщения WM_PALETTECHANGED и WM_QUERYNEWPALETTE. Для обработки этих сообщений можно использовать алгоритм, описанный в главе "Цвет и цветовые палитры".

Загрузка bmp-файла и проверка заголовков

Вы можете загрузить в оперативную память весь bmp-файл сразу или вначале только заголовки, а затем таблицу цветов и биты изображений. В приложении BMPINFO, рисующем изображения DIB в своем окне, мы использовали первый способ, отведя для загрузки bmp-файла один сплошной блок глобальной памяти.

Составляя программу чтения bmp-файла в память, не следует забывать о том, что размер файла, а следовательно и размер нужного для его загрузки блока памяти, практически всегда превышает 64 Кбайт. Поэтому для чтения такого файла лучше всего использовать функцию _hread , позволяющую прочитать сразу весь файл в один блок памяти любого (теоретически) размера.:

_hread(hfDIBFile, lpBuf, *dwFileSize);

Мы уже пользовались этой функцией для перекодировки файла из OEM в ANSI.

Прочитав файл в память, следует убедиться, что его первые два байта содержат значение 0x4d42 ("BM"). Если это так, нужно определить формат bmp-файла. Для этого следует проанализировать содержимое поля biSize, расположенное сразу после заголовка BITMAPFILEHEADER. Для файлов в формате Windows в этом поле должно быть значение 40, что соответствует размеру структуры BITMAPINFOHEADER. Для файлов в формате Presentation Manager в этом поле должно находиться значение 12 (размер структуры BITMAPCOREHEADER).

Ваше приложение может отвергнуть файл в формате Presentation Manager, и это не будет большим недостатком для приложения Windows. В случае необходимости bmp-файлы Presentation Manager могут быть преобразованы в формат Windows, например, с помощью приложения Paintbrush.

Убедившись в том, что вы загрузили bmp-файл в формате Windows, следует проверить содержимое полей структуры BITMAPINFOHEADER.

Следует проверить поля biPlanes, biBitCount и biCompression. Вы можете использовать для проверки следующие критерии:

ПолеКритерии проверки
biPlanesДолжно содержать значение 1
biBitCount Может быть равно 1, 4, 8 или 24.Вы можете столкнуться с новыми 16- и 32-битовыми форматами файлов DIB, используемых в Windows NT. Для них в этом поле могут находиться также значения 16 и 32. Если ваше приложение не умеет обрабатывать такие файлы, данную ситуацию следует рассматривать как ошибочную
biCompressionМожет принимать одно из следующих значений: BI_RGB, BI_RLE4, BI_RLE8.При использовании метода компрессии BI_RLE4 содержимое поля biBitCount должно быть равно 4. При использовании метода компрессии BI_RLE8 содержимое поля biBitCount должно быть равно 8.Ваше приложение может ограничиться обработкой bmp-файлов в формате BI_RGB, как это делает, например, приложение Paintbrush

Можно было бы проверить содержимое и других полей структуры BITMAPINFOHEADER, однако это необязательно, так как они не содержат критической информации. Проверка "с пристрастием" может привести к тому, что пользователи будут думать, будто ваше приложение не умеет читать такие файлы, с которыми легко справляются другие приложения.

Итак, подводя итоги, можно выдать следующие рекомендации:

смело игнорируйте bmp-файлы в формате Presentation Manager, а если вы не можете так поступить, преобразуйте их в формат Windows;

в структуре BITMAPINFOHEADER проверяйте только поля biPlanes, biBitCount и biCompression;

так как метод компрессии RLE4 и RLE8 используются редко и не приводит к значительной экономии памяти, ваше приложение может не поддерживать компрессованные bmp-файлы.

Создание цветовой палитры

Процесс создания цветовой палитры несложен. Вначале надо убедиться в том, что 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);

Рисование DIB

Если отображаемый bmp-файл содержит таблицу цветов, и на предыдущем этапе была создана палитра, ее следует выбрать в контекст отображения и реализовать:

hOldPal = SelectPalette(hdc, hPal, FALSE);
RealizePalette(hdc);

После этого вы можете нарисовать DIB одним из двух способов.

Первый способ рисования заключается в предварительном преобразовании изображения DIB в изображение DDB с последующим рисованием изображения DDB. Вы уже умеете рисовать изображение DDB, для этого его следует выбрать в специально созданный контекст памяти и затем отобразить функцией BitBlt или StretchBlt.

Для преобразования DIB в DDB вы должны использовать функцию SetDIBits :

int WINAPI SetDIBits(
  HDC hdc,                 // контекст отображения
  HBITMAP hbmp,            // изображение DDB
  UINT uStartScan,         // номер первой строки
  UINT uScanLines,         // количество строк
  const void FAR* lpvBits, // биты изображения
  BITMAPINFO FAR* lpbmi,   // заголовок изображения
  UINT fuColorUse);        // содержимое таблицы цветов

Параметр hdc должен содержать идентификатор контекста отображения, в котором будет отображаться полученное изображение DDB.

Через параметр hbmp следует передать идентификатор битового изображения DDB, совместимого с контекстом hdc. Его можно создать при помощи функции CreateCompatibleBitmap. После преобразования это изображение можно будет использовать для рисования функциями BitBlt или StretchBlt.

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

Параметр uScanLines определяет количество строк сканирования, участвующих в преобразовании. Если нужно преобразовать все изображение, для этого параметра следует указать высоту изображения, взятую из заголовка BITMAPINFOHEADER.

Через параметр lpvBits следует передать указатель на область памяти, содержащую биты изображения в формате DIB.

В процессе преобразования функция SetDIBits использует заголовок bmp-файла BITMAPINFO, указатель на который следует передать через параметр lpbmi.

Последний параметр fuColorUse указывает функции на содержимое таблицы цветов, которая расположена сразу после структуры BITMAPINFOHEADER. Возможны два значения - DIB_RGB_COLORS и DIB_PAL_COLORS.

Если указано значение DIB_RGB_COLORS , таблица цветов содержит RGB-цвета, которые можно использовать для создания палитры. Если же указано значение DIB_PAL_COLORS , таблица цветов содержит 16-битовые ссылки на элементы системной палитры.

Если вы загрузили bmp-файл в память, таблица цветов обычно содержит именно RGB-цвета, поэтому для преобразования и последующего рисования изображения вы должны указать значение DIB_RGB_COLORS.

Возвращаемое функцией SetDIBits значение равно количеству преобразованных строк сканирования или нулю при ошибке.

Поясним процесс рисования на простом примере.

Пусть мы загрузили изображение DIB с шириной wWidth и высотой wHeight. Создаем изображение DDB, совместимое с контекстом отображения hdc и имеющее те же размеры. Для этого воспользуемся функцией CreateCompatibleBitmap :

hbmp = CreateCompatibleBitmap(hdc, wWidth, wHeight);

Создадим также контекст памяти, совместимый с контекстом отображения:

hMemDC = CreateCompatibleDC(hdc);

Далее вызываем функцию SetDIBits, которая преобразует биты изображения DIB и запишет их в созданное нами изображение DDB с идентификатором hbmp:

SetDIBits(hdc, hbmp, 0, wHeight,
  lpDibBits, (LPBITMAPINFO)lpih, DIB_RGB_COLORS);

Теперь нам нужно нарисовать полученное изображение DDB. Для этого выбираем его в контекст памяти и переносим в контекст отображения, например, функцией BitBlt:

hbmp = (HBITMAP)SelectObject(hMemDC, hbmp);
BitBlt(hdc, x, y, wWidth, wHeight,
       hMemDC, 0, 0, SRCCOPY);

Все! Изображение нарисовано. Теперь можно удалить контекст памяти, не забыв перед этим выбрать в него старое битовое изображение (размером 1х1 пиксел):

DeleteObject(SelectObject(hMemDC, hbmp));
DeleteDC(hMemDC);

Второй способ нарисовать DIB немного проще:

StretchDIBits(hdc,
   x, y, wWidth, wHeight,
   0, 0, wWidth, wHeight,
   lpDibBits, (LPBITMAPINFO)lpih,
   DIB_RGB_COLORS, SRCCOPY);

Прототип функции StretchDIBits выглядит несколько громоздко, однако эта функция дополнительно позволяет масштабировать рисуемое изображение. Функция имеет параметры, аналогичные параметрам функций StretchBlt и SetDIBits:

BOOL WINAPI StretchDIBits(
  HDC hdc,          // контекст для рисования
  int nXDest,       // x-координата верхнего левого угла
                    //    области рисования
  int nYDest,       // y-координата верхнего левого угла
                    //    области рисования
  int nWidthDest,   // новая ширина изображения
  int nHeightDest,  // новая высота изображения
  int nXSrc,        // x-координата верхнего левого угла
                    //    исходной области
  int nYSrc,        // y-координата верхнего левого угла
                    //    исходной области
  int nWidthSrc,    // ширина исходного изображения
  int nHeightSrc,   // высота исходного изображения
  const void FAR* lpvBits, // биты изображения
  BITMAPINFO FAR* lpbmi,   // заголовок изображения
  UINT fuColorUse,         // содержимое таблицы цветов
  DWORD dwRop);            // код растровой операции

Возвращаемое значение равно количеству преобразованных строк сканирования или нулю при ошибке.

Преобразование DDB в DIB

Если перед вами встанет задача преобразования DDB в DIB (например, для последующей записи изображения в bmp-файл), вам не обойтись без функции GetDIBits :

int WINAPI GetDIBits(
  HDC hdc,                 // контекст отображения
  HBITMAP hbmp,            // изображение DDB
  UINT uStartScan,         // номер первой строки
  UINT uScanLines,         // количество строк
  void FAR* lpvBits,       // биты изображения
  BITMAPINFO FAR* lpbmi,   // заголовок изображения
  UINT fuColorUse);        // содержимое таблицы цветов

Параметры этой функции полностью аналогичны параметрам функции SetDIBits, однако действие прямо противоположное. Функция преобразует биты изображения в формат DIB и записывает их по адресу, заданному параметром lpvBits. Дополнительно заполняется заголовок lpbmi (если параметр lpvBits указан как NULL, функция ограничивается заполнением заголовка изображения)

Если вы собираетесь сохранить изображение DIB в bmp-файле, вы должны самостоятельно сформировать заголовок файла BITMAPFILEHEADER.

4.5. Приложение BMPINFO

После всего сказанного выше у вас могло сложиться впечатление, что процедура рисования изображений DIB значительно труднее, чем процедура рисования файлов DDB. В самом деле, для отображения содержимого bmp-файла вы должны считать его в память, проверить формат всех структур, при необходимости создать палитру и следить за ее изменениями со стороны других приложений. В момент рисования вам нужно подготовить значительное количество структур, указателей и параметров. Увы, для рисования изображений DIB в программном интерфейсе GDI операционной системы Windows версии 3.1 нет ничего более удобного, чем описанные нами функции.

Большинство приложений, тем не менее, нуждается в рисовании изображений DIB. Для иллюстрации методов работы с такими изображениями мы подготовили приложение BMPINFO. Это приложение умеет загружать bmp-файлы любых "легальных" форматов Windows и Presentation Manager версии 1.x, и выводит соответствующую информацию из заголовков этих файлов, однако оно способно рисовать только некомпрессованные изображения в формате Windows. Как мы уже говорили, в большинстве случаев это как раз именно то, что нужно.

В меню "File" есть строки "Open", "Info..." и, конечно, "Exit". С помощью строки "Open" вы можете выбрать bmp-файл. Если это некомпрессованный файл в формате Windows, он рисуется в окне приложения (рис. 4.7).

Рис. 4.7. Главное окно приложения BMPINFO

Выбрав из меню "File" строку "Info...", вы можете просмотреть информацию о файле, такую, как размер файла и заголовка, формат файла, размер изображения в пикселах и т. д. (рис. 4.8).

Рис. 4.8. Информация о bmp-файле

Если выбранный bmp-файл имеет формат Presentation Manager, на экране появится диалоговая панель, аналогичная изображенной на рис. 4.8.

Основной файл исходных текстов приложения BMPINFO приведен в листинге 4.6.


Листинг 4.6. Файл bmpinfo/bmpinfo.cpp


// ----------------------------------------
// Приложение BMPINFO
// Просмотр и анализ bmp-файлов в формате DIB
// ----------------------------------------

#define STRICT
#include <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.0n"
            "(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*.bmp;*.dib;*.rleAny Files*.*";

  // Идентификатор открываемого файла
  HFILE hf;

  // Инициализация имени выбираемого файла
  // не нужна, поэтому создаем пустую строку
  szFile[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%ldn", biSize);
  wsprintf(szBuf1, "Размер файла, байт:    t%ldn",
     dwFileSize);
  lstrcat(szBuf, szBuf1);
  wsprintf(szBuf1, 
    "Смещение изображения, байт:t%ldn", bfOffset);
  lstrcat(szBuf, szBuf1);

  // В зависимости от формата DIB (PM или Windows)
  // выводим различную информацию из заголовка файла
  if((nDIBType == WINRGB_DIB) ||
     (nDIBType == WINRLE4_DIB) ||
     (nDIBType == WINRLE8_DIB))
  {
    wsprintf(szBuf1,  "nBMP для Windowsn", biSize);
    lstrcat(szBuf, szBuf1);
    lpDIBInfoHeader =
      (LPBITMAPINFOHEADER)(lpDIBPtr +
      sizeof(BITMAPFILEHEADER));

    wsprintf(szBuf1, "Размер:t%ldx%ldn",
      lpDIBInfoHeader->biWidth, lpDIBInfoHeader->biHeight);
    lstrcat(szBuf, szBuf1);

    wsprintf(szBuf1, "Бит на пиксел:t%dn",
      lpDIBInfoHeader->biBitCount);
    lstrcat(szBuf, szBuf1);

    wNumColors = DIBNumColors(lpDIBPtr);
    wsprintf(szBuf1, "Таблица цветов:t%dn", wNumColors);
    lstrcat(szBuf, szBuf1);

    if(lpDIBInfoHeader->biCompression == BI_RGB)
    {
      lstrcat(szBuf, "Без компрессииn");
    }
    else if(lpDIBInfoHeader->biCompression == BI_RLE4)
    {
      lstrcat(szBuf, "Компрессия RLE4n");
    }
    else if(lpDIBInfoHeader->biCompression == BI_RLE8)
    {
      lstrcat(szBuf, "Компрессия RLE8n");
    }
  }

  // Для файлов DIB в формате PM
  else
  {
    wsprintf(szBuf1,
      "nBMP для Presentation Managern", biSize);
    lstrcat(szBuf, szBuf1);
    lpDIBCoreHeader =
      (LPBITMAPCOREHEADER)(lpDIBPtr +
      sizeof(BITMAPFILEHEADER));

    wsprintf(szBuf1, "Размер:t%dx%dn",
      lpDIBCoreHeader->bcWidth, lpDIBCoreHeader->bcHeight);
    lstrcat(szBuf, szBuf1);

    wsprintf(szBuf1, "Бит на пиксел:t%dn",
      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.

DIBSelectFile

Эта функция выводит на экран стандартную диалоговую панель "Open", с помощью которой можно выбрать bmp-файл.

Функция возвращает идентификатор открытого файла или NULL, если пользователь отказался от выбора.

DIBReadFile

С помощью этой функции открытый файл читается в глобальный блок памяти. Этот блок заказывается непосредственно перед чтением, причем его размер соответствует размеру файла.

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

Во время чтения курсор мыши принимает форму песочных часов, так как процедура чтения может быть достаточно длительной.

Так как размер bmp-файлов обычно больше 64 Кбайт, для чтения используется функция _hread.

DIBInfo

Функция выводит информацию о загруженном изображении DIB. Идентификатор блока памяти, содержащего файл изображения, передается этой функции в качестве первого параметра. Через второй параметр передается размер файла, определенный функцией чтения файла DIBReadFile.

DIBType

Функция получает в качестве параметра идентификатор блока памяти, содержащего загруженный файл изображения DIB. Она определяет его формат и выполняет проверку полей структур заголовка.

Если в качестве параметра этой функции было передано значение NULL, она возвращает код ошибки -2. Если не удалось зафиксировать блок памяти, возвращается код ошибки -1.

Функция проверяет первые два байта bmp-файла. Если они не содержат значения 0x4d42, возвращается код ошибки 0.

Далее функция определяет формат заголовка. Для изображения в стандарте Windows проверяются поля biPlanes, biBitCount и biCompression структуры BITMAPINFOHEADER, а для изображения в стандарте Presentation Manager - поля biPlanes и biBitCount структуры BITMAPCOREHEADER.

DIBNumColors

Эта функция определяет размер таблицы цветов. Если изображение DIB содержит таблицу цветов уменьшенного размера, возвращается размер таблицы из поля biClrUsed структуры BITMAPINFOHEADER. В противном случае размер таблицы цветов определяется исходя из содержимого поля biBitCount структуры BITMAPINFOHEADER (количество бит памяти, определяющих цвет пиксела).

DIBHeight

Функция возвращает высоту изображения DIB в пикселах как значение поля biHeight структуры BITMAPINFOHEADER.

DIBWidth

Функция возвращает ширину изображения DIB в пикселах как значение поля biWidth структуры BITMAPINFOHEADER.

DIBFindBits

Эта функция возвращает адрес массива бит изображения. В качестве параметра ей необходимо передать адрес зафиксированного блока памяти, содержащего загруженный файл изображения.

DIBPaint

Эта функция рисует изображение DIB. Первый параметр определяет контекст отображения. Второй и третий - координаты верхнего левого угла прямоугольной области, в которой будет нарисовано изображение. Через последний параметр передается идентификатор блока памяти, содержащего загруженное изображение DIB.

Рисование выполняется при помощи функции StretchDIBits.

DIBPaintBlt

Функция аналогична предыдущей, однако перед рисованием выполняется преобразование DIB в DDB. Далее преобразованное изображение выводится через контекст памяти в контекст отображения при помощи функции BitBlt.

DIBCreatePalette

Функция создает логическую палитру на базе таблицы цветов изображения DIB, возвращая идентификатор созданной палитры или NULL при ошибке. Если DIB не содержит таблицу цветов, палитра не создается.

Прототипы всех описанных выше функций, а также необходимые константы и типы данных описаны в файле dib.hpp (листинг 4.9).


Листинг 4.9. Файл bmpinfo/dib.hpp


#define WINRGB_DIB  1
#define WINRLE4_DIB 2
#define WINRLE8_DIB 3
#define PM_DIB  10

typedef HGLOBAL HDIB;
typedef unsigned char huge* LPDIB;

HFILE DIBSelectFile(void);
HDIB  DIBReadFile(HFILE hfDIBFile, DWORD *dwFileSize);
BOOL  DIBInfo(HDIB hDib, DWORD dwFileSize);
int   DIBType(HDIB hDib);
WORD  DIBNumColors(LPDIB lpDib);
WORD  DIBHeight(LPDIB lpDib);
WORD  DIBWidth(LPDIB lpDib);
HPALETTE DIBCreatePalette(HDIB hDIB);
BOOL  DIBPaint(HDC hDC, int x, int y, HDIB hDIB);

Обратите внимание на тип LPDIB, который описан как указатель типа huge:

typedef unsigned char huge* LPDIB;

Это необходимо для правильной адресации внутри массива данных, размер которого превосходит 64 Кбайт.

Файл определения ресурсов приложения BMPINFO приведен в листинге 4.10.


Листинг 4.10. Файл bmpinfo/bmpinfo.rc


#include "bmpinfo.hpp"

APP_MENU MENU 
BEGIN
  POPUP "&File"
    BEGIN
      MENUITEM "&Open",          CM_FILEOPEN
      MENUITEM "&Info...",       CM_FILEINFO
      MENUITEM SEPARATOR
      MENUITEM "E&xit",          CM_FILEEXIT
    END

  POPUP "&Help"
    BEGIN
      MENUITEM "&About...",      CM_HELPABOUT
    END
END

Файл определения модуля приложения BMPINFO вы сможете найти в листинге 4.11.


Листинг 4.11. Файл bmpinfo/bmpinfo.def


; =============================
; Файл определения модуля
; =============================
NAME        BMPINFO
DESCRIPTION 'Приложение BMPINFO, (C) 1994, Frolov A.V.'
EXETYPE     windows
STUB        'winstub.exe'
STACKSIZE   8120
HEAPSIZE    1024
CODE        preload moveable discardable
DATA        preload moveable multiple



  • Главная
  • Новости
  • Новинки
  • Скрипты
  • Форум
  • Ссылки
  • О сайте




  • Emanual.ru – это сайт, посвящённый всем значимым событиям в IT-индустрии: новейшие разработки, уникальные методы и горячие новости! Тонны информации, полезной как для обычных пользователей, так и для самых продвинутых программистов! Интересные обсуждения на актуальные темы и огромная аудитория, которая может быть интересна широкому кругу рекламодателей. У нас вы узнаете всё о компьютерах, базах данных, операционных системах, сетях, инфраструктурах, связях и программированию на популярных языках!
     Copyright © 2001-2024
    Реклама на сайте