47952 (597372), страница 19
Текст из файла (страница 19)
Преобразование DIB в DDB
Для этого надо воспользоваться функцией:
HBITMAP CreateDIBitmap (hDC, lpDibInfoHdr, dwInit, lpImage, lpDibInfo, nColorUse);
которая создает вовсе не независимый от устройства битмап, как можно решить, глядя на название. Эта функция создает самый обыкновенный, зависимый от устройства битмап по указанному независимому от устройства.
Параметр hDC задает контекст устройства, с которым будет совместим создаваемый битмап.
Параметр lpDibInfoHdr является указателем на структуру BITMAPINFOHEADER (BITMAPCOREHEADER), а lpDibInfo — на структуру BITMAPINFO (BITMAPCOREINFO). Так как BITMAPINFO начинается с заголовка BITMAPINFOHEADER, то оба указателя обычно одинаковы.
Параметр dwInit указывает, надо ли осуществлять инициализацию изображения обычного битмапа данными изображения DIB. Если надо, то он должен быть равен CBM_INIT, иначе 0. Нулевое значение dwInit применяется только если битмап создается в несколько приемов: сначала создается битмап как объект GDI, а затем на него переносится изображение.
Параметр lpImage указывает на данные изображения DIB, а nColorUse задает тип таблицы определения цветов.
Если вы решили при создании обычного битмапа не инициализировать его изображение, то можете выполнить эту операцию позже, применяя функцию:
int SetDIBits (hDC, hBmp, nStartScan, nCountScan, lpImage, lpDibInfo, nColorUse);
Параметр hDC задает контекст устройства, а hBmp — обычный битмап, который инициализируется этой функцией. Надо следить, что бы данный битмап не был выбран в контекст устройства в момент вызова функции SetDIBits.
Параметры nStartScan и nCountScan задают переносимую за одну операцию полосу независимого от устройства битмапа. С помощью этих параметров можно разбить перенос одного большого битмапа на несколько операций, аналогично функции SetDIBitsToDevice.
Параметр lpImage указывает на буфер данных, lpDibInfo является указателем на структуру BITMAPINFO, содержащую заголовок и таблицу определения цветов, а nColorUse указывает на тип применяемой таблицы определения цветов (обычно DIB_RGB_COLORS).
Функция возвращает число перенесенных этой операцией строк развертки.
Использовать зависимый от устройства битмап вместо DIB имеет смысл при многократном отображении битмапа, например, если битмап отображается в окне: в этом случае перерисовка будет осуществляться при обработке каждого сообщения WM_PAINT. Типичный пример использования зависимого битмапа для отображения приведен ниже. В этом примере предполагается, что:
в созданном окне будет отображаться битмап, находящийся в файле с именем C:\TEST\MY.BMP
используются распаковщики сообщений из windowsx.h
для загрузки битмапа из файла применяется функция LoadDIBfromFile, приведенная на странице 52, а для освобождения занятых им ресурсов — функция FreeDIB (см. страницу 53)
Загрузка битмапа осуществляется однократно при создании окна (при обработке сообщения WM_CREATE, в примере — в функции Cls_OnCreate), тогда же DIB преобразуется в зависимый от устройства и, так как он больше не нужен, уничтожается. Хендл зависимого от устройства битмапа сохраняется в структуре описания окна, в двойном слове со смещением 0 (см. функции GetWindowLong и SetWindowLong).
Далее, при необходимости перерисовать битмап (сообщение WM_PAINT, функция–обработчик Cls_OnPaint) зависимый от устройства битмап отображается с помощью функции BitBlt.
Когда окно закрывается (сообщение WM_DESTROY, функция Cls_OnDestroy) находящийся в памяти зависимый от устройства битмап уничтожается, так как все занятые ресурсы GDI должны быть обязательно освобождены.
// включаемые файлы и описание структур см. в примере на странице 52
HBITMAP ConvertDIBtoDDB (LP_DIB lpDib)
{HDC hdc;
HBITMAP hbmp;
// для преобразования DIB в обычный битмап нужен контекст устройства,
// на котором будет осуществляться отображение (окно, дисплей, принтер)
hdc = GetWindowDC (NULL); // используем контекст всего дисплея
// создаем зависимый битмап и запоминаем его хендл
hbmp = CreateDIBitmap (
hdc, lpDib->lpDibHdr, CBM_INIT, lpDib->lpImage,
(LPBITMAPINFO) (lpDib->lpDibHdr), DIB_RGB_COLORS);
ReleaseDC (NULL, hdc); // освобождаем контекст дисплея
return hbmp;}
BOOL Cls_OnCreate (HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{_DIB dib;
// загружаем битмап из файла
if (LoadDIBfromFile (&dib, "C:\\TEST\\MY.BMP")) {
// создаем зависимый битмап и запоминаем его хендл
// предположим, что при регистрации класса окон мы зарезервировали
// 4 байта в структуре описания окна; там мы сохраним хендл DDB,
// который будет отображаться в окне.
SetWindowLong (hwnd, 0, (LONG)ConvertDIBtoDDB (&dib));
FreeDIB (&dib); // освобождаем память, занятую DIB}
return TRUE;}
void Cls_OnPaint (HWND hwnd)
{PAINTSTRUCT ps;
BITMAP bmp;
HBITMAP hBmp;
BeginPaint (hwnd, &ps);
// получаем хендл битмапа и узнаем его характеристики
hBmp = (HBITMAP)GetWindowLong (hwnd, 0);
GetObject (hBmp, sizeof (bmp), &bmp);
// создаем совместимый контекст и выбираем в него битмап
hCDC = CreateCompatibleDC (ps.hdc);
SelectBitmap (hCDC, hBmp);
// отображаем битмап в окне
BitBlt (ps.hdc, 0,0, bmp.bmWidth,bmp.bmHeight, hCDC, 0,0, SRCCOPY);
// удаляем совместимый контекст (битмап при этом сохранится)
DeleteDC (hCDC);
EndPaint (hwnd, &ps);}
void Cls_OnDestroy (HWND hwnd)
{HBITMAP hBmp;
// получаем хендл битмапа
hBmp = (HBITMAP)GetWindowLong (hwnd, 0);
if (hBmp) {
// если битмап существует, удаляем его
DeleteBitmap (hBmp);
SetWindowLong (hwnd, 0, 0L);}}
Обратное преобразование DDB в DIB
Кроме того Вы можете осуществить обратную операцию, перенеся данные обычного битмапа в независимый от устройства битмап. Это делается с помощью функции:
int GetDIBits (hDC, hBmp, nStartScan, nCountScan, lpImage, lpDib, nColorUse);
Параметры и возвращаемое этой функцией значение такое же, как и для функции SetDIBits.
Параметр lpImage может быть NULL, тогда эта функция не переносит данных битмапа, а только лишь заполняет структуру BITMAPINFO. Эта особенность очень часто используется на практике — при необходимости получить информацию о битмапе структура BITMAPINFOHEADER обнуляется, в нее записывается самая необходимая информация (размер структуры biSize, размеры изображения biWidth и biHeight, информация о формате битмапа biPlanes, biBitCount и biCompression), после чего вызывается функция GetDIBits с нулевым указателем на область данных изображения. GDI при этом просто заполняет структуру BITMAPINFOHEADER остальными данными, в том числе вычисляет размер области, необходимой для хранения данных (поле biSizeImage). В дальнейшем очень просто выделить блок памяти необходимого размера и повторным вызовом функции GetDIBits получить непосредственно само изображение.
// включаемые файлы и описание структур см. в примере на странице 52
BOOL ConvertDDBtoDIB (LP_DIB lpDib, HBITMAP hbmp)
{HDC hdc;
DWORD dwSize;
BITMAPFILEHEADER bmfh;
struct {
BITMAPINFOHEADER bmih;
RGBQUAD palette[ 256 + 3 ];
} bmh;
BITMAP bm;
// инициализируем возвращаемые данные:
lpDib->hglbDib = NULL;
lpDib->lpDibHdr = (LPBITMAPINFOHEADER)NULL;
lpDib->lpImage = (LPSTR)NULL;
lpDib->uDibFlags = 0;
// для преобразования DDB в DIB нужен контекст устройства
hdc = GetWindowDC (NULL); // используем контекст всего дисплея
// получаем информацию об обычном битмапе
GetObject ( (HGDIOBJ)hbmp, sizeof (bm), (LPVOID)&bm);
bmh.bmih.biSize = sizeof (bmih);
bmh.bmih.biWidth = bm.bmWidth;
bmh.bmih.biHeight = bm.bmHeight;
bmh.bmih.biPlanes = (WORD)1;
bmh.bmih.biBitCount = bm.bmPlanes * bm.bmBitsPixel;
// определяем формат битмапа
bmh.bmih.biCompression = ( (bmh.bmih.biBitCount == 16) || (bmh.bmih.biBitCount == 32)) ?
BI_BITFIELDS : BI_RGB;
// обнуляем остальные поля
bmh.bmih.biSizeImage = bmh.bmih.biXPelsPerMeter = bmh.bmih.biYPelsPerMeter =
bmh.bmih.biClrUsed = bmh.bmih.biClrImportant = 0L;
// определяем все остальные данные
GetDIBits (
hdc, hbmp, 0, abs (bmh.bmih.biHeight),
(LPVOID)0L, (LPBITMAPINFO)&bmh, DIB_RGB_COLORS);
// выделяем необходимое пространство
dwSize = sizeof (BITMAPFILEHEADER) +
( (bmh.bmih.biCompression == BI_BITFIELDS) ? sizeof (DWORD)*3 : 0L) +
sizeof (RGBQUAD) * (bmh.bmih.biClrUsed ? bmh.bmih.biClrUsed :
(bmh.bmih.biBitCount <= 8 ? 1< lpDib->lpDibHdr = (LPBITMAPINFOHEADER)GlobalAllocPtr (GHND, dwSize+bmh.bmih.biSizeImage); if (lpDib->lpDibHdr != (LPBITMAPINFOHEADER)NULL) { // уточняем информацию bmh.bmih.biXPelsPerMeter = (LONG) ( (GetDeviceCaps (hdc, HORZRES) * 1000UL) / GetDeviceCaps (hdc, HORZSIZE)); bmh.bmih.biYPelsPerMeter = (LONG) ( (GetDeviceCaps (hdc, VERTRES) * 1000UL) / GetDeviceCaps (hdc, VERTSIZE)); // копируем заголовок битмапа _memcpy_ (lpDib->lpDibHdr, &bmh, dwSize); // получаем изображение lpDib->lpImage = (LPSTR) (lpDib->lpDibHdr) + dwSize; GetDIBits ( hdc, hbmp, 0, abs (bmh.bmih.biHeight), (LPVOID) (lpDib->lpImage), (LPBITMAPINFO) (lpDib->lpDibHdr), DIB_RGB_COLORS); // и устанавливаем остальные поля структуры _DIB: lpDib->hglbDib = GlobalPtrHandle (lpDib->lpDibHdr); lpDib->uDibFlags = DIB_FILE; // выделенное пространство должно быть освобождено} ReleaseDC (NULL, hdc); // освобождаем контекст дисплея return lpDib->uDibFlags ? TRUE : FALSE;} Создание ассоциаций DIB с контекстом устройства Эти операции надо выполнять различными способами, в зависимости от API: 1) Для Windows API удобно использование так называемого DIB драйвера. В том виде, в каком было формально описано применение DIB драйвера, этот путь практически ничего не дает — слишком много ограничений на использование этого драйвера и на использование получаемого контекста устройства. 2) Для Win32 API можно создать специальную «DIB секцию », которая описывает DIB, но с помощью хендла HBITMAP, применяемого для описания DDB. Это позволяет связать DIB с контекстом устройства и выполнить рисование непосредственно на нем. Этот путь является во многих случаях предпочтительным, особенно с учетом несколько неудачной реализации функции CreateDIBitmap в некоторых версиях Win32 (CreateDIBitmap на некоторых платформах выделяет пространство для хранения битмапа в куче, используемой GDI, в то время как для DIB–секций пространство всегда выделяется в куче, используемой по умолчанию). DIB драйвер (Windows API) Как видно из предыдущего материала, самая сложная операция — сохранение измененного независимого от устройства битмапа. При этом необходимо выполнить значительный объем операций и, кроме этого, приходится анализировать заголовки битмапа, различающиеся в разных версиях. Операция загрузки битмапа из файла или из ресурсов осуществляется качественно проще и, что очень удобно, алгоритм не зависит от версии битмапа. По–видимому это подтолкнуло разработчиков Microsoft включить в состав распространяемых с компилятором компонент специальный DIB–драйвер (DIB.DRV)11, который позволяет модифицировать существующий DIB, не анализируя его заголовок. Используя этот драйвер вы можете легко получить хендл контекста устройства, связанного с указанным вами упакованным DIB. Далее все операции рисования на этом контексте устройства будут сопровождаться изменениями DIB. После уничтожения созданного контекста устройства в упакованном DIB сохранится измененное изображение. Во всем этом есть два существенных но: — созданный таким образом контекст не может быть использован для операций передачи растровых изображений. То есть отобразить данный DIB с помощью операций BitBlt или StretchBlt невозможно — для этого необходимо осуществить отображение битмапа, обращаясь к его упакованному представлению. Однако самая сложная операция — получение измененного изображения в виде упакованного DIB — осуществляется без вашего участия. — DIB драйвер не является компонентом операционной системы, так что получить его можно только в составе redistribution kit, сопровождавшем компиляторы во времена Windows 3.x; к сожалению в современные компиляторы он не включен и мне пока не попадалось его версий для Win32 API. Для того, что бы создать контекст устройства, ассоциированный с DIB, Вы должны использовать функцию CreateDC (см. также раздел «Получение хендла контекста устройства», стр. 5): HDC hDibDC = CreateDC ("DIB", NULL, NULL, lpPackedDib); // рисуем на контексте, после чего его освобождаем DeleteDC (hDibDC); Параметр со значением "DIB" задает имя драйвера (DIB.DRV), два другие должны быть NULL, а последний параметр указывает на данные, передаваемые драйверу при создании контекста устройства. Для DIB.DRV он должен указывать на упакованный DIB, то есть на заголовок битмапа BITMAPINFOHEADER (BITMAPCOREHEADER), сопровождаемый всеми необходимыми данными (палитрой и данными изображения). После использования такого контекста вы должны его уничтожить с помощью функции DeleteDC. При работе с DIB–драйвером необходимо учитывать, что обрабатываемые битмапы не должны быть сжатыми (BI_RLE4 или BI_RLE8), так как:














