#else
#define _memcpy_ (to,from,sz) hmemcpy ((void huge*) (to), (void huge*) (from), (sz))
#endif
BOOL LoadDIBfromFile (LP_DIB lpDib, LPSTR lpszFileName)
{HFILE hFile;
DWORD dwSize;
BITMAPFILEHEADER bmfh;
// инициализируем возвращаемые данные:
lpDib->hglbDib = NULL;
lpDib->lpDibHdr = (LPBITMAPINFOHEADER)NULL;
lpDib->lpImage = (LPSTR)NULL;
lpDib->uDibFlags = 0;
// открываем файл с битмапом для чтения
hFile = _lopen (lpszFileName, READ);
if (hFile == HFILE_ERROR) return FALSE;
// определяемразмерупакованногобитмапа
dwSize = _llseek (hFile, 0L, 2); _llseek (hFile, 0L, 0);
if (dwSize >= sizeof (bmhf)) dwSize -= sizeof (bmhf);
// выделяем блок для хранения упакованного битмапа
lpDib->lpDibHdr = (LPBITMAPINFOHEADER)GlobalAllocPtr (GHND, dwSize);
if (lpDib->lpDibHdr != (LPBITMAPINFOHEADER)NULL) {
// считываемзаголовокфайла
if ( (_lread (hFile, &bmhf, sizeof (bmhf)) == sizeof (bmhf)) &&
(bmhf.bfType == DIB_SIGNATURE)) {
// если заголовок успешно считан, считываем сам битмап
if (_hread (hFile, lpDib->lpDibHdr, dwSize) == dwSize) {
// и устанавливаем нужные поля структуры _DIB:
lpDib->hglbDib = GlobalPtrHandle (lpDib->lpDibHdr);
lpDib->lpImage = (LPSTR) (
(char huge*) (lpDib->lpDibHdr) + bmhf.bfOffBits - sizeof (bmhf));
lpDib->uDibFlags = DIB_FILE;}}
// если где-то возникла ошибка - освобождаем память
if (lpDib->uDibFlags == 0) {
GlobalFreePtr (lpDib->lpDibHdr);
lpDib->lpDibHdr = (LPBITMAPINFOHEADER)NULL;}}
_lclose (hFile);
return lpDib->uDibFlags ? TRUE : FALSE;}
Следует обратить внимание на то, что в этой процедуре основная часть кода выполняет проверки или связана с несколько избыточным описанием структуры _DIB; в частных случаях вся процедура может свестись к выполнению 3х–4х функций.
По сути близкий к этому случай может быть связан с загрузкой независимых от устройства битмапов из ресурсов приложения. При рассмотрении зависимых от устройства битмапов было отмечено, что функция LoadBitmap, загружающая битмап из ресурсов приложения, возвращает зависимый от устройства битмап, предназначенный для воспроизведения на дисплее. Это может быть неудобно, если битмап должен отображаться, скажем, на принтере. По счастью в ресурсы приложения включается непосредственно независимый от устройства битмап, что позволяет получить к нему доступ с помощью функций FindResource и LoadResource. В результате вы получите указатель на блок памяти, содержащий целиком образ файла битмапа, включая структуру BITMAPFILEHEADER. Останется только вычислить адрес начала данных изображения и адрес информации о битмапе:
// включаемые заголовки и описание структуры _DIB - см. в предыдущем примере
BOOL LoadDIBfromResources (LP_DIB lpDib, HINSTANCE hInstance, LPSTR lpszResName)
{LPBITMAPFILEHEADER lpbmfh;
HRSRC hresDib;
// инициализируемвозвращаемыеданные:
lpDib->hglbDib = NULL;
lpDib->lpDibHdr = (LPBITMAPINFOHEADER)NULL;
lpDib->lpImage = (LPSTR)NULL;
lpDib->uDibFlags = 0;
// ищемнужныйресурс
hresDib = FindResource (hInstance, lpszResName, RT_BITMAP);
if (!hresDib) return FALSE;
// ресурснайден, получаемегохендл
lpDib->hglbDib = LoadResource (hInstance, hresDib);
if (! (lpDib->hglbDib)) return FALSE;
// получаемуказательназагруженныйресурс
lpbmfh = (LPBITMAPFILEHEADER)LockResource (lpDib->hglbDib);
if (lpbmfh != (LPBITMAPFILEHEADER)NULL) {
// заполняемостальныеполяструктуры _DIB:
lpDib->lpDibHdr = (LPBITMAPINFOHEADER) (lpbmfh + 1);
lpDib->lpImage = (char FAR*) (lpbmfh) + bmhf.bfOffBits;
lpDib->uDibFlags = DIB_RESOURCE;}
if (lpDib->uDibFlags == 0) {
#ifndef __NT__
FreeResource (lpDib->hglbDib);
#endif
lpDib->hglbDib = NULL;}
return lpDib->uDibFlags ? TRUE : FALSE;}
Заканчивая рассмотрение функций для загрузки независимых от устройства битмапов из файла или из ресурса приложения, приведем еще одну функцию, освобождающую выделенные ресурсы. Необходимость в этой функции возникает, как только вводится собственная структура _DIB, в которой содержатся хендлы и указатели на выделяемые ресурсы разного типа (блок памяти или ресурс приложения).
{BOOL fResult = FALSE;
switch (lpDib->uDibFlags) {
case DIB_FILE:
if (lpDib->lpDibHdr) GlobalFreePtr (lpDib->lpDibHdr);
fResult = TRUE;
break;
case DIB_RESOURCE:
#ifndef __NT__
if (lpDib->hglbDib) {
UnlockResource (lpDib->hglbDib); // для NT нетребуется
FreeResource (lpDib->hglbDib);
// для NT нетребуется}
#endif
fResult = TRUE;
break;
default:
break;}
// инициализируемструктуру _DIB:
lpDib->hglbDib = NULL;
lpDib->lpDibHdr = (LPBITMAPINFOHEADER)NULL;
lpDib->lpImage = (LPSTR)NULL;
lpDib->uDibFlags = 0;
return fResult;}
Заголовок независимого от устройства битмапа
Непосредственно после заголовка файла битмапа следует информация, описывающая характеристики битмапа — его размеры, количество цветов, используемую палитру, режим сжатия изображения и многое другое. Как уже было отмечено, информация о независимых от устройства битмапах условно делится на две структуры данных: 1) описание самого битмапа и 2) описание используемых битмапом цветов.
В разных версиях Windows были предусмотрены разные форматы описания битмапов, по счастью совместимые снизу–вверх. Имеет смысл обзорно ознакомиться с возможностями битмапов разных версий и изменениями, произошедшими в их описании.
Формат OS/2
В ранних версиях GDI для описания битмапов применялись структуры, совместимые с ранним форматом OS/2. Для описания информации о битмапе применялась структура BITMAPCOREHEADER, а для описания используемых цветов — палитры — массив структур RGBTRIPLE (он необязателен):
typedef struct tagBITMAPCOREHEADER {DWORD bcSize;short bcWidth;short bcHeight;WORD bcPlanes;WORD bcBitCount;} BITMAPCOREHEADER; | typedef struct tagRGBTRIPLE {BYTE rgbtBlue;BYTE rgbtGreen;BYTE rgbtRed;} RGBTRIPLE; |
Сначала рассмотрим структуру BITMAPCOREHEADER, описывающую битмап:
Поле bcSize содержит размер этой структуры (sizeof (BITMAPCOREHEADER)), его значение должно быть равно 12. Поля bcWidth и bcHeight задают размеры данного битмапа. Так как для задания размеров используется целое число со знаком, то максимальный размер битмапа в этого формата равен 32767x32767 пикселей.
Поле bcPlanes указывает количество цветовых планов (плоскостей), используемых битмапом. Его значение для независимого от устройства битмапа всегда должно быть равно1. Поле bcBitCount указывает количество бит, используемых для задания цвета пикселя. Возможно одно из следующих значений:
1 — монохромный битмап
4 — 16ти цветный битмап
8 — 256ти цветный битмап
24 — битмап в истинных цветах (TrueColor).
Все остальные значения для полей bcPlanes и bcBitCount являются недопустимыми. Если битмап имеет 2, 16 или 256 цветов, то непосредственно после структуры BITMAPCOREHEADER следует палитра (palette) — таблица определения цветов в виде массива из 2, 16 или 256 записей типа RGBTRIPLE. Считается, что изображение такого битмапа содержит логические номера цветов для каждого пикселя, а соответствие логического номера истинному цвету задается соответствующей записью в палитре. Каждая запись RGBTRIPLE задает интенсивности красной (red), зеленой (green) и синей (blue) компонент цвета пикселя, в виде числа от 0 до 255. Таким образом возможно описание 16 777 216 возможных цветов из которых строится палитра, используемая битмапом.
Последний вариант, когда битмап имеет 24 бита на пиксель, предполагает, что 24х битовый номер цвета пикселя соответствует истинному цвету, то есть записи из трех компонент основных цветов RGB (структура RGBTRIPLE сама имеет размер 24 бита). Понятно, что в этом случае палитра становится не нужна и в заголовок битмапа она не помещается вовсе.
Часто для удобства вместо структур BITMAPCOREHEADER и массива записей RGBTRIPLE используют объединенную структуру BITMAPCOREINFO, которая просто описывает в качестве полей структуру BITMAPCOREHEADER и массив из одной записи RGBTRIPLE.
typedef struct _BITMAPCOREINFO {
BITMAPCOREHEADER bmciHeader;
RGBTRIPLEbmciColors[1];
} BITMAPCOREINFO;
Такая структура несколько упрощает доступ к описанию битмапа по указателю: при использовании BITMAPCOREHEADER и RGBTRIPLE необходимо манипулировать с двумя указателями, а при использовании BITMAPCOREINFO достаточно только одного — указывающего на начало заголовка. Например, вместо такого фрагмента кода:
LPBITMAPCOREHEADER lpbmch = ...; // считаем, что указатель на заголовок нам дан
LPRGBTRIPLE lprgbt;
lprgbt = (LPRGBTRIPLE) (lpbmch + 1); // получаем указатель на палитру
// для доступа к полям заголовка используем, например lpbmch->bcWidth
// для доступа к палитре используем, например lprgbt[i].rgbtRed;
Можно использовать чуть более простой фрагмент, в котором применяется только один указатель:
LPBITMAPCOREINFOlpbmci = ...; // считаем, что указатель на заголовок нам дан
// для доступа к полям заголовка lpbmci->bmciHeader.bcWidth
// для доступа к палитре lpbmci->bmciColors[i].rgbtRed;
Однако использовать структуру BITMAPCOREINFO при загрузке битмапа не слишком удобно, так как ее полный размер может быть различным, смотря по количеству цветов битмапа (причем он может быть либо меньше, либо больше, чем sizeof (BITMAPCOREINFO) и заведомо не равен ему). Его можно вычислить как размер структуры BITMAPCOREHEADER (или значение поля bcSize) плюс размер таблицы определения цветов: нуль, если поле bcBitCount равно 24, или число цветов, умноженное на размер структуры RGBTRIPLE:
UINTuSizeCoreInfo;
LPBITMAPCOREHEADERlpbmch;
uSizeCoreInfo = lpbmch->bcSize + (
lpbmch->bcBitCount==24 ? 0 : (1 << lpbmch->bcBitCount) * sizeof (RGBTRIPLE));
Непосредственно вслед за структурой BITMAPCOREINFO следуют собственно данные изображения. Их можно найти в DIB–файле как по значению поля bfOffBits заголовка файла BITMAPFILEHEADER, так и считывая их непосредственно после таблицы определения цветов. Анализируя заголовок битмапа можно определить и необходимый размер области для хранения изображения. Изображение хранится по строкам развертки, в каждой строке для задания цвета пикселя отводится bcBitCount последовательных бит. Полная длина строки выравнивается в сторону завышения до ближайшей границы, кратной двойному слову (в зависимых от устройства битмапах строка выравнивалась до четного размера, а в случае DIB — кратного четырем). Строки развертки перечисляются снизу–вверх. Для вычисления размера изображения можно воспользоваться таким фрагментом:
DWORD dwSizeImage;
LPBITMAPCOREHEADER lpbmch; // считаем, что указатель на заголовок нам дан
dwSizeImage = ( (lpbmch->bcWidth * lpbmch->bcBitCount + 31) >> 3) & ~3L;
dwSizeImage *= lpbmch->bcHeight;
В этом фрагменте выполняются следующие действия: сначала вычисляется длина строки развертки в битах (lpbmch->bcWidth * lpbmch->bcBitCount), далее нам надо получить эту длину в двойных словах (то есть деленную на 32) и округленную в большую сторону; затем пересчитать из двойных слов в байты — умножить на 4. Этот процесс можно несколько ускорить — пересчет в число двойных слов с округлением в большую сторону легко проделать по формуле (x + 31)/32, или, используя более быстрые операции, (x+31)>>5, так как 32 это 25. Далее надо умножить на 4, то есть ((x+31)>>5)*4 = ((x+31)>>5)<<2), или, в окончательном варианте, ((x+31)>>3)& (~3): так как при умножении на 4 (сдвиге влево на 2 бита), младшие 2 разряда будут обнулены, то заменяя деление с умножением на сдвиг вправо, мы должны сбросить два младших бита в 0.