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), так как: а) драйвер не может осуществлять сжатие битмапа заново после каждой операции рисования, б) необходимый для хранения битмапа объем памяти может увеличиваться в процессе рисования, так как он зависит от реально достигнутой степени сжатия, разной для разных изображений, а размер блока памяти, содержащего упакованный DIB, определяется вами и не может изменяться в процессе рисования на контексте.
DIB – c екция (Win 32 API)
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; DWORD dsOffset; } 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. Для этого предназначена пара функций: UINT GetDIBColorTable (hdc, uStartIndex, cEntries, lprgbColors); 1 UINT SetDIBColorTable (hdc, uStartIndex, cEntries, lprgbColors); 1 Обратите внимание — функции используют не хендл DIB–секции, а хендл совместимого контекста устройства, в который должна быть выбрана DIB–секция. Практические примеры: 1) Создание пустой DIB–секции 1: struct {// не ‘BITMAPINFO bmi’ - нам надо зарезервировать место под палитру BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[ 256+3 ]; // в BITMAPINFO используется bmiColors[1] } bmi; LPVOID lpData; HDC hdcDisplay; 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 // (даже на Windows NT 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; // информация о битмапе HANDLE hf; // хендл файла в котором будет записан 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), &dwWritten, (LPOVERLAPPED)0L);} // пишем палитру при ее наличии if (nColors) { // палитра присутствует WriteFile ( hf, (LPVOID) (rgbs), sizeof (RGBQUAD) * nColors, &dwWritten, (LPOVERLAPPED)0L);} // записываем полученное от GDI изображение // строго говоря, надо убедиться, что это не битмап OS/2 по ds.dsBmih.biSize // и либо использовать ds.dsBmih.biSizeImage, либо вычислять самим, если это // битмап OS/2 WriteFile ( hf, (LPVOID) (ds.dsBm.bmBits), ds.dsBmih.biSizeImage, &dwWritten, (LPOVERLAPPED)0L); // вместо ds.dsBm.bmBits можно воспользоваться lpData из предыдущего примера CloseHandle (hf);} // считаем, что больше DIB–секция нам не нужна DeleteObject (hbmpDibSection); 3) Загрузка DIB–файла с помощью DIB–секции 1: HWND hwnd; // предположим, что мы будем отображать // DIB в этом окне HANDLE hfDib; // данные для проецирования DIB файла HANDLE hmapDib; BY_HANDLE_FILE_INFORMATION finfo; LPBITMAPFILEHEADER lpbmfh; HANDLE hmapTemp; // packed DIB LPBITMAPINFO lpbmi; HBITMAP hbmpDibSection; HDC hdcMem; HDC hdcDisplay; LONG dwOffBits; // смещение до данных изображения LPVOID lpData; lpbmi = (LPBITMAPINFO)0L; // инициализируем указатель — дальше проверим // скопируем временное проецирование для "Packed DIB" hfDib = CreateFile ( "TestDIB.bmp", GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hfDib!= INVALID_HANDLE_VALUE) { hmapDib = CreateFileMapping (hfDib, 0, PAGE_READ|SEC_COMMIT, 0,0, 0); lpbmfh = (LPBITMAPFILEHEADER)MapViewOfFile (hmapDib, FILE_MAP_READ, 0,0, 0); // вычислим смещение данных изображения в "Packed DIB" для использования позже dwOffBits = lpbmfh->bfOffBits - sizeof (BITMAPFILEHEADER); // создадим временное проецирование GetFileInformationByHandle (hfDib, &finfo); finfo.nFileSizeLow -= sizeof (BITMAPFILEHEADER); hmapTemp = CreateFileMapping ( INVALID_HANDLE_VALUE, 0, PAGE_READWRITE|SEC_COMMIT, 0,finfo.nFileSizeLow, 0); lpbmi = (LPBITMAPINFO)MapViewOfFile (hmapTemp, FILE_MAP_WRITE, 0,0, 0); if (lpbmi && lpbmfh) { // собственно копирование файла... CopyMemory ( (LPVOID)lpbmi, (LPVOID) (lpbmfh + 1), finfo.nFileSizeLow);} UnmapViewOfFile ((LPVOID)pbmfh); CloseHandle (hmapDib); CloseHandle (hfDib);} // если проецирование создано и файл скопирован, то отобразим его в окне if (lpbmi) { hdcDisplay = GetWindowDC (hwnd); hbmpDibSection = CreateDIBSection ( hdcDisplay, lpbmi, DIB_RGB_COLORS, &lpData, hmap, dwOffBits); hdcMem = CreateCompatibleDC (hdcDisplay); SelectObject (hdcMem, hbmpDibSection); BitBlt ( hdcDisplay, 0,0, lpbmi->bmiHeader.biWidth, lpbmi->bmiHeader.biHeight, hdcMem, 0,0, SRCCOPY); ReleaseDC (hwnd, hdcDisplay); DeleteDC (hdcMem); DeleteObject (hbmpDibSection); UnmapViewOfFile ((LPVOID)lpbmi); CloseHandle (hmapTemp);}
Метафайлы
Сохранять изображения можно не только в виде битмапов. Альтернативный, причем достаточно интересный, метод представлен с помощью так называемых метафайлов (metafile). Когда мы рассматривали способы получения контекста устройства, то отмечали, что один из перечисленных методов предназначен для создания метафайлов. Это довольно специфичное графическое устройство, которому не соответствует ровным счетом никакого реального устройства. Более того, при работе с метафайлом не создается никакого изображения. Метафайл можно рассматривать как специфичное записывающее устройство, которое запоминает все функции GDI, которые выполняются над ним. То есть, когда вы пытаетесь, скажем, нарисовать эллипс, метафайл запомнит команду для рисования эллипса в данном месте и данного размера. Позже вы сможете с помощью специальной функции осуществить воспроизведение метафайла на требуемом контексте устройства. Название метафайл придумано не случайно. Все запоминаемые команды GDI сохраняются в файле, который может быть сохранен на диске и использован позже. В какой-то степени можно сравнить метафайл с микропрограммой, осуществляющей вывод изображения. Однако это весьма специфичная программа. Для того, что бы оценить область применения метафайлов, надо рассмотреть их основные особенности. Для этого сравним метафайл с битмапом. Конечно эти сравнения весьма относительны, всегда можно придумать такой случай, когда оценки окажутся совершенно неверными: метафайл компактнее битмапов время отображения битмапов меньше времени воспроизведения метафайла при использовании обычных, зависимых от устройства битмапов возможны проблемы с передачей цветов и с переносом на другие устройства, с метафайлом таких проблем меньше при изменении размера битмапа возможно существенное снижение качества изображения, метафайл менее чувствителен к этому разработка метафайла отличается некоторыми особенностями, так как не все функции GDI могут быть использованы с метафайлами. Полезно, кроме того, узнать, каких вещей сделать с помощью метафайла невозможно: Метафайл не может использовать переменных. Запоминается выполняемая функция GDI не с выражениями, использованными для вычисления аргументов, а с их численными значениями. При записи метафайла сохраняется список вызываемых функций GDI с использованными числовыми значениями аргументов. Чему они соответствуют, выяснится при воспроизведении метафайла. Метафайл не сохраняет никаких GDI функций, начинающихся со слова Get... более того, метафайл не выполняет никаких действий, связанных с возвращением информации в программу — так как во время воспроизведения метафайла многих переменных может не быть, или они могут быть по другим адресам. Метафайл не использует функций, ссылающихся на него как на реальный контекст устройства: CreateCompatibleDC, ReleaseDC, DeleteDC, CreateCompatibleBitmap, CreateDiscardableBitmap, PlayMetaFile (имеется в виду что на одном метафайле нельзя воспроизвести другой[13]). С метафайлами не работают также функции: GrayString, DrawIcon, SetBrushOrg, FillRect, FrameRect. Однако не все ограничения метафайла являются непреодолимыми, многие из них легко обходятся. Например, мы хотим с помощью метафайла выводить изображение, занимающее все окно. При создании метафайла мы предположим, что изображение размещается в прямоугольнике, скажем, 100 x 100 единиц. Таким образом созданный метафайл будет воспроизводить изображение в области 100 x 100, независимо от размеров окна; но перед воспроизведением метафайла мы можем выбрать собственную систему координат такой, что бы размер внутренней области окна в этой системе координат был равен 100 x 100, и тогда изображение займет все окно. Из всего сказанного вытекает, что использовать метафайл как обычное графическое устройство не совсем удобно. Требуется специально проектировать процесс рисования на метафайле, что бы им было удобно пользоваться. А как можно работать с метафайлом? Сначала метафайл должен быть создан — это делается с помощью функции: HDC CreateMetaFile (lpszFileName); HDC CreateEnhMetaFile (hdcRef, lpszFileName, lprectBound, lpszDescription); 1 Данная функция создает контекст устройства, связанный с метафайлом. Параметр lpszFileName указывает имя файла, в который будет происходить запись команд. Здесь надо ввести дополнительное понятие. Метафайлы разделяют на метафайлы, использующие диск (файл) для записи команд (disk-based), и метафайлы, использующие блок памяти (memory-based) для хранения набора команд. Если вы указали имя создаваемого файла, то создается метафайл, использующий диск. Однако в качестве lpszFileName может быть указан NULL, тогда будет создан метафайл, использующий память. Функция CreateEnhMetaFile создает метафайл в формате Win32, обладающий несколько большими возможностями, чем обычный. В частности в заголовке метафайла будет сохранена информация о размерах записанного (параметр lprectBound) в нем изображения и о разрешающей способности устройства (параметр hdcRef), для которого метафайл создан. Помимо этого в заголовок включается небольшая строка, поясняющая название сохраненного рисунка и название приложения, осуществившего запись этого рисунка (параметр lpszDescription). При вызове функции CreateEnhMetaFile допускается задавать нулевые значения hdcRef (по умолчанию будут использованы характеристики дисплея) и lprectBound (размер изображения будет вычисляться в процессе записи рисунка). Создаваемые этими функциями метафайлы — разные объекты, так что для работы с метафайлами Win32 необходимо использовать свой набор функций, а для работы с метафайлами Windows 3.x — свой. В именах функций, работающих с метафайлами Win32 как правило присутствует текст...EnhMetaFile. В процессе записи метафайла Win32 можно задать текст комментария: BOOL GdiComment (hdcEnhMF, cbSize, lpData); После получения хендла контекста устройства можно осуществить запись метафайла, используя обычные функции GDI. После того, как метафайл сформирован, Вы должны закрыть его: HMETAFILE CloseMetaFile (hdcMetaFile); HENHMETAFILE CloseEnhMetaFile (hdcEnhMetaFile); 1 При этом уничтожается связанный с метафайлом контекст устройства и возвращается хендл метафайла, который может использоваться для его воспроизведения. Этот метафайл является объектом GDI, так что надо очень внимательно следить за удалением этого объекта. В отличие от большинства объектов GDI вместо функции DeleteObject должна быть использована функция: BOOL DeleteMetaFile (hMF); BOOL DeleteEnhMetaFile (hEnhMF); 1 Где hMF (hEnhMF) — хендл метафайла (а не контекста устройства). При этом метафайл как объект GDI уничтожается, а файл, содержащий его, остается на диске. Если вы хотите удалить и этот файл тоже, то либо воспользуйтесь функцией для удаления файлов стандартной библиотеки времени выполнения, например unlink, либо функцией Win32 API DeleteFile. Если вы уже записали метафайл на диск, то вы можете легко создать объект GDI, соответствующий этому метафайлу с помощью функции: HMETAFILE GetMetaFile (lpszFileName); HENHMETAFILE GetEnhMetaFile (lpszFileName); 1 Еще один способ получения хендла метафайла связан с использованием глобальных блоков памяти, содержащих данные метафайла. Вы можете сами загрузить метафайл в глобальный блок памяти или включить метафайл в виде ресурса приложения и загрузить его из ресурсов (для каждого ресурса создается отдельный глобальный блок памяти). Получив таким образом блок памяти с метафайлом вы можете создать метафайл, ссылающийся на этот блок с помощью функций: HMETAFILE SetMetaFileBits (HGLOBAL hGMemMetaFile); HMETAFILE SetMetaFileBitsBetter (HGLOBAL hGMemMetaFile); HENHMETAFILE SetEnhMetaFileBits (cbBuffer, lpData); 1 HENHMETAFILE SetWinMetaFileBits (cbBuffer, lpData, hdcRef, lpMETAFILEPICT); 1 Операции SetMetaFileBits и SetMetaFileBitsBetter возвращают хендл метафайла, созданного из данных, содержащихся в глобальном блоке памяти. Этот метафайл, естественно, является метафайлом, использующим память. Причем исходный блок после создания метафайла нельзя уничтожать или использовать, так как Windows будет использовать его непосредственно. Функция SetMetaFileBitsBetter отличается от SetMetaFileBits тем, что делает "хозяином" глобального блока, не ваше приложение, а GDI. Таким образом этот объект может использоваться буфером обмена или средствами OLE, так как он не уничтожается при завершении работы вашего приложения. Однако при этом уже вы сами обязаны проследить за тем, что бы в итоге освободить все занятые ресурсы. Операция SetEnhMetaFileBits создает метафайл из блока данных, заданного его размером и указателем. Функция SetWinMetaFileBits создает метафайл Win32 из данных, подготовленных для метафайла Windows 3.x. Возможна и обратная операция — преобразовать метафайл в блок глобальной памяти. Это делается с помощью функции: HGLOBAL GetMetaFileBits (hMF); UINT GetEnhMetaFileBits (hEnhMF, cbBuffer, lpBuffer); 1 UINT GetWinMetaFileBits (hEnhMF, cbBuffer, lpBuffer, int nMapMode, hdcRef); 1 После вызова функции GetMetaFileBits нельзя использовать hMF, он считается уничтоженным, но зато вы можете использовать возвращенный хендл блока глобальной памяти. Так вы можете узнать размер метафайла, сохранить его на диске и т.д. Функция GetEnhMetaFileBits в отличие от GetMetaFileBits не уничтожает метафайла, так что вы сами должны будете позаботиться об его уничтожении. Функция GetWinMetaFileBits кроме того записывает полученные данные в старом формате. В том случае, когда надо метафайл, использующий память, превратить в метафайл, использующий диск, надо воспользоваться функцией: HMETAFILE CopyMetaFile (hMF, lpszFileName); HENHMETAFILE CopyEnhMEtaFile (hEnhMF, lpszFileName); 1 которая скопирует данные метафайла из памяти в дисковый файл с заданным именем. После копирования можно закрыть исходный метафайл с помощью функции DeleteMetaFile (DeleteEnhMetaFile 1). Для воспроизведения метафайла предназначена функция BOOL PlayMetaFile (hDC, hMF); BOOL PlayEnhMetaFile (hDC, hEnhMF, lpRect); 1 которая проигрывает указанный параметром hMF метафайл на заданном контексте устройства hDC. В некоторых случаях, когда воспроизведение метафайла занимает много времени, или если надо отдельные записи метафайла корректировать перед воспроизведением, удобно воспользоваться другой функцией для воспроизведения метафайла: BOOL EnumMetaFile (hDC, hMF, lpfnMFEnumProc, lParam); BOOL EnumEnhMetaFile (hDC, hEnhMF, lpfnEnhMFEnumProc, lParam, lpRect); 1 Эта функция перебирает все записи метафайла hMF (hEnhMF) и для каждой из них вызывает функцию, заданную указателем lpfnMFEnumProc (lpfnEnhMFEnumProc). Параметр hDC указывает контекст устройства, на котором должен воспроизводиться метафайл, а lParam произвольные данные, которые вы решите передать в функцию lpfnMFEnumProc (lpfnEnhMFEnumProc). В случае Windows API параметр lpfnMFEnumProc должен быть не указателем на саму функцию, а указателем, возвращенным функцией MakeProcInstance; при этом после перебора всех записей метафайла (то есть после возврата из EnumMetaFile) вы должны уничтожить созданный указатель с помощью функции FreeProcInstance; в случае Win32 API надо передавать непосредственно указатель на требуемую функцию. Функция, которая вызывается для обработки каждой записи метафайла должна иметь следующий вид: int CALLBACK _export MFEnumProc ( HDC hDC, LPHANDLETABLE lpHTable, LPMETARECORD lpMR, int cHandles, LONG lParam){...} Или, в случае Win32 API: int EnhMFEnumProc (// 1 HDC hDC, HANDLETABLE FAR* lpHTable, ENHMETARECORD *lpMR, int cHandles, LONG lParam){...} Когда Windows вызывает эту процедуру, параметр hDC указывает хендл контекста устройства, на котором воспроизводиться метафайл. Параметр lpHTable является указателем на таблицу хендлов объектов GDI, созданных при воспроизведении метафайла. Причем параметр cHandles указывает число объектов в этой таблице. Параметр lpMR является указателем на структуру, описывающую ту запись метафайла, которая должна воспроизводиться, а параметр lParam содержит те данные, которые вы передали через одноименный параметр функции EnumMetaFile. Функцию MFEnumProc вы должны написать сами. В простейшем случае она может состоять из единственного вызова функции: void PlayMetaFileRecord (hDC, lpHTable, lpMR, cHandles); void PlayEnhMetaFileRecord (hDC, lpHTable, lpMR, cHandles); 1 Которая осуществит воспроизведение данной записи метафайла. Однако до, после или вместо этого вы можете проанализировать запись метафайла и выполнить нужные действия. Вы можете даже изменять запись, причем эти изменения сохраняются в метафайле. Итак, что содержится в записи? typedef struct tagMETARECORD { DWORD rdSize; UINT rdFunction; UINT rdParm[ 1 ]; } METARECORD; typedef struct tagENHMETARECORD {// 1 DWORD iType; DWORD nSize; DWORD dParm[ 1 ]; } ENHMETARECORD; Поле rdSize указывает полный размер записи метафайла, включая заголовок, в словах (то есть реально его надо умножать на 2, что бы получить размер в байтах). Поле rdFunction задает номер выполняемой функции GDI. Эти номера вы можете посмотреть в windows.h, они начинаются с префикса META_ (например META_RECTANGLE). Младший байт этого слова содержит номер функции GDI, а старший — размер передаваемых этой функции аргументов, выраженный в словах. Массив rdParm является массивом данных, передаваемых функции GDI. Данные перечислены в обратном порядке, по сравнению с тем, как они размещены в описании функции. Если при этом функция содержит указатель на что–либо (например TextOut содержит указатель на текст), то этот объект целиком включается в запись на месте соответствующего аргумента. В случае Win32 поля несколько иные: iType задает «тип записи», то есть номер функции, которые имеют префикс EMR_, вместо META_ (например EMR_SETMAPMODE), поле nSize задает размер непосредственно в байтах, а dParm — параметры записи. Вы можете легко проанализировать запись и выполнить требуемые действия. [1] Логический дюйм часто равен физическому. При выводе на экран разрешающая способность может быть чересчур низкой, по сравнению с разрешающей способностью принтера. При этом становится целесообразным несколько увеличивать реальное изображение, что бы сохранить приемлемое восприятие изображения. Так, например, шрифт размером 8 пунктов на принтере читается совершенно свободно, но при отображении на экране в реальном масштабе буквы часто становятся плохо различимыми. Логический дюйм это либо нормальный дюйм — для устройств с высокой разрешающей способностью, либо больше — для устройств с низкой разрешающей способностью. [2] По крайней мере так считает Microsoft. Вообще говоря, существует несколько различных полиграфических систем с применением разных единиц отсчета, в которых размер точки может несколько варьироваться. [3] Исключение — Windows NT. В этой системе для каждого запущенного приложения загружается своя копия системных модулей, включая GDI32.EXE; Этот механизм не настолько громоздок, как кажется, за счет специальных механизмов защиты страниц «копирование при записи». Достоинство — высокая защищенность, так как приложение может навредить только себе, даже если оно разрушит или не освободит системные объекты. В этом случае при завершении работы приложения все ресурсы, созданные им, автоматически освобождаются. Однако очень надеяться на эту особенность не стоит — если ресурсы не освобождать вовремя, то размер файла подкачки страниц может существенно увеличиться, в связи с чем возникнет вопрос о наличии свободного дискового пространства. [4] По–видимому, некоторые подобные рекомендации сделаны для обеспечения возможной совместимости с другими системами в будущем. Так, например, при работе в X–Windows (в основном UNIX–машины), при удалении контекста удаляются все объекты, выбранные в него. Если оставаться в рамках Windows или Win32 API, то выбранные в контекст устройства объекты GDI вовсе не обязательно заменять на стандартные перед освобождением контекста. [5] По крайней мере современные версии OS/2 часто применяют битмапы в ином формате — попытка их проанализировать, используя подобное описание приводит к ошибке, так что использованное название «битмап OS/2» выглядит по меньшей мере сильно устаревшим. Ключом в определении версии битмапа является размер его заголовка. Так, например, размер заголовка битмапа OS/2 (версия 1.2 — формат, поддерживаемый также и Microsoft Windows) равен 12 байтам, размер заголовка Windows битмапа равен 40 байтам (не считая так называемых 4ой и 5ой версий битмапов); более современный формат битмапов OS/2 (версия 2.0) равен 64 байтам. [6] Иногда возникает непроизвольная ошибка при попытке создать палитру (как объект GDI): для создания палитры необходимо в функцию CreatePalette передать массив структур типа PALETTENTRY, который имеет схожий с RGBQUAD формат. Естественное желание — просто использовать палитру битмапа в качестве массива структур PALETEENTRY. Неприятность связана с тем, что компоненты красного, синего и зеленого цветов в этих структурах перечислены в разном порядке; таким образом необходимо все–таки создавать собственный массив структур PALETENTRY для создания палитры. [7] В случае Windows NT требуется только чтобы биты для каждой компоненты образовывали непрерывную группу, а число бит для задания каждой компоненты и порядок задания этих групп не фиксированы. [8] Формат 5–6–5 является альтернативным, для его задания обязательно задание масок цветов; в то время как для задания формата 5–5–5 маски можно опустить — он считается стандартным. [9] Очень может быть, что это зависит от видеоадаптера и драйверов — на тестовой машине был установлен Diamond Stealth 64 PCI с чипом S3 Vision968. Стандартные драйвера для Windows 3.x вообще не поддерживают режимов 16 и 32 бит/пиксель. [10] Или RGBTRIPLE, если битмап соответствует формату OS/2 [11] Если вы будете его использовать, то вы должны включить файл драйвера DIB.DRV в установочный комплект вашего приложения — в стандартной системе этот драйвер не содержится; обычно он включен в redistribution kit, сопровождающий компиляторы. [12] Разве что кроме форматов OS/2, использующих для задания палитры записи RGBTRIPLE по 3 байта каждая; тогда необходимо следить, что бы размер палитры обеспечивал выравнивание начала изображения на границу, кратную 4 байтам. [13] На практике воспроизведение одного метафайла на другом выполняется успешно. Однако, коль скоро Microsoft советует так не делать, то лучше и не пытаться — вполне может оказаться так, что последующие версии Windows перестанут выполнять эту операцию.
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|