Win32 API содержит специальную функцию CreateDIBSection, которая создает хендл DDB битмапа (HBITMAP), ассоциированный не с DDB, а с независимым от устройства битмапом. Таким образом существует возможность выполнения над DIB всех операций, типичных для DDB. Так, например, можно получить HBITMAP, соответствующий DIB и выбрать его в контекст устройства.
HBITMAP CreateDIBSection (hdc, lpbmi, nColorUse, ppvBits, hSection, dwOffset); 1
Параметр hdc задает хендл контекста устройства, информация о цветах и палитре которого используется когда параметр nColorUse равен DIB_PAL_COLORS.
Параметр lpbmi является указателем на структуру BITMAPINFO, содержащую заголовок битмапа и, при необходимости, палитру или маски (для HiColor режимов). BITMAPINFOHEADER, являющийся частью BITMAPINFO содержит информацию об организации битмапа и его размерах.
С помощью параметра nColorUse задается способ использования палитры. Значение DIB_RGB_COLORS указывает, что палитра битмапа содержит таблицу записей RGBQUAD (RGBTRIPLE), а значение DIB_PAL_COLORS указывает, что вместо палитры битмапа размещен массив целых чисел, являющихся индексами цветов в системной палитре.
Параметр ppvBits является указателем на переменную типа LPVOID. В эту переменную будет записан указатель на начало данных изображения.
Два последних параметра: hSection и dwOffset используются, если битмап содержится в проецируемом в память файле. В этом случае hSection является хендлом проецирования, возвращенном функцией CreateFileMapping, а dwOffset — смещение от начала файла до данных изображения. В описании указывается, что функция CreateDIBSection требует, что бы значение dwOffset было кратно 4 (длина строки растра в DIB всегда выравнивается на границу двойного слова). Если в проецируемом файле содержится так называемый «Packed DIB», то есть битмап без заголовка файла, то смещение до начала данных изображения само собой будет кратно 4 байтам[12].
Однако в нормальных файлах битмапов заголовок файла присутствует. Он описывается структурой BITMAPFILEHEADER, которая имеет размер 14 байт. Очевидно, что 14 не кратно 4. И, как следствие, для большинства битмапов суммарный размер заголовка файла, заголовка битмапа и данных о цветах (палитры или масок) не может быть кратен 4 (!). Размер структуры BITMAPINFOHEADER равен 40. Суммарный размер обоих заголовков равен 54 и не кратен 4. Палитра, состоящая из записей RGBAUAD по 4 байта каждая, либо маски цветов — три двойных слова никак не могут выровнять конец заголовка по границе двойного слова. В тоже время, если при вызове функции CreateDIBSection задать величину dwOffset не кратную 4, то функция вернет NULL, хотя код ошибки (возвращаемый функцией GetLastError) не будет установлен. Как результат — обычные битмапы в виде файлов нельзя спроецировать в память и передать функции CreateDIBSection.
В итоге функция CreateDIBSection может легко применяться для создания нового DIB — в этом случае hSection и dwOffset следует задать равными 0. Тогда GDI сам создаст необходимое проецирование и вернет хендл битмапа. При необходимости сохранения DIB в виде файла можно с помощью функции GetObject прочитать информацию о DIB–секции:
DIBSECTION ds; // 1
int GetObject (hbmpDIBSection, sizeof (DIBSECTION), &ds);
Структура DIBSECTION содержит следующую информацию:
typedef struct _DIBSECTION {// 1
BITMAP dsBm;
BITMAPINFOHEADER dsBmih;
DWORD dsBitfields[ 3 ];
HANDLE dshSection;
DWORDdsOffset;
} DIBSECTION;
Поле dsBm содержит структуру BITMAP, описывающую секцию как DDB; В этой структуре можно прочитать поле bmBits (в примере выше это будет ds.dsBm.bmBits), которое является указателем на данные изображения DIB–секции. Этот указатель совпадает с тем, который возвращается в параметре ppvBits при вызове функции CreateDIBSection и может быть использован функциями работы с DIB.
Поле dsBmih описывает секцию как DIB; В основном значения полей этой структуры совпадают с теми, которые были указаны при создании секции. Однако, если вы не вычисляли сами размер данных изображения перед вызовом CreateDIBSection, то GDI сам вычислит нужное значение и возвратит его в поле biSizeImage (в примере выше это будет ds.dsBmih.biSizeImage).
Массив dsBitfields содержит маски цветов; они заполняются в зависимости от числа цветов и установленного режима сжатия. Подробнее о масках см. «Формат Win32 (Windows NT 3.x)», стр.58.
Поля dshSection и dsOffset повторяют значения, указанные при вызове функции CreateDIBSection. Если вы указали нулевые значения, то и эти поля также будут нулевыми, несмотря на то, что система сама создает проецирование.
При использовании структуры DIBSECTION нужно следить за тем, что бы вы не создавали DIB–секцию для битмапов в формате OS/2. Непосредственно GDI эту работу выполнит без затруднений, но при этом функция GetObject возвратит в структуре DIBSECTION не BITMAPINFOHEADER, а BITMAPCOREHEADER, другого размера и с другими полями. Если вам придется все–же работать с битмапами OS/2, то заодно придется описать и собственную структуру, аналогичную DIBSECTION; лучше всего просто превратить ее в union, содержащий вариант для Windows и для OS/2.
Если же вы собираетесь использовать CreateDIBSection для редактирования уже существующего DIB, то стоит воспользоваться одним из двух возможных способов: а) создать временное проецирование и скопировать в него битмап, пропустив заголовок файла или б) загрузить DIB обычным образом, создать пустую DIB–секцию, скопировать в нее изображение (скажем, с помощью SetDIBitsToDevice) и освободить первоначально загруженный DIB.
Внимание! Если вы вызывали CreateDIBSection с нулевыми значениями hSection и dwOffset, то при удалении созданной секции с помощью DeleteObject система сама удалит созданное проецирование (вам оно недоступно, так как GetObject возвращает также нули в полях dshSection и dsOffset). Но если вы сами создали проецирование, то вы обязаны сами его удалить.
При работе с DIB–секциями часто возникает необходимость получить палитру, используемую этой секцией. Типичный случай — сохранение DIB–секции в виде файла: если число цветов меньше или равно 256, то такая секция обязательно содержит палитру. Причем в этом случае нужна палитра не в виде структуры LOGPALETTE или массива записей PALETTENTRY, а в виде массива записей RGBQUAD. Для этого предназначена пара функций:
UINTGetDIBColorTable (hdc, uStartIndex, cEntries, lprgbColors); 1
UINTSetDIBColorTable (hdc, uStartIndex, cEntries, lprgbColors); 1
Обратите внимание — функции используют не хендл DIB–секции, а хендл совместимого контекста устройства, в который должна быть выбрана DIB–секция.
Практические примеры:
1) Создание пустой DIB–секции 1:
struct {// не ‘BITMAPINFO bmi’ - нам надо зарезервировать место под палитру
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[ 256+3 ]; // в BITMAPINFO используется bmiColors[1]
} bmi;
LPVOIDlpData;
HDChdcDisplay;
HDC hdcMem;
HBITMAP hbmpDibSection;
int nFirstCol;
int nColors; // число цветов в системной палитре
PALETTEENTRY pe[ 256 ]; // системнаяпалитра
int i;
hdcDisplay = GetWindowDC ( (HWND)0L);
// создаем DIB–секцию, для чего полностью заполняем bmi, включая маски и палитру
bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = GetDeviceCaps (hdcDisplay, HORZRES); // пустьразмер DIB
bmi.bmiHeader.biHeight = GetDeviceCaps (hdcDisplay, VERTRES); // совпадаетсэкраном
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount =
GetDeviceCaps (hdcDisplay, BITSPIXEL) * GetDeviceCaps (hdcDisplay, PLANES);
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biSizeImage = 0L; // аэтопустьGDI вычисляет
bmi.bmiHeader.biXPelsPerMeter =
GetDeviceCaps (hdcDisplay, HORZRES)*1000 / GetDeviceCaps (hdcDisplay, HORZSIZE);
bmi.bmiHeader.biYPelsPerMeter =
GetDeviceCaps (hdcDisplay, VERTRES)*1000 / GetDeviceCaps (hdcDisplay, VERTSIZE);
bmi.bmiHeader.biClrUsed = 0;
bmi.bmiHeader.biClrImportant = 0;
// обнулимпалитру
ZeroMemory ( (LPVOID)bmi.bmiColors, sizeof (bmi.bmiColors));
// решим, будем–ли мы задавать маски цветов для режимов 16 и 32 bpp
// если задавать, то только стандартные 5–5–5, 5–6–5 или 8–8–8 и указать BI_BITFIELDS
// (даже на WindowsNT 4.0 CreateDIBSectionработала только со стандартными масками_
// можно и не задавать; битмапы 16 и 32 bppможно создать и как BI_RGB
// В ДАННОМ ПРИМЕРЕ БУДЕМ ЗАДАВАТЬ РЕЖИМ BI_BITFIELDS
nFirstCol = 0;
switch (bmi.bmiHeader.biBitCount) {
case 16:
bmi.bmiColors[0].rgbGreen = 124; // red: 0x7C00
bmi.bmiColors[1].rgbGreen = 3; // green: 0x03E0
bmi.bmiColors[1].rgbBlue = 224;
bmi.bmiColors[2].rgbBlue = 31; // blue: 0x001F
bmi.bmiHeader.biCompression = BI_BITFIELDS;
nFirstCol = 3;
break;
case 32:
bmi.bmiColors[0].rgbRed = 255; // red: 0x00FF0000
bmi.bmiColors[1].rgbGreen = 255; // green: 0x0000FF00
bmi.bmiColors[2].rgbBlue = 255; // blue: 0x000000FF
bmi.bmiHeader.biCompression = BI_BITFIELDS;
nFirstCol = 3;
break;}
// проверим, нужно–ли назначать битмапу палитру?
nColors = GetDeviceCaps (hdcDisplay, SIZEPALETTE);
// для 16, 24 и 32 nColorsбудет равен 0
if (nColors) {
GetSystemPaletteEntries (hdcDisplay, 0, nColors, pe);
for (i =0; i < nColors; i++) {
bmi.bmiColors[ i + nFirstCol ].rgbRed = pe[i].peRed;
bmi.bmiColors[ i + nFirstCol ].rgbGreen = pe[i].peGreen;
bmi.bmiColors[ i + nFirstCol ].rgbBlue = pe[i].peBlue;}
bmi.bmiHeader.biClrUsed = nColors;}
// создаем секцию по полученному описанию
hbmpDibSection = CreateDIBSection (
hdcDisplay, (LPBITMAPINFO)&bmi, DIB_RGB_COLORS, &lpData, (HANDLE)0L, 0);
// заполним секцию каким–либо изображением
if (hbmpDibSection) {
hdcMem = CreateCompatibleDC (hdcDisplay);
SelectObject (hdcMem, hbmpDibSection);
// собственно здесь и выполняется редактирование DIB–секции
BitBlt (
hdcMem, 0,0, bmi.bmiHeader.biWidth,bmi.bmiHeader.biHeight,
hdcDisplay, 0,0,
SRCCOPY);
DeleteDC (hdcMem);}
ReleaseDC ( (HWND)0L, hdcDisplay);
// hbmpDibSection оставляем для использования в дальнейшем
2) Сохранение DIB–секции в виде .bmp файла 1:
HBITMAP hbmpDibSection; // этот хендл мы получаем из предыдущего примера
BITMAPFILEHEADER bmfh; // заголовок файла битмапа
DIBSECTION ds; // информация о битмапе
HANDLEhf; // хендл файла в котором будет записан DIB
int nColors; // число цветов в палитре битмапа
RGBQUAD rgbs[ 256 ]; // палитра, заполняется если nColors != 0
HDC hdcDisplay;
HDC hdcMem;
DWORD dwWritten;
// получаем кое-какую информацию о записанном битмапе
GetObject (hbmpDibSection, sizeof (ds), &ds);
// определяемразмерпалитры
hdcDisplay = GetWindowDC ( (HWND)0L);
hdcMem = CreateCompatibleDC (hdcDisplay);
SelectObject (hdcMem, hbmpDibSection);
ReleaseDC ( (HWND)0L, hdcDisplay);
nColors = ds.dsBmih.biClrUsed ? ds.dsBmih.biClrUsed :
(ds.dsBmih.biBitCount <= 8 ? 1<<ds.dsBmih.biBitCount : 0);
if (nColors) {
// палитраприсутствует
nColors = GetDIBColorTable (hdcMem, 0, nColors, rgbs);
ds.dsBmih.biClrUsed = nColors;}
DeleteDC (hdcMem);
// сохраняем в файле
hf = CreateFile (
"TestDIB.bmp", GENERIC_READ, 0,0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hf != INVALID_HANDLE_VALUE) {
// заполняем и записываем заголовок файла
bmfh.bfType = 'MB';
bmfh.bfSize = bmfh.bfReserved1 = bmfh.bfReserved2 = 0L;
bmfh.bfOffBits =
sizeof (BITMAPFILEHEADER) +
ds.dsBmih.biSize +
(ds.dsBmih.biCompression == BI_BITFIELDS ? sizeof (ds.dsBitfields): 0) +
nColors * sizeof (RGBQUAD);
WriteFile (hf, (LPVOID)&bmfh, sizeof (bmfh), &dwWritten, (LPOVERLAPPED)0L);
// записываем полученный от GDIзаголовок битмапа
WriteFile (hf, (LPVOID)&ds.dsBmih,ds.dsBmih.biSize,&dwWritten, (LPOVERLAPPED)0L);
// проверяемналичиемасокцветов
if (ds.dsBmih.biCompression == BI_BITFIELDS) {
// пишеммаски
WriteFile (
hf, (LPVOID) (ds.dsBitfields), sizeof (ds.dsBitfields),