Операционная система Microsoft Windows 3.1 для программиста. Дополнительные главы© Александр Фролов, Григорий ФроловТом 17, М.: Диалог-МИФИ, 1994, 287 стр. 2.2. Запись и чтение графических изображенийПередача через Clipboard графических изображений выглядит сложнее, чем передача текста, однако и эта задача разрешима. Вы можете записать в Clipboard битовое изображение DDB в формате, который зависит от устройства отображения, цветовую палитру, метафайл, а также битовое изображение в формате DIB. Какой из перечисленных форматов выбрать для передачи данных в вашем приложении? Это зависит в первую очередь от того, что и каким приложениям вы собираетесь передавать. Мы рассмотрим широко распространенную задачу передачи через Clipboard битовых изображений DIB и DDB. Создавая приложения, способные сохранять в Clipboard изображения DIB и DDB, вам придется иметь дело с метафайлами. Такие приложения, как Paintbrush, при записи графических изображений в Clipboard сохраняют данные одновременно в нескольких форматах, добавляя к ним еще и цветовую палитру. В этом случае то приложение, которое будет извлекать изображение из Clipboard, может выбрать для себя наиболее удобный формат. Например, графический редактор Micrografx Designer версии 3.02 при записи изображений в Clipboard помимо своих нестандартных форматов использует форматы CF_BITMAP и CF_METAFILEPICT. Приложение Paintbrush сохраняет данные аналогичным образом, добавляя к своим собственным форматам форматы CF_BITMAP, CF_PALETTE и CF_METAFILEPICT. А вот графический редактор Microsoft Draw, который поставляется вместе с текстовым процессором Microsoft Word for Windows версии 2.0, сохраняет данные в Clipboard используя свои собственные форматы данных и формат CF_METAFILEPICT. По возможности ваше приложение должно быть способно сохранять и читать изображения в перечисленных выше форматах. В этом случае пользователь сможет вставить сохраненное вашим приложением изображение из Clipboard в какой-либо стандартный графический редактор. Несмотря на то, что для записи DIB в Clipboard предусмотрен специальный формат данных CF_DIB (блок памяти, который содержит биты изображения и начинается со структуры BITMAPINFO), многие приложения записывают DIB в виде DDB и цветовой палитры, созданных на основе DIB. Это потому, что не все приложения умеют читать из Clipboard данные в формате CF_DIB. Поэтому запись DIB сводится к записи DDB и палитры. Как мы уже говорили, следует также записать DIB в формате метафайла, к чему мы еще вернемся. Рассмотрим по отдельности способы записи DDB, палитры и метафайла в Clipboard. Запись DDBЗапись DDB в Clipboard выполняется достаточно просто. Пусть мы создали DDB (например, при помощи функции CreateCompatibleBitmap) и сохранили идентификатор созданного DDB в переменной hBitmap. В этом случае нам необходимо выполнить следующую последовательность действий. Открыть Clipboard функцией OpenClipboard Сбросить содержимое Clipboard функцией EmptyClipboard Вызвать функцию SetClipboardData, передав ей через первый параметр константу CF_BITMAP, а через второй - идентификатор DDB Закрыть Clipboard функцией CloseClipboard Все перечисленные выше действия можно проделать, например, так: OpenClipboard(hwnd); EmptyClipboard(); if(hBitmap) SetClipboardData(CF_BITMAP, hBitmap); CloseClipboard(); Отметим, что ваше приложение не должно удалять DDB, идентификатор которого был использован при вызове функции SetClipboardData. После записи в Clipboard память, занимаемая DDB, переходит в распоряжение Windows, и ваше приложение не должно ее использовать. Запись палитрыЕсли приложение должно уметь записывать в Clipboard многоцветное изображение DIB, вместе с DDB необходимо записать в Clipboard цветовую палитру, используемую этим изображением. Напомним, что файл в формате DIB может содержать наряду с битами изображения специальную таблицу цветов, на базе которой создается цветовая палитра. Вы можете получить дополнительную информацию о работе с DIB и цветовыми палитрами из 14 тома "Библиотеки системного программиста". Для записи цветовой палитры в Clipboard вам достаточно передать идентификатор палитры функции SetClipboardData, указав этой функции в качестве первого параметра константу CF_PALETTE: if(hPal) SetClipboardData(CF_PALETTE, hPal); Разумеется, перед записью палитры необходимо открыть Clipboard. Если палитра и DDB создаются на базе DIB, и ваше приложение записывает и то, и другое в Clipboard, нужно выполнить следующие действия: Открыть Clipboard функцией OpenClipboard Сбросить содержимое Clipboard функцией EmptyClipboard Вызвать функцию SetClipboardData, передав ей через первый параметр константу CF_BITMAP, а через второй - идентификатор DDB Вызвать функцию SetClipboardData, передав ей через первый параметр константу CF_PALETTE, а через второй - идентификатор цветовой палитры Закрыть Clipboard функцией CloseClipboard Палитра "сопровождает" DDB, обеспечивая приложения таблицей цветов, необходимой для правильного отображения DDB. Передав идентификатор палитры функции SetClipboardData, вы должны "забыть" про него, так как он уже не принадлежит вашему приложению. Запись метафайлаПрактически все графические редакторы, такие как Paintbrush, Micrografix Designer, Photo Finish и т. п., при записи фрагмента изображения в Clipboard сохраняют данные не только в своих собственных форматах, но и в формате метафайла. В этом разделе мы рассмотрим процесс записи метафайла в Clipboard, а немного позже расскажем об особенностях чтения метафайла из Clipboard и проигрывания его в контексте отображения. Сначала перечислим шаги, которые ваше приложение должно выполнить для записи метафайла в Clipboard. Создать метафайл в оперативной памяти (но не в виде файла на диске) С помощью функций SetWindowOrgEx и SetWindowExtEx установить в контексте метафайла размер и начало логической системы координат и размеры изображения Выполнить рисование сохраняемого в Clipboard изображения с помощью графического интерфейса GDI, пользуясь контекстом метафайла Закрыть метафайл Заказать глобальный блок памяти для заголовка метафайла (структура типа METAFILEPICT) и зафиксировать этот блок памяти Заполнить поля структуры METAFILEPICT, указав режим отображения, размеры изображения и идентификатор метафайла Расфиксировать блок памяти, содержащий заголовок метафайла METAFILEPICT Открыть Clipboard функцией OpenClipboard (если Clipboard не был открыт ранее при записи данных в других форматах) Сбросить содержимое Clipboard функцией EmptyClipboard (если Clipboard не был очищен ранее при записи данных в других форматах) Вызвать функцию SetClipboardData, передав ей через первый параметр константу CF_METAFILEPICT, а через второй - идентификатор расфиксированного блока памяти, содержащего заполненный заголовок метафайла METAFILEPICT Закрыть Clipboard функцией CloseClipboard (если в дальнейшем не предполагается сохранять в Clipboard данные, пользуясь другими форматами) Как видите, процедура записи метафайла в Clipboard достаточно громоздкая, однако использование метафайлов для передачи данных через Clipboard имеет свои преимущества. В частности, если в контексте метафайла установлены режимы отображения MM_ISOTROPIC или MM_ANISOTROPIC, в процессе вставки изображения легко выполнить масштабирование, соответственно, с сохранением отношения высоты к ширине или без сохранения этого отношения. Первые шаги процедуры записи метафайла в Clipboard (создание метафайла и настройка контекста метафайла) несложны. Для создания метафайла в оперативной памяти следует воспользоваться функцией CreateMetaFile, передав ей в качестве параметра значение NULL: hdcMeta = CreateMetaFile((LPSTR)NULL); Далее необходимо выбрать начало логической системы координат и размеры изображения. Как правило, для метафайла, сохраняемого в Clipboard, используются режимы отображения MM_ISOTROPIC или MM_ANISOTROPIC. В этом случае при записи графического изображения размером (ptSize.x, ptSize.y) вы можете определить начало логической системы координат и размеры изображены следующим образом: SetWindowOrgEx(hdcMeta, 0, 0, NULL); SetWindowExtEx(hdcMeta, ptSize.x, ptSize.y, NULL); Отметим, что на данном этапе не следует устанавливать режим отображения в контексте метафайла явным вызовом функции SetMapMode. Режим отображения устанавливается позже при заполнении заголовка метафайла. На следующем шаге приложение должно нарисовать в контексте метафайла изображение, которое будет сохранено в Clipboard. Приведем фрагмент приложения BMPINFO, рассмотренного ниже, который рисует в контексте метафайла битовое изображение DIB: // Создаем палитру из DIB hPal = DIBCreatePalette(hDib); if(hPal) { // Выбираем и реализуем палитру в контекст // метафайла hOldPal = SelectPalette(hdcMeta, hPal, FALSE); RealizePalette(hdcMeta); } // Рисуем DIB в контексте метафайла DIBPaint(hdcMeta, 0, 0, hDib); // Выбираем старую палитру if(hPal) SelectPalette(hdcMeta, hOldPal, FALSE); Процедура рисования DIB в контексте метафайла ничем не отличается от аналогичной процедуры для контекста отображения. Эта процедура, а также функции DIBCreatePalette и DIBPaint были описаны в 14 томе "Библиотеки системного программиста" (см. разделы "Рисование изображений DIB" и "Приложение BMPINFO"). После того как изображение нарисовано, следует закрыть метафайл: hMF = CloseMetaFile(hdcMeta); Теперь нужно создать и заполнить заголовок метафайла. Прежде всего, заказываем и фиксируем глобальный блок памяти нужного размера: hMeta = GlobalAlloc(GHND, sizeof(METAFILEPICT)); lpMeta = (LPMETAFILEPICT)GlobalLock(hMeta); Структура METAFILEPICT и указатель на нее описаны в файле windows.h следующим образом: typedef struct tagMETAFILEPICT { int mm; int xExt; int yExt; HMETAFILE hMF; } METAFILEPICT; typedef METAFILEPICT FAR* LPMETAFILEPICT; При заполнении заголовка метафайла в поле mm необходимо записать нужный режим отображения: lpMeta->mm = MM_ANISOTROPIC; Поле hMF должно содержать идентификатор метафайла, полученный от функции CloseMetaFile: lpMeta->hMF = hMF; Наибольшую трудность вызывает заполнение полей xExt и yExt. Эти поля заполняются по-разному в зависимости от выбранного режима отображения. Если используются режимы отображения, отличные от MM_ISOTROPIC или MM_ANISOTROPIC, в поля xExt и yExt следует записать размеры изображения в тех единицах измерения, которые соответствуют режиму отображения, указанному в поле mm. Однако, как мы уже говорили, для обеспечения возможности масштабирования изображения после вставки из Clipboard практически все приложения устанавливают в заголовке метафайла режимы отображения MM_ISOTROPIC или MM_ANISOTROPIC. Для этих режимов возможны несколько вариантов заполнения полей xExt и yExt. Во-первых, приложение может записать в эти поля нулевые значения, не передавая через Clipboard никакой информации о размерах изображения или об отношении высоты к ширине изображения. При рисовании вставленного изображения из Clipboard, не имеющего информации о размерах, "принимающее" приложение может выбрать размеры и отношение высоты к ширине по собственному усмотрению. Во-вторых, приложение может записать в поля xExt и yExt положительные значения - предпочтительный размер изображения в сотых долях миллиметра (такая единица измерения используется в режиме отображения MM_HIMETRIC). Если приложение прочитало из Clipboard изображение, для которого установлены предпочтительные размеры, оно может использовать эти размеры для рисования. Оно также может проигнорировать предпочтительные размеры и нарисовать изображение по-своему, что, однако, приведет в некоторых случаях к искажению изображения (например, при уменьшении размеров битового изображения). В-третьих, приложение может записать в поля xExt и yExt отрицательные значения. Отношение этих отрицательных значений передает информацию об отношении ширины к высоте изображения. При этом информация об абсолютных размерах изображения не передается. Итак, выбрав один из перечисленных выше вариантов, необходимо заполнить поля xExt и yExt, завершив таким образом формирование заголовка метафайла: lpMeta->xExt = xPicSize; lpMeta->yExt = yPicSize; Перед записью данных в Clipboard нужно расфиксировать блок памяти, содержащий заголовок метафайла и передать его функции SetClipboardData: GlobalUnlock(hMeta); if(hMeta) SetClipboardData(CF_METAFILEPICT, hMeta); Новая версия приложения BMPINFOВ 14 томе "Библиотеки системного программиста" мы привели исходные тексты приложения BMPINFO, с помощью которого можно просматривать битовые изображения DIB. Новая версия этого приложения позволяет сохранять загруженное изображение в Clipboard, пользуясь форматами CF_BITMAP, CF_PALETTE и CF_METAFILE. Так как полные листинги приложения BMPINFO занимают много места, мы решили опубликовать только те функции, которые были изменены или добавлены к старой версии приложения BMPINFO, описанной в 14 томе. Вы можете приобрести вместе с книгой дискету, на которой записаны исходные тексты всех приложений, описанных в этой книге, в том числе полные исходные тексты новой версии приложения BMPINFO. Основной файл исходных текстов приложения (листинг 2.5) претерпел небольшие изменения, связанные с добавлением нового временного меню "Edit". Это меню содержит строку "Copy", предназначенную для копирования загруженного изображения в Clipboard. Листинг 2.5. Файл bmpinfo/bmpinfo.cpp // ---------------------------------------- // Приложение BMPINFO // Просмотр и анализ bmp-файлов в формате DIB // с возможностью копирования // соответствующего DDB и палитры в Clipboard // ---------------------------------------- #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(!hPrevInstance) 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)) { TranslateMessage(&msg); 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(hInstance, "APP_ICON"); 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); 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; } case WM_COMMAND: { switch (wParam) { case CM_HELPABOUT: { MessageBox(hwnd, "Bitmap Information, v.1.1\n" "(C) Frolov A.V., 1995", "About BMPINFO", MB_OK | MB_ICONINFORMATION); return 0; } 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; } case CM_FILEINFO: { if(hDib != NULL) DIBInfo(hDib, dwFileSize); return 0; } case CM_FILEEXIT: { DestroyWindow(hwnd); return 0; } // Копирование рисунка в Clipboard case CM_EDITCOPY: { if(hDib == NULL) return 0; DIBCopyToClipboard(hDib, hwnd); return 0; } } } case WM_PALETTECHANGED: { if (hwnd == (HWND) wParam) break; } case WM_QUERYNEWPALETTE: { HDC hdc; HPALETTE hOldPal; int nChanged; hdc = GetDC(hwnd); 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); if(hDib) GlobalFree(hDib); PostQuitMessage(0); return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); } Обработчик сообщения от строки "Copy" временного меню "Edit" вызывает функцию DIBCopyToClipboard, определенную в файле dib.cpp. Эта функция копирует изображение в Clipboard сразу в нескольких форматах. Через первый параметр функции передается идентификатор загруженного изображения DIB, через второй - идентификатор главного окна приложения. В листинге 2.6 мы привели фрагмент файла dib.cpp, содержащий новые функции, добавленные для работы с Clipboard. Листинг 2.6. Фрагмент файла bmpinfo/dib.cpp // ----------------------------------------------------- // Функции для работы с файлами в формате DIB // ----------------------------------------------------- #define STRICT #include <windows.h> #include <windowsx.h> #include <commdlg.h> #include <mem.h> #pragma hdrstop #include "dib.hpp" // ------------------------------- // Функция SizeToHiMetric // Преобразование ширины и высоты для // режима отображения MM_HIMETRIC // ------------------------------- static void SizeToHiMetric(int *width, int *height) { HDC hDC = GetDC(0); if(hDC) { // Определяем количество пикселов на один // логический дюйм по горизонтали и вертикали int dpiX = GetDeviceCaps(hDC, LOGPIXELSX); int dpiY = GetDeviceCaps(hDC, LOGPIXELSY); // Константа для пересчета const long HiMetricPerInch = 2540; // Выполняем пересчет if(width) *width = int (*width * HiMetricPerInch / dpiX); if(height) *height = int (*height * HiMetricPerInch / dpiY); ReleaseDC(0, hDC); } } // ------------------------------- // Функция DIBCopyToClipboard // Копирование DIB в Clipboard // ------------------------------- BOOL DIBCopyToClipboard(HDIB hDib, HWND hwnd) { POINT ptSize; HDC hdcMeta, hdc, hdcMem; HMETAFILE hMF; HBITMAP hBitmap; HPALETTE hPal, hOldPal; HGLOBAL hMeta; LPMETAFILEPICT lpMeta; int x, y; // Если DIB не загружен или он имеет тип, // отличный от WINRGB_DIB, копирование не выполняем if(hDib == NULL) return FALSE; if(DIBType(hDib) != WINRGB_DIB) return FALSE; // Открываем и очищаем Clipboard if(!OpenClipboard(hwnd)) return FALSE; EmptyClipboard(); // Определяем размеры DIB DIBGetBmpRect(hDib, &ptSize); x = ptSize.x; y = ptSize.y; // Создаем DDB // Копируем в него содержимое окна, // размеры копируемой области равны // размерам DIB hdc = GetDC(hwnd); hdcMem = CreateCompatibleDC(hdc); hBitmap = CreateCompatibleBitmap(hdc, x, y); if(hBitmap) { SelectObject(hdcMem, hBitmap); StretchBlt(hdcMem, 0, 0, x, y, hdc, 0, 0, x, y, SRCCOPY); } DeleteDC(hdcMem); ReleaseDC(hwnd, hdc); // Создаем метафайл в памяти hdcMeta = CreateMetaFile((LPSTR)NULL); // Устанавливаем начало координат и размеры изображения SetWindowOrgEx(hdcMeta, 0, 0, NULL); SetWindowExtEx(hdcMeta, ptSize.x, ptSize.y, NULL); // Создаем палитру из DIB hPal = DIBCreatePalette(hDib); if(hPal) { // Выбираем и реализуем палитру в контекст // метафайла hOldPal = SelectPalette(hdcMeta, hPal, FALSE); RealizePalette(hdcMeta); } // Рисуем DIB в контексте метафайла DIBPaint(hdcMeta, 0, 0, hDib); // Выбираем старую палитру if(hPal) SelectPalette(hdcMeta, hOldPal, FALSE); // Закрываем метафайл hMF = CloseMetaFile(hdcMeta); // Заказываем память для заголовка метафайла hMeta = GlobalAlloc(GHND, sizeof(METAFILEPICT)); lpMeta = (LPMETAFILEPICT)GlobalLock(hMeta); // Преобразуем координаты для // режима отображения MM_HIMETRIC SizeToHiMetric(&x, &y); // Заполняем заголовок метафайла lpMeta->mm = MM_ANISOTROPIC; lpMeta->xExt = x; lpMeta->yExt = y; lpMeta->hMF = hMF; // Расфиксируем память заголовка метафайла GlobalUnlock(hMeta); // Записываем метафайл в Clipboard if(hMeta) SetClipboardData(CF_METAFILEPICT, hMeta); // Записываем DDB в Clipboard if(hBitmap) SetClipboardData(CF_BITMAP, hBitmap); // Записываем палитру в Clipboard if(hPal) SetClipboardData(CF_PALETTE, hPal); // Закрываем Clipboard CloseClipboard(); return TRUE; } Функция SizeToHiMetric предназначена для преобразования значений ширины и высоты изображения из пикселов в сотые доли миллиметра. Напомним, что при заполнении заголовка метафайла в изотропном и анизотропном режиме отображения размеры изображения указываются в сотых долях миллиметра. Для выполнения преобразования функция SizeToHiMetric определяет количество пикселов в одном логическом дюйме для контекста отображения, связанного с экраном дисплея. Далее ширина (высота) изображения (в пикселах) умножается на значение 2540 (1 дюйм соответствует 25,4 миллиметра) и делится на количество пикселов в одном логическом дюйме. Функция DIBCopyToClipboard выполняет копирование данных в Clipboard одновременно в нескольких форматах, используя методики, описанные нами в предыдущих разделах. Для определения размеров DIB вызывается функция DIBGetBmpRect, определенная в файле dib.cpp (в листинге не представлена). Чтение DDBДля чтения данных из Clipboard в формате CF_BITMAP (который соответствует битовому изображению DDB) ваше приложение должно выполнить следующие действия: Открыть Clipboard функцией OpenClipboard Вызвать функцию GetClipboardData, передав ей через единственный параметр константу CF_BITMAP, и проверить возвращенное значение. Если функция вернула NULL, Clipboard не содержит данных в формате CF_BITMAP. Ненулевое значение является идентификатором битового изображения DDB, который можно использовать для рисования Закрыть Clipboard функцией CloseClipboard Приведем фрагмент кода, читающий данные из Clipboard в формате CF_BITMAP: OpenClipboard(hwnd); hBitmap = (HBITMAP)GetClipboardData(CF_BITMAP); if(hBitmap != NULL) DrawBitmap(hdc, 0, 0, hBitmap); CloseClipboard(); Для рисования DDB используется функция DrawBitmap, описанная нами ранее в 14 томе "Библиотеки системного программиста". Исходный текст этой функции вы сможете также найти в проекте приложения CLIPSHOW, которое будет описано ниже. Чтение палитрыЗаписывая изображение DDB в формате CF_BITMAP в Clipboard, приложения обычно сохраняют там же и цветовую палитру в формате CF_PALETTE. Если ваше приложение работает с многоцветными изображениями, оно должно извлекать из Clipboard не только битовое изображение, но и палитру. Действия, необходимые для чтения палитры, аналогичны действиям, выполняемым при чтении DDB: Открыть Clipboard функцией OpenClipboard Вызвать функцию GetClipboardData, передав ей через единственный параметр константу CF_PALETTE, и проверить возвращенное значение. Если функция вернула NULL, Clipboard не содержит данных в формате CF_PALETTE. Ненулевое значение является идентификатором палитры, который должен использоваться для выбора и реализации палитры Закрыть Clipboard функцией CloseClipboard Следующий фрагмент кода демонстрирует извлечение палитры, ее выбор и реализацию после извлечения изображения DDB: hBitmap = (HBITMAP)GetClipboardData(CF_BITMAP); if(hBitmap != NULL) { hPal = (HPALETTE)GetClipboardData(CF_PALETTE); if(hPal) { hOldPal = SelectPalette(hdc, hPal, FALSE); RealizePalette(hdc); } DrawBitmap(hdc, 0, 0, hBitmap); } Чтение метафайлаРассмотрим теперь процедуру чтения из Clipboard и отображения в окне метафайла. Эта процедура усложняется необходимостью анализа режима отображения, установленного для метафайла, и необходимостью учета трех способов задания размеров изображения при рисовании (напомним, что размеры могут быть совсем не заданы, заданы в виде ширины и высоты, заданы в виде отношения ширины и высоты). Открыть Clipboard функцией OpenClipboard (если Clipboard не был открыт ранее при чтении данных в других форматах) Вызвать функцию GetClipboardData, передав ей через единственный параметр константу CF_METAFILEPICT, и проверить возвращенное значение. Если функция вернула NULL, Clipboard не содержит данных в формате CF_METAFILEPICT. Ненулевое значение является идентификатором блока памяти заголовка метафайла. Этот метафайл можно проиграть в контексте отображения для рисования содержимого Clipboard Зафиксировать блок памяти заголовка метафайла Выполнить проигрывание метафайла в контексте отображения Расфиксировать блок памяти заголовка метафайла Закрыть Clipboard функцией CloseClipboard В приложении CLIPSHOW указанная последовательность действий выполняется следующим образом: hmf = (HMETAFILE)GetClipboardData(CF_METAFILEPICT); if(hmf != NULL) { lpmfp = (LPMETAFILEPICT)GlobalLock(hmf); if(lpmfp != NULL) { SaveDC(hdc); PrepareMetaFile(hdc, lpmfp, cxClient, cyClient); PlayMetaFile(hdc, lpmfp->hMF); RestoreDC(hdc, -1); GlobalUnlock(hmf); } } После фиксирования заголовка метафайла приложение сохраняет текущий контекст отображения функцией SaveDC, так как перед проигрыванием метафайла этот контекст будет изменен в соответствии с содержимым полей заголовка метафайла. После проигрывания контекст отображения восстанавливается при помощи функции RestoreDC. Проигрывание метафайла выполняется функцией PlayMetaFile из программного интерфейса Windows, причем идентификатор метафайла берется из заголовка. Однако перед проигрыванием необходимо выполнить некоторые подготовительные действия, для чего в нашем приложении вызывается функция PrepareMetaFile. Прежде всего, эта функция устанавливает контекст отображения, указанный в заголовке метафайла, вызывая функцию SetMapMode: void PrepareMetaFile(HDC hdc, LPMETAFILEPICT lpmfp, int cxClient, int cyClient) { int x, y; SetMapMode(hdc, lpmfp->mm); if(lpmfp->mm == MM_ISOTROPIC || lpmfp->mm == MM_ANISOTROPIC) { if(lpmfp->xExt == 0) SetViewportExtEx(hdc, cxClient, cyClient, NULL); else if(lpmfp->xExt > 0) { x = lpmfp->xExt; y = lpmfp->yExt; HiMetricToSize(&x, &y); SetViewportExtEx(hdc, x, y, NULL); } else if(lpmfp->xExt < 0) { x = -lpmfp->xExt; y = -lpmfp->yExt; HiMetricToSizeScaled(&x, &y, cxClient, cyClient); SetViewportExtEx(hdc, x, y, NULL); } } else SetViewportExtEx(hdc, lpmfp->xExt, lpmfp->yExt, NULL); } Далее для изотропного и анизотропного режимов отображения анализируется содержимое полей xExt и yExt заголовка метафайла с целью выбора размеров изображения в контексте отображения. Если в заголовке метафайла размеры изображения не указаны (равны нулю), функция PrepareMetaFile устанавливает размер изображения, равным размеру внутренней области окна (размеры окна передаются функции через параметры). Если размеры изображения указаны и являются положительными значениями, функция PrepareMetaFile пересчитывает эти размеры из сотых долей миллиметра в пикселы (вызывая функцию HiMetricToSize) и устанавливает размеры изображения, равными значениям, переданным через заголовок метафайла. Если же размеры изображения, указанные в заголовке метафайла, меньше нуля, функция PrepareMetaFile изменяет их знак на положительный. Затем она выполняет преобразование с учетом размеров внутренней области окна, используемого для отображения. Функция HiMetricToSizeScaled масштабирует изображение таким образом, чтобы при любом размере окна изображение помещалось в него целиком и чтобы при этом сохранялось правильное отношение ширины и высоты изображения (указанное в заголовке метафайла). Исходные тексты функций HiMetricToSize и HiMetricToSizeScaled приведены ниже в разделе "Приложение CLIPSHOW". Если же в заголовке метафайла указан режим отображения, отличный от изотропного или анизотропного, размеры изображения устанавливаются равными значениям, указанным в заголовке. |