Чернобелое битовое изображение
Рис 4.1. Черно-белое битовое изображение
Этому представлению соответствует дамп памяти, представленный ниже: 00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20: 00 00 FF FF FF FF FF FF FF FF FF FF FF FF FF 00 30: 00 00 00 FF 00 00 00 00 FF 00 00 00 00 FF 00 00 40: 00 00 00 00 FF 00 00 00 FF 00 00 00 FF 00 00 00 50: 00 00 00 00 00 FF 00 00 FF 00 00 FF 00 00 00 00 60: 00 00 00 00 00 00 FF 00 FF 00 FF 00 00 00 00 00 70: 00 00 00 00 00 00 00 FF 00 FF 00 00 00 00 00 00 80: 00 00 00 00 00 00 00 00 FF 00 00 00 00 00 00 00 90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Так как буфер, в котором хранится строка, должен иметь длину, кратную длине слова (два байта), буфер каждой строки дополняется нулем.
Обратите внимание, что для изображений DDB используется система координат, соответствующая режиму отображения MM_TEXT, т. е. система координат, принятая для устройства отображения.
Битовое изображение
Рис 4.2. Битовое изображение
Подготовим в памяти массив, описывающий это изображение. Каждая строка массива соответствует одной строке сканирования битового изображения: BYTE bBytes[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
При этом нам необходимо принимать во внимание, что размер одной строки сканирования изображения должен быть кратен 16 битам, т. е. двум байтам.
Однако сам по себе приведенный выше массив бит не содержит информации о размере изображения или о количестве бит, обозначающих цвет одного пиксела. Для формирования битового изображения необходимо подготовить структуру типа BITMAP, содержащую все необходимые сведения: BITMAP bmp = { 0, 64, 9, 8, 1, 1, NULL };
В этом массиве указаны размеры изображения (ширина - 64 пиксела, высота - 9 пикселов), размер памяти для одной строки сканирования в байтах (равен 8), количество цветовых плоскостей (одна) и количество бит, используемых для представления цвета одного пиксела (один бит). Указатель на массив бит будет проинициализирован непосредственно перед созданием изображения, так как сегмент данных может переместиться.
После того как массив данных и структура подготовлены, можно вызывать функцию CreateBitmapIndirect, передав ей в качестве параметра указатель на структуру: bmp.bmBits = (LPSTR)bBytes; bmLogo2 = CreateBitmapIndirect(&bmp);
Непосредственно перед вызовом функции CreateBitmapIndirect следует установить в структуре типа BITMAP указатель на массив бит изображения.
Есть еще одна возможность. Вы можете создать битовое изображение, вызвав функцию CreateBitmap : HBITMAP WINAPI CreateBitmap( int nWidth, // ширина изображения int nHeight, // высота изображения UINT cbPlanes, // количество цветовых плоскостей UINT cbBits, // количество бит на один пиксел const void FAR* lpvBits); // указатель на массив бит
Через параметры этой функции передаются значения, которые необходимо записать в структуру типа BITMAP перед вызовом функции CreateBitmapIndirect.
Функции CreateBitmap и CreateBitmapIndirect возвращают идентификатор созданного в памяти изображения, который можно использовать для выбора изображения в контекст памяти, или NULL при ошибке.
Приложение BMPLOGO
Рис 4.3. Приложение BMPLOGO
Заметим, что для рисования слова "Bitmap" мы не пользовались какими-либо особенностями шрифтов True Type. Эффект теней был получен при помощи двухкратного вывода монохромного битового изображения со сдвигом, причем каждый раз мы использовали различные растровые операции.
Исходный текст главного модуля приложения представлен в листинге 4.1.
Листинг 4.1. Файл bmplogo/bmplogo.cpp // ---------------------------------------- // Приложение BMPLOGO // Демонстрация различных способов рисования // битовых изображений DDB // ---------------------------------------- #define STRICT #include <windows.h> #include <windowsx.h> #include <mem.h> // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); void DrawBitmapRop(HDC hDC, int x, int y, HBITMAP hBitmap, DWORD dwRop); // Имя класса окна char const szClassName[] = "BmpLogoClass"; // Заголовок окна char const szWindowTitle[] = "Bitmap Logo"; // Размеры внутренней области окна short cxClient, cyClient; // Идентификатор копии приложения HINSTANCE hInst; // Битовое изображение BYTE bBytes[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; // Структура, описывающая битовое изображение BITMAP bmp = { 0, 64, 9, 8, 1, 1, NULL }; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Инициализируем приложение if(!InitApp(hInstance)) return FALSE; // Сохраняем идентификатор приложения hInst = hInstance; // После успешной инициализации приложения создаем // главное окно приложения 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)) { DispatchMessage(&msg); } return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна // Записываем во все поля структуры нулевые значения memset(&wc, 0, sizeof(wc)); // Устанавливаем системный цвет для фона окна wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1); wc.lpszMenuName = NULL; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); 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; switch (msg) { case WM_CREATE: { return 0; } // При изменении размеров окна сохраняем // новые значения для ширины и высоты case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; } // Рисование в окне case WM_PAINT: { HBITMAP bmLogo1, bmLogo2; // Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps); // Загружаем изображение из ресурсов приложения bmLogo1 = LoadBitmap(hInst, "Logo1"); // Выводим изображение два раза со смещением, // используя разные коды растровых операций. // Это дает эффект тени DrawBitmapRop(hdc, 20, 20, bmLogo1, SRCAND); DrawBitmapRop(hdc, 15, 15, bmLogo1, MERGEPAINT); // Завершаем формирование структуры bmp bmp.bmBits = (LPSTR)bBytes; // Создаем битовое изображение из массива // данных, расположенных в памяти bmLogo2 = CreateBitmapIndirect(&bmp); // Рисуем это изображение DrawBitmapRop(hdc, 0, 0, bmLogo2, SRCCOPY); // Удаляем изображения DeleteBitmap(bmLogo1); DeleteBitmap(bmLogo2); // Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
Здесь для нас представляет интерес главным образом обработчик сообщения WM_PAINT, который и рисует битовые изображения.
Изображение слова "Bitmap" находится в ресурсах и имеет идентификатор Logo1. Для загрузки его в памяти вызывается функция LoadBitmap: bmLogo1 = LoadBitmap(hInst, "Logo1");
Далее изображение выводится в первый раз, при этом используется растровая операция SRCAND: DrawBitmapRop(hdc, 20, 20, bmLogo1, SRCAND);
Затем то же самое изображение выводится еще раз, но с небольшим смещением и с использованием другой растровой операции: DrawBitmapRop(hdc, 15, 15, bmLogo1, MERGEPAINT);
Функция DrawBitmapRop аналогична описанной нами ранее функции DrawBitmap, однако она имеет дополнительный параметр, позволяющий выбрать растровую операцию. Мы привели исходный текст этой функции в листинге 4.2.
Затем обработчик сообщения WM_PAINT создает в памяти и выводит на экран еще одно монохромное битовое изображение: bmp.bmBits = (LPSTR)bBytes; bmLogo2 = CreateBitmapIndirect(&bmp); DrawBitmapRop(hdc, 0, 0, bmLogo2, SRCCOPY);
При создании изображения используются приемы, описанные в предыдущем разделе.
Перед возвратом управления обработчик удаляет оба созданных им битовых изображения: DeleteBitmap(bmLogo1); DeleteBitmap(bmLogo2);
Листинг 4.2. Файл bmplogo/drawbmp.cpp // ---------------------------------------- // Рисование изображения типа bitmap // с использованием различных растровых операций // ---------------------------------------- #define STRICT #include <windows.h> void DrawBitmapRop(HDC hDC, int x, int y, HBITMAP hBitmap, DWORD dwRop) { HBITMAP hbm, hOldbm; HDC hMemDC; BITMAP bm; POINT ptSize, ptOrg; // Создаем контекст памяти, совместимый // с контекстом отображения hMemDC = CreateCompatibleDC(hDC); // Выбираем изображение bitmap в контекст памяти hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap); // Если не было ошибок, продолжаем работу if (hOldbm) { // Для контекста памяти устанавливаем тот же // режим отображения, что используется в // контексте отображения SetMapMode(hMemDC, GetMapMode(hDC)); // Определяем размеры изображения GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm); ptSize.x = bm.bmWidth; // ширина ptSize.y = bm.bmHeight; // высота // Преобразуем координаты устройства в логические // для устройства вывода DPtoLP(hDC, &ptSize, 1); ptOrg.x = 0; ptOrg.y = 0; // Преобразуем координаты устройства в логические // для контекста памяти DPtoLP(hMemDC, &ptOrg, 1); // Рисуем изображение bitmap BitBlt(hDC, x, y, ptSize.x, ptSize.y, hMemDC, ptOrg.x, ptOrg.y, dwRop); // Восстанавливаем контекст памяти SelectObject(hMemDC, hOldbm); } // Удаляем контекст памяти DeleteDC(hMemDC); }
Файл описания ресурсов приложения (листинг 4.3) содержит только одну строку, которая ссылается на файл битового изображения logo1.bmp.
Листинг 4.3. Файл bmplogo/bmplogo.rc Logo1 BITMAP logo1.bmp
В листинге 4.4 показано битовое изображение, содержащее слово "Bitmap".
Листинг 4.4. Файл bmplogo/logo1.bmp
Формат bmpфайла для Windows версии 3 х
Рис 4.4. Формат bmp-файла для Windows версии 3.х
Файл, содержащий битовое изображение, начинается со структуры BITMAPFILEHEADER. Эта структура описывает тип файла и его размер, а также смещение области битов изображения.
Сразу после структуры BITMAPFILEHEADER в файле следует структура BITMAPINFO, которая содержит описание изображения и таблицу цветов. Описание изображения (размеры изображения, метод компрессии, размер таблицы цветов и т.д.) находится в структуре BITMAPINFOHEADER. В некоторых случаях (не всегда) в файле может присутствовать таблица цветов (как массив структур RGBQUAD), присутствующих в изображении.
Биты изображения обычно располагаются сразу после таблицы цветов. Точное значение смещения битов изображения находится в структуре BITMAPFILEHEADER.
Структура BITMAPFILEHEADER , а также указатели на нее, описаны в файле windows.h: typedef struct tagBITMAPFILEHEADER { UINT bfType; DWORD bfSize; UINT bfReserved1; UINT bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER; typedef BITMAPFILEHEADER* PBITMAPFILEHEADER; typedef BITMAPFILEHEADER FAR* LPBITMAPFILEHEADER;
Структура BITMAPFILEHEADER одинакова как для bmp-файлов Windows, так и для bmp-файлов оболочки Presentation Manager (Рисунок 4.5). И в том, и в другом случае она расположена в начале файла и обычно используется для идентификации типа файла.
Приведем описание полей этой структуры.
Формат bmpфайла для Presentaton Manager операционной системы OS/2 версии 1 х
Рис 4.5. Формат bmp-файла для Presentaton Manager операционной системы OS/2 версии 1.х
В самом начале файла расположена структура BITMAPFILEHEADER. Ее формат полностью соответствует формату аналогичной структуры bmp-файлов операционной системы Windows. В этой структуре, в частности, есть информация о смещении области битов изображения относительно начала файла.
После структуры BITMAPFILEHEADER в файле располагается структура BITMAPCOREINFO, содержащая структуру BITMAPCOREHEADER и таблицу цветов в виде массива структур RGBTRIPLE : typedef struct tagBITMAPCOREINFO { BITMAPCOREHEADER bmciHeader; RGBTRIPLE bmciColors[1]; } BITMAPCOREINFO; typedef BITMAPCOREINFO* PBITMAPCOREINFO; typedef BITMAPCOREINFO FAR* LPBITMAPCOREINFO;
Формат структуры BITMAPCOREHEADER приведен ниже: typedef struct tagBITMAPCOREHEADER { DWORD bcSize; short bcWidth; short bcHeight; WORD bcPlanes; WORD bcBitCount; } BITMAPCOREHEADER; typedef BITMAPCOREHEADER* PBITMAPCOREHEADER; typedef BITMAPCOREHEADER FAR* LPBITMAPCOREHEADER;
Приведем описание отдельных полей этой структуры.
Изображение в окне Windows
Рис 4.6. Изображение в окне Windows
В отличие от изображений DDB, при работе с изображениями DIB используется система координат, начало которой расположено в левом нижнем углу, а координатные оси направлены вверх и вправо.
Если ваше приложение загружает изображение в оперативную память, необходимо предварительно определить размер буфера. Если изображение не компрессовано (т. е. в поле biCompression структуры BITMAPINFOHEADER находится значение BI_RGB), для вычисления размера буфера можно воспользоваться следующей формулой: dwSizeImage = ((nBits + 31)/32 * 4) * biHeight
где nBits = biBitCount * biWidth
В этих формулах переменные biHeight, biBitCount и biWidth являются полями структуры BITMAPINFOHEADER.
Если же изображение хранится в компрессованом формате (в поле biCompression структуры BITMAPINFOHEADER находятся значения BI_RLE4 или BI_RLE8), нужный размер буфера можно получить из поля biSizeImage структуры BITMAPINFOHEADER.
Формат области битов изображения для цветных файлов сложнее, однако размер области определяется аналогично.
Как правило, приложения не формируют сложные изображения непосредственно в оперативной памяти, а загружают их из bmp-файлов. Однако иногда требуется создать простое черно/белое изображение непосредственно в памяти. В этом случае требуется знать точный формат области битов изображения.
Но в большинстве случаев для вывода готового изображения от вас требуется только умение определить размер области битов изображения для получения соответствующего буфера, куда эта область загружается из bmp-файла перед выводом изображения на экран. Поэтому мы не будем рассматривать формат области битов изображения для цветных bmp-файлов. Отметим только, что для bmp-файлов в формате DIB для представления цвета одного пиксела используются несколько бит памяти. Например, в 16-цветных файлах цвет пиксела представляется при помощи 4 бит памяти, в 256-цветных файлах для этой цели используется 8 бит, файлы True Color используют 24 бита.
Главное окно приложения BMPINFO
Рис 4.7. Главное окно приложения BMPINFO
Выбрав из меню "File" строку "Info...", вы можете просмотреть информацию о файле, такую, как размер файла и заголовка, формат файла, размер изображения в пикселах и т. д. (Рисунок 4.8).
Информация о bmpфайле
Рис 4.8. Информация о bmp-файле
Если выбранный bmp-файл имеет формат Presentation Manager, на экране появится диалоговая панель, аналогичная изображенной на Рисунок 4.8.
Основной файл исходных текстов приложения BMPINFO приведен в листинге 4.6.
Листинг 4.6. Файл bmpinfo/bmpinfo.cpp // ---------------------------------------- // Приложение BMPINFO // Просмотр и анализ bmp-файлов в формате DIB // ---------------------------------------- #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(!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)) { 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(NULL, IDI_APPLICATION); 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); // Если DIB был загружен, и он в формате // некомпрессованного bmp-файла для Windows, // рисуем его if((hDib != NULL) && (DIBType(hDib) == WINRGB_DIB)) { // Если при загрузке была создана палитра, // выбираем ее if(hPal) { hOldPal = SelectPalette(hdc, hPal, FALSE); RealizePalette(hdc); } // Рисуем DIB DIBPaint(hdc, 0, 0, hDib); // Выбираем старую палитру if(hPal) { SelectPalette(hdc, hOldPal, FALSE); } } // Для других форматов bmp-файлов выводим // информацию из заголовка файла 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.0\n" "(C) Frolov A.V., 1994", "About BMPINFO", MB_OK | MB_ICONINFORMATION); return 0; } // Загрузка bmp-файла 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; } // Выводим диалоговую панель с информацией из // заголовка bmp-файла case CM_FILEINFO: { if(hDib != NULL) DIBInfo(hDib, dwFileSize); return 0; } // Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd); return 0; } default: return 0; } } // Это сообщение приходит при изменении // системной палитры. Наше приложение в ответ // на это сообщение заново реализует свою логическую // палитру и при необходимости перерисовывает окно case WM_PALETTECHANGED: { // Если это не наше окно, передаем управление // обработчику сообщения WM_QUERYNEWPALETTE if (hwnd == (HWND) wParam) break; } // В ответ на это сообщение приложение должно // реализовать свою логическую палитру и // обновить окно case WM_QUERYNEWPALETTE: { HDC hdc; HPALETTE hOldPal; int nChanged; // Выбираем логическую палитру в // контекст отображения hdc = GetDC(hwnd); // При обработке сообщения WM_QUERYNEWPALETTE // палитра выбирается для активного окна, // а при обработке сообщения WM_PALETTECHANGED - // для фонового 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); PostQuitMessage(0); return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); }
Обработчик сообщения WM_CREATE сбрасывает содержимое переменных, в которых находятся идентификатор открытого bmp-файла, идентификатор загруженного изображения DIB, а также идентификатор палитры: hfDIBFile = NULL; hDib = NULL; hPal = NULL;
Обработчик сообщения WM_PAINT, рисующий изображение DIB, достаточно прост: 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;
Если загружено изображение DIB, его идентификатор отличен от NULL. В этом случае вызывается функция DIBType, определенная в нашем приложении в файле dib.cpp (листинг 4.8). Эта функция выполняет все необходимые проверки полей структуры заголовка изображения и возвращает тип изображения. Для некомпрессованных изображений DIB в формате Windows возвращается значение WINRGB_DIB.
Если при загрузке bmp-файла выяснилось, что он содержит таблицу цветов, создается палитра, идентификатор которой записывается в переменную hPal. Если содержимое этой переменной отлично от NULL, перед рисованием обработчик сообщения WM_PAINT выбирает палитру в контекст отображения и реализует ее, вызывая функции SelectPalette и RealizePalette.
Далее вызывается функция DIBPaint, определенная в файле dib.cpp, которая рисует изображение DIB. В качестве параметров этой функции передается идентификатор контекста отображения, координаты (x,y) левого верхнего угла прямоугольной области, в которой нужно нарисовать изображение, и идентификатор загруженного изображения DIB.
После этого восстанавливается старая палитра (если были изменения палитры).
Если же было загружено изображение DIB в формате Presentation Manager, вызывается функция DIBInfo, которая выводит на экран диалоговую панель с параметрами этого изображения.
При выборе в меню "File" строки "Open" получает управление обработчик сообщения WM_COMMAND. Этот обработчик вызывает функцию DIBSelectFile, определенную в файле dib.cpp. Функция DIBSelectFile выводит на экран стандартную диалоговую панель "Open" и позволяет вам выбрать для загрузки любой файл. Идентификатор открытого файла записывается в переменную hfDIBFile.
Далее файл читается в память, для чего вызывается функция DIBReadFile, определенная в файле dib.cpp. Функция читает весь файл в заказанный ей глобальный блок памяти, причем перед возвратом управления отмечает этот блок как перемещаемый и возвращает идентификатор блока памяти. Дополнительно она записывает размер файла в байтах в переменную, адрес которой указан ей в качестве второго параметра.
После удачного чтения с помощью функции DIBType определяется тип файла. Если это некомпрессованный файл в формате Windows, вызывается функция DIBCreatePalette, которая проверяет наличие таблицы цветов и при необходимости создает логическую палитру, записывая ее идентификатор в переменную hPal.
Затем для перерисовки окна и отображения загруженного изображения вызывается функция InvalidateRect.
Обработчики сообщений об изменении системной палитры WM_PALETTECHANGED и WM_QUERYNEWPALETTE аналогичны использованным в приложении PALETTE, поэтому мы не будем их описывать еще раз.
Перед завершением работы приложения функция окна удаляет логическую палитру, если она была создана, вызывая макрокоманду DeletePalette.
Идентификаторы строк меню описаны в файле bmpihfo.hpp (листинг 4.7).
Листинг 4.7. Файл bmpinfo/bmpinfo.hpp #define CM_HELPABOUT 301 #define CM_FILEOPEN 302 #define CM_FILEINFO 303 #define CM_FILEEXIT 304
Все функции, предназначенные для работы с bmp-файлами и изображениями DIB, загруженными в оперативную память, мы вынесли в отдельный файл dib.cpp (листинг4.8).
Листинг 4.8. Файл bmpinfo/dib.cpp // ----------------------------------------------------- // Функции для работы с файлами в формате DIB // ----------------------------------------------------- #define STRICT #include <windows.h> #include <windowsx.h> #include <commdlg.h> #include <mem.h> #pragma hdrstop #include "dib.hpp" // ------------------------------- // Функция DIBSelectFile // Выбор DIB-файла // ------------------------------- HFILE DIBSelectFile(void) { // Структура для выбора файла OPENFILENAME ofn; // Буфер для записи пути к выбранному файлу char szFile[256]; // Буфер для записи имени выбранного файла char szFileTitle[256]; // Фильтр расширений имени файлов char szFilter[256] = "Bitmap Files\0*.bmp;*.dib;*.rle\0Any Files\0*.*\0"; // Идентификатор открываемого файла HFILE hf; // Инициализация имени выбираемого файла // не нужна, поэтому создаем пустую строку szFile[0] = '\0'; // Записываем нулевые значения во все поля // структуры, которая будет использована для // выбора файла memset(&ofn, 0, sizeof(OPENFILENAME)); // Инициализируем нужные нам поля // Размер структуры ofn.lStructSize = sizeof(OPENFILENAME); // Идентификатор окна ofn.hwndOwner = NULL; // Адрес строки фильтра ofn.lpstrFilter = szFilter; // Номер позиции выбора ofn.nFilterIndex = 1; // Адрес буфера для записи пути // выбранного файла ofn.lpstrFile = szFile; // Размер буфера для записи пути // выбранного файла ofn.nMaxFile = sizeof(szFile); // Адрес буфера для записи имени // выбранного файла ofn.lpstrFileTitle = szFileTitle; // Размер буфера для записи имени // выбранного файла ofn.nMaxFileTitle = sizeof(szFileTitle); // В качестве начального каталога для // поиска выбираем текущий каталог ofn.lpstrInitialDir = NULL; // Определяем режимы выбора файла ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; // Выбираем входной файл if (GetOpenFileName(&ofn)) { // Открываем выбранный файл hf = _lopen(ofn.lpstrFile, OF_READ); // Возвращаем идентификатор файла return hf; } // При отказе от выбора возвращаем // нулевое значение else return 0; } // ------------------------------- // Функция DIBReadFile // Чтение DIB-файла // ------------------------------- HDIB DIBReadFile(HFILE hfDIBFile, DWORD *dwFileSize) { // Идентификатор глобального блока // памяти, который будет использован для // чтения файла HDIB hDib; HCURSOR hCursor; // идентификатор курсора // Указатель на глобальный блок памяти LPDIB lpBuf; // Курсор в виде песочных часов hCursor = SetCursor (LoadCursor(NULL, IDC_WAIT)); // Определяем размер файла. Для этого // устанавливаем текущую позицию на // конец файла *dwFileSize = _llseek(hfDIBFile, 0l, 2); // Устанавливаем текущую позицию // на начало файла _llseek(hfDIBFile, 0l, 0); // Заказываем глобальный блок памяти, // размер которого равен длине файла hDib = (HDIB)GlobalAlloc(GMEM_FIXED, *dwFileSize); lpBuf = (unsigned char huge *)GlobalLock(hDib); // Если мало свободной памяти, // возвращаем признак ошибки if(lpBuf == NULL) return(NULL); // Читаем файл в полученный блок памяти _hread(hfDIBFile, lpBuf, *dwFileSize); // Восстанавливаем курсор SetCursor (hCursor); // Расфиксируем память GlobalUnlock(hDib); // Закрываем файл _lclose(hfDIBFile); return hDib; } // ------------------------------- // Функция DIBInfo // Вывод диалоговой панели с информацией // о DIB-файле // ------------------------------- BOOL DIBInfo(HDIB hDib, DWORD dwFileSize) { char szBuf[256], szBuf1[256]; DWORD biSize; LPBITMAPFILEHEADER lpDIBFileHeader; LPBITMAPINFOHEADER lpDIBInfoHeader; LPBITMAPCOREHEADER lpDIBCoreHeader; DWORD bfOffset; BOOL bWDIB; LPDIB lpDIBPtr; int nDIBType; WORD wNumColors; // Определяем тип битового изображения nDIBType = DIBType(hDib); if(!nDIBType) // если ошибка, выдаем сообщение { MessageBox(NULL, "Ошибка в формате DIB-файла", "Bitmap Info", MB_OK | MB_ICONHAND); return FALSE; } // Фиксируем область памяти, в которую загружен DIB lpDIBPtr = (unsigned char huge *)GlobalLock(hDib); if(lpDIBPtr == NULL) return(FALSE); lpDIBFileHeader = (LPBITMAPFILEHEADER)lpDIBPtr; // Определяем смещение бит изображения // и размер заголовка bfOffset = lpDIBFileHeader->bfOffBits; biSize = (DWORD)(lpDIBPtr[sizeof(BITMAPFILEHEADER)]); // Готовим текстовую строку для вывода wsprintf(szBuf, "Размер заголовка, байт:\t%ld\n", biSize); wsprintf(szBuf1, "Размер файла, байт: \t%ld\n", dwFileSize); lstrcat(szBuf, szBuf1); wsprintf(szBuf1, "Смещение изображения, байт:\t%ld\n", bfOffset); lstrcat(szBuf, szBuf1); // В зависимости от формата DIB (PM или Windows) // выводим различную информацию из заголовка файла if((nDIBType == WINRGB_DIB) || (nDIBType == WINRLE4_DIB) || (nDIBType == WINRLE8_DIB)) { wsprintf(szBuf1, "\nBMP для Windows\n", biSize); lstrcat(szBuf, szBuf1); lpDIBInfoHeader = (LPBITMAPINFOHEADER)(lpDIBPtr + sizeof(BITMAPFILEHEADER)); wsprintf(szBuf1, "Размер:\t%ldx%ld\n", lpDIBInfoHeader->biWidth, lpDIBInfoHeader->biHeight); lstrcat(szBuf, szBuf1); wsprintf(szBuf1, "Бит на пиксел:\t%d\n", lpDIBInfoHeader->biBitCount); lstrcat(szBuf, szBuf1); wNumColors = DIBNumColors(lpDIBPtr); wsprintf(szBuf1, "Таблица цветов:\t%d\n", wNumColors); lstrcat(szBuf, szBuf1); if(lpDIBInfoHeader->biCompression == BI_RGB) { lstrcat(szBuf, "Без компрессии\n"); } else if(lpDIBInfoHeader->biCompression == BI_RLE4) { lstrcat(szBuf, "Компрессия RLE4\n"); } else if(lpDIBInfoHeader->biCompression == BI_RLE8) { lstrcat(szBuf, "Компрессия RLE8\n"); } } // Для файлов DIB в формате PM else { wsprintf(szBuf1, "\nBMP для Presentation Manager\n", biSize); lstrcat(szBuf, szBuf1); lpDIBCoreHeader = (LPBITMAPCOREHEADER)(lpDIBPtr + sizeof(BITMAPFILEHEADER)); wsprintf(szBuf1, "Размер:\t%dx%d\n", lpDIBCoreHeader->bcWidth, lpDIBCoreHeader->bcHeight); lstrcat(szBuf, szBuf1); wsprintf(szBuf1, "Бит на пиксел:\t%d\n", lpDIBCoreHeader->bcBitCount); lstrcat(szBuf, szBuf1); } MessageBox(NULL, (LPSTR)szBuf, "Bitmap Info", MB_OK | MB_ICONINFORMATION); GlobalUnlock(hDib); return TRUE; } // ------------------------------- // Функция DIBType // Определение и проверка формата DIB // ------------------------------- int DIBType(HDIB hDib) { LPBITMAPFILEHEADER lpDIBFileHeader; LPBITMAPINFOHEADER lpih; LPBITMAPCOREHEADER lpch; DWORD biSize; LPDIB hDIBPtr; int nDIBType; if(hDib == NULL) // Неправильный идентификатор DIB return(-2); // Фиксируем память, в которой находится DIB hDIBPtr = (LPDIB)GlobalLock(hDib); if(hDIBPtr == NULL) return(-1); lpDIBFileHeader = (LPBITMAPFILEHEADER)hDIBPtr; // Проверяем тип файла if(lpDIBFileHeader->bfType != 0x4d42) { GlobalUnlock(hDib); return 0; } // Проверяем размер заголовка biSize = (DWORD)(hDIBPtr[sizeof(BITMAPFILEHEADER)]); if(biSize == sizeof(BITMAPINFOHEADER)) // 40 байт { // Это заголовок DIB в формате Windows lpih = (LPBITMAPINFOHEADER)(hDIBPtr + sizeof(BITMAPFILEHEADER)); // Проверяем основные поля заголовка DIB if((lpih->biPlanes == 1) && ((lpih->biBitCount == 1) || (lpih->biBitCount == 4) || (lpih->biBitCount == 8) || (lpih->biBitCount == 24)) && ((lpih->biCompression == BI_RGB) || (lpih->biCompression == BI_RLE4 && lpih->biBitCount == 4) || (lpih->biCompression == BI_RLE8 && lpih->biBitCount == 8))) { // Определяем метод компрессии файла if(lpih->biCompression == BI_RGB) nDIBType = WINRGB_DIB; else if(lpih->biCompression == BI_RLE4) nDIBType = WINRLE4_DIB; else if(lpih->biCompression == BI_RLE8) nDIBType = WINRLE8_DIB; else nDIBType = 0; } else nDIBType = 0; } else if(biSize == sizeof(BITMAPCOREHEADER)) // 12 байт { // Это заголовок DIB в формате Presentation Manager lpch = (LPBITMAPCOREHEADER)(hDIBPtr + sizeof(BITMAPFILEHEADER)); // Проверяем основные поля заголовка DIB if((lpch->bcPlanes == 1) && (lpch->bcBitCount == 1 || lpch->bcBitCount == 4 || lpch->bcBitCount == 8 || lpch->bcBitCount == 24)) { nDIBType = PM_DIB; } else nDIBType = 0; } else nDIBType = 0; GlobalUnlock(hDib); // Возвращаем тип файла или признак ошибки return nDIBType; } // ------------------------------- // Функция DIBNumColors // Определение размера палитры // ------------------------------- WORD DIBNumColors(LPDIB lpDib) { DWORD dwColorUsed; LPBITMAPINFOHEADER lpih; lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER)); // Количество цветов dwColorUsed = lpih->biClrUsed; // Если используется палитра уменьшенного размера, // возвращаем нужный размер if(dwColorUsed) return((WORD)dwColorUsed); // Если количество использованных цветов не указано, // вычисляем стандартный размер палитры исходя из // количества бит, определяющих цвет пиксела switch(lpih->biBitCount) { case 1: return 2; case 4: return 16; case 8: return 256; default: return 0; // палитра не используется } } // ------------------------------- // Функция DIBHeight // Определение высоты DIB в пикселах // ------------------------------- WORD DIBHeight(LPDIB lpDib) { LPBITMAPINFOHEADER lpih; lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER)); return lpih->biHeight; } // ------------------------------- // Функция DIBWidth // Определение ширины DIB в пикселах // ------------------------------- WORD DIBWidth(LPDIB lpDib) { LPBITMAPINFOHEADER lpih; lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER)); return lpih->biWidth; } // ------------------------------- // Функция DIBFindBits // Определение адреса массива бит изображения // ------------------------------- LPSTR DIBFindBits(LPDIB lpDib) { LPBITMAPFILEHEADER lpfh; LPBITMAPINFOHEADER lpih; lpfh = (LPBITMAPFILEHEADER)lpDib; // Используем значение, указанное в заголовке // файла (если оно не равно нулю) if(lpfh->bfOffBits) return((LPSTR)lpfh + lpfh->bfOffBits); // Вычисляем адрес исходя из размеров заголовков и // таблицы цветов lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER)); return((LPSTR)lpih + lpih->biSize + (DWORD)(DIBNumColors(lpDib) * sizeof(RGBQUAD))); } // ------------------------------- // Функция DIBPaint // Рисование DIB при помощи функции StretchDIBits // ------------------------------- BOOL DIBPaint(HDC hdc, int x, int y, HDIB hDib) { HBITMAP hbmp; HDC hMemDC; WORD wHeight, wWidth; LPDIB lpDib; LPBITMAPINFOHEADER lpih; lpDib = (LPDIB)GlobalLock(hDib); if(lpDib == NULL) return(-1); lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER)); // Определяем размеры DIB wHeight = lpih->biHeight; wWidth = lpih->biWidth; // Рисуем DIB без масштабирования StretchDIBits(hdc, x, y, wWidth, wHeight, 0, 0, wWidth, wHeight, DIBFindBits(lpDib), (LPBITMAPINFO)lpih, DIB_RGB_COLORS, SRCCOPY); GlobalUnlock(hDib); return TRUE; } // ------------------------------- // Функция DIBPaintBlt // Рисование DIB при помощи функции BitBlt // ------------------------------- BOOL DIBPaintBlt(HDC hdc, int x, int y, HDIB hDib) { HBITMAP hbmp; HDC hMemDC; WORD wHeight, wWidth; LPDIB lpDib; LPBITMAPINFOHEADER lpih; lpDib = (LPDIB)GlobalLock(hDib); if(lpDib == NULL) return(-1); lpih = (LPBITMAPINFOHEADER)(lpDib + sizeof(BITMAPFILEHEADER)); wHeight = lpih->biHeight; wWidth = lpih->biWidth; // Создаем совместимое битовое изображение hbmp = CreateCompatibleBitmap(hdc, wWidth, wHeight); // Создаем совместимый контекст памяти hMemDC = CreateCompatibleDC(hdc); // Преобразуем DIB в DDB SetDIBits(hdc, hbmp, 0, wHeight, DIBFindBits(lpDib), (LPBITMAPINFO)lpih, DIB_RGB_COLORS); // Выбираем DDB в контекст отображения hbmp = (HBITMAP)SelectObject(hMemDC, hbmp); // Рисуем DDB BitBlt(hdc, x, y, wWidth, wHeight, hMemDC, 0, 0, SRCCOPY); // Удаляем контекст памяти DeleteObject(SelectObject(hMemDC, hbmp)); DeleteDC(hMemDC); GlobalUnlock(hDib); return TRUE; } // ------------------------------- // Функция DIBCreatePalette // Создаем палитру на базе таблицы цветов DIB // ------------------------------- HPALETTE DIBCreatePalette(HDIB hDib) { LPLOGPALETTE lpPal; HPALETTE hPal = NULL; HANDLE hLogPal; int i, wNumColors; LPSTR lpbi; LPBITMAPINFO lpbmi; if (!hDib) return NULL; lpbi = (LPSTR)GlobalLock(hDib); lpbmi = (LPBITMAPINFO)(lpbi + sizeof(BITMAPFILEHEADER)); // Определяем размер таблицы цветов wNumColors = DIBNumColors(lpbi); // Если в DIB есть таблица цветов, создаем палитру if (wNumColors) { // Заказываем память для палитры hLogPal = GlobalAlloc(GHND, sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * wNumColors); if (!hLogPal) { GlobalUnlock(hDib); return NULL; } // Получаем указатель на палитру lpPal = (LPLOGPALETTE)GlobalLock(hLogPal); // Заполняем заголовок lpPal->palVersion = 0x300; lpPal->palNumEntries = wNumColors; // Заполняем палитру for (i = 0; i < wNumColors; i++) { lpPal->palPalEntry[i].peRed = lpbmi->bmiColors[i].rgbRed; lpPal->palPalEntry[i].peGreen = lpbmi->bmiColors[i].rgbGreen; lpPal->palPalEntry[i].peBlue = lpbmi->bmiColors[i].rgbBlue; lpPal->palPalEntry[i].peFlags = 0; } // Создаем палитру hPal = CreatePalette(lpPal); if (!hPal) { GlobalUnlock(hLogPal); GlobalFree(hLogPal); return NULL; } GlobalUnlock(hLogPal); GlobalFree(hLogPal); } GlobalUnlock(hDib); // Возвращаем идентификатор созданной палитры return hPal; }
Опишем функции, определенные в файле dib.cpp.
Рисунки символов
Рис 5.1. Рисунки символов
Глифы могут иметь различный внешний вид (typeface). Операционная система Windows классифицирует шрифты на несколько типов, или семейств (font family). Эти типы называются Modern, Roman, Swiss, Script, Decorative.
Шрифты семейства Modern имеют одинаковую ширину букв. Таким шрифтом оформлены все листинги программ в нашей книге. Шрифты семейства Roman содержат буквы различной ширины, имеющие засечки. Семейство Swiss отличается тем, что при переменной ширине букв они не имеют засечек. Буквы в шрифтах семейства Script как бы написаны от руки. Семейство Decorative содержит глифы в виде небольших картинок (пиктограмм).
В следующей таблице мы привели примеры шрифтов различных семейств.
Диалоговая панель "Font"
Рис 5.2. Диалоговая панель "Font"
Из списка "Font", который расположен в левой верхней части этой диалоговой панели, пользователь может выбрать название шрифта. Список "Font Style" позволяет выбрать один из доступных стилей, например, наклонный или жирный шрифт. Список "Size" предназначен для выбора размера шрифта. С помощью переключателей "Strikeout" и "Underline", расположенных в поле "Effects", можно создать, соответственно, перечеркнутый и подчеркнутый шрифт. И, наконец, из меню "Color" можно выбрать цвет букв.
Образец выбранного шрифта отображается в поле "Sample".
Обратите внимание на то, что в списке "Font" некоторые шрифты отмечены двойной буквой "T". Это масштабируемые шрифты True Type.
Приведем прототип функции ChooseFont: BOOL WINAPI ChooseColor(CHOOSEFONT FAR* lpcf);
Единственный параметр функции является указателем на структуру типа CHOOSEFONT. Эта структура, а также сама функция ChooseFont, определены в файле commdlg.h. Структура определена следующим образом: typedef struct tagCHOOSEFONT { DWORD lStructSize; HWND hwndOwner; HDC hDC; LOGFONT FAR* lpLogFont; int iPointSize; DWORD Flags; COLORREF rgbColors; LPARAM lCustData; UINT (CALLBACK* lpfnHook)(HWND, UINT, WPARAM, LPARAM); LPCSTR lpTemplateName; HINSTANCE hInstance; LPSTR lpszStyle; UINT nFontType; int nSizeMin; int nSizeMax; } CHOOSEFONT; typedef CHOOSEFONT FAR *LPCHOOSEFONT;
Перед вызовом функции ChooseFont вы должны проинициализировать нужные поля структуры CHOOSEFONT, записав в остальные поля нулевые значения.
Опишем назначение отдельных полей структуры CHOOSEFONT:
Вывод текста с наклоном
Рис 5.3. Вывод текста с наклоном
С помощью этого приложения вы можете убедиться в том, что повернуть можно только масштабируемые шрифты True Type.
Вывод перевернутого текста
Рис 5.4. Вывод перевернутого текста
Исходный текст приложения приведен в листинге 5.1.
Листинг 5.1. Файл fontview/fontview.cpp // ---------------------------------------- // Приложение FONTVIEW // Просмотр шрифтов // ---------------------------------------- #define STRICT #include <windows.h> #include <windowsx.h> #include <commdlg.h> #include <mem.h> #pragma hdrstop #include "fontview.hpp" // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); BOOL GetFont(HWND hWnd, LOGFONT *lf, CHOOSEFONT *cf); // Имя класса окна char const szClassName[] = "FontViewClass"; // Заголовок окна char const szWindowTitle[] = "Font Viewer"; // Размеры внутренней области окна short cxClient, cyClient; // Идентификатор копии приложения HINSTANCE hInst; // Строка для вывода char szChars[] = ": AaBbCcDdEeFfGg АаБбВвГгДдЕе"; char szBuf[256]; // Угол наклона строки при выводе int nOrientation = 0; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Инициализируем приложение if(!InitApp(hInstance)) return FALSE; hInst = hInstance; // После успешной инициализации приложения создаем // главное окно приложения 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)) { 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(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_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 CHOOSEFONT cf; static LOGFONT lf; static HFONT hfont, hfOldFont;; switch (msg) { // При изменении размеров окна сохраняем // новые значения для ширины и высоты case WM_SIZE: { cxClient = LOWORD(lParam); cyClient = HIWORD(lParam); return 0; } // Рисование в окне case WM_PAINT: { // Получаем контекст отображения для // рисования во внутренней области окна hdc = BeginPaint(hwnd, &ps); // Устанавливаем угол наклона строки lf.lfOrientation = lf.lfEscapement = nOrientation; // Создаем шрифт на базе заполненной // структуры LOGFONT hfont = CreateFontIndirect(&lf); if(hfont) { // Выбираем шрифт в контекст отображения hfOldFont = SelectFont(hdc, hfont); // Определяем название шрифта GetTextFace(hdc, 80, szBuf); // Добавляем к нему текстовую строку lstrcat(szBuf, szChars); // Устанавливаем цвет текста SetTextColor(hdc, cf.rgbColors); // Выводим текст, пользуясь выбранным шрифтом TextOut(hdc, cxClient/2, cyClient/2, szBuf, lstrlen(szBuf)); // Выбираем старый шрифт SelectFont(hdc, hfOldFont); // Удаляем созданный нами шрифт DeleteFont(hfont); } // Освобождаем контекст отображения EndPaint(hwnd, &ps); return 0; } // Обработка сообщений от меню case WM_COMMAND: { switch (wParam) { // Выбор шрифта при помощи диалоговой панели case CM_FONTSEL: { // Записываем во все поля структуры типа // LOGFONT нулевые значения memset(&lf, 0, sizeof(LOGFONT)); // Выбираем шрифт if(GetFont(hwnd, &lf, &cf)) { // Перерисовываем окно InvalidateRect(hwnd, NULL, TRUE); } return 0; } // Выбираем шрифт, указывая семейство case CM_FDECOR: { memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_DECORATIVE; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FMODERN: { memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_MODERN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FROMAN: { memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_ROMAN; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FSCRIPT: { memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_SCRIPT; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FSWISS: { memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_SWISS; InvalidateRect(hwnd, NULL, TRUE); return 0; } // Выбираем угол поворота строки case CM_FONT00: { nOrientation = 0; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT30: { nOrientation = 300; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT45: { nOrientation = 450; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT90: { nOrientation = 900; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT180: { nOrientation = 1800; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT270: { nOrientation = 2700; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_FONT360: { nOrientation = 3600; InvalidateRect(hwnd, NULL, TRUE); return 0; } case CM_HELPABOUT: { MessageBox(hwnd, "Font Viewer, v.1.0\n" "(C) Frolov A.V., 1994", "About FONTVIEW", MB_OK | MB_ICONINFORMATION); return 0; } // Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd); return 0; } default: return 0; } } case WM_DESTROY: { PostQuitMessage(0); return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam); } // ===================================== // Функция GetFont // ===================================== BOOL GetFont(HWND hWnd, LOGFONT *lf, CHOOSEFONT *cf) { LPSTR szFontStyle[LF_FACESIZE]; // Записываем нулевые значения во все поля // структуры, которая будет использована для // выбора шрифта memset(cf, 0, sizeof(CHOOSEFONT)); // Размер структуры cf->lStructSize = sizeof(CHOOSEFONT); // Идентификатор окна cf->hwndOwner = hWnd; // Указатель на структуру LOGFONT cf->lpLogFont = lf; // Флаги, определяющие внешний вид диалоговой панели cf->Flags = CF_SCREENFONTS | CF_USESTYLE | CF_EFFECTS; // Дополнительные данные cf->lCustData = 0L; // Цвет текста cf->rgbColors = RGB(0,0,0); // Адрес функции фильтра cf->lpfnHook = (FARPROC)NULL; // Адрес шаблона диалоговой панели cf->lpTemplateName = (LPSTR)NULL; // Идентификатор копии приложения cf->hInstance = hInst; // Стиль шрифта cf->lpszStyle = (LPSTR)szFontStyle; // Тип шрифта cf->nFontType = SCREEN_FONTTYPE; // Ограничения на минимальный и максимальный // размер шрифта cf->nSizeMin = 0; cf->nSizeMax = 0; // Вызываем функцию выбора шрифта return ChooseFont(cf); }
Текстовая строка, которая выводится на экран, находится в глобальном массиве szChars. В переменной nOrientation находится текущее значение угла поворота, которое задается при помощи меню "Orientation".
Обработчик сообщения WM_PAINT пользуется структурой lf типа LOGFONT, подготовленной при выборе шрифта.
Перед созданием шрифта обработчик устанавливает нужный угол наклона строки: lf.lfOrientation = lf.lfEscapement = nOrientation;
Затем создается шрифт: hfont = CreateFontIndirect(&lf);
Далее шрифт выбирается в контекст отображения, для чего используется макрокоманда SelectFont: hfOldFont = SelectFont(hdc, hfont);
Идентификатор шрифта, который был выбран в контекст отображения раньше, сохраняется в переменной hfOldFont.
Затем обработчик вызывает функцию GetTextFace, которая копирует в буфер szBuf текстовую строку с названием шрифта, выбранного в контекст отображения. Эта строка затем дописывается ко строке szChars и выводится на экран.
Перед выводом устанавливается цвет текста, который берется из заполненной на этапе выбора шрифта структуры CHOOSEFONT: SetTextColor(hdc, cf.rgbColors);
Для вывода текста мы используем функцию TextOut, которая была подробно описана в 11 томе "Библиотеки системного программиста".
Перед возвратом управления обработчик сообщения WM_PAINT выбирает в контекст отображения старый шрифт и удаляет созданный шрифт: SelectFont(hdc, hfOldFont); DeleteFont(hfont);
Когда вы выберите строку "Fonts..." из меню "Font", получит управление обработчик сообщения WM_COMMAND. Он запишет во все поля структуры lf типа LOGFONT нулевые значения и вызовет функцию GetFont, определенную в нашем приложении. После этого он вызовет функцию InvalidateRect для перерисовки окна приложения.
Функция GetFont инициализирует нулевыми значениями структуру cf типа CHOOSEFONT, а затем заполняет в этой структуре нужные поля и вызывает функцию ChooseFont: memset(cf, 0, sizeof(CHOOSEFONT)); cf->lStructSize = sizeof(CHOOSEFONT); cf->hwndOwner = hWnd; cf->lpLogFont = lf; cf->Flags = CF_SCREENFONTS | CF_USESTYLE | CF_EFFECTS; cf->lCustData = 0L; cf->rgbColors = RGB(0,0,0); cf->lpfnHook = (FARPROC)NULL; cf->lpTemplateName = (LPSTR)NULL; cf->hInstance = hInst; cf->lpszStyle = (LPSTR)szFontStyle; cf->nFontType = SCREEN_FONTTYPE; cf->nSizeMin = 0; cf->nSizeMax = 0; return ChooseFont(cf);
Если вы выбираете из меню "Font" одно из семейств шрифтов, структура lf инициализируется нулевыми значениями, а затем в ней устанавливается поле lfPitchAndFamily: memset(&lf, 0, sizeof(LOGFONT)); lf.lfPitchAndFamily = FF_DECORATIVE;
Затем вызывается функция InvalidateRect, что приводит к перерисовке окна приложения.
Установка угла наклона выполняется достаточно просто и заключается в изменении значения переменной nOrientation с последующей перерисовкой окна: case CM_FONT30: { nOrientation = 300; InvalidateRect(hwnd, NULL, TRUE); return 0; }
Все константы, которые используются для работы с меню, описаны в файле fontview.hpp (листинг 5.2).
Листинг 5.2. Файл fontview/fontview.hpp #define CM_HELPABOUT 301 #define CM_FONTSEL 302 #define CM_FILEEXIT 303 #define CM_FONT30 304 #define CM_FONT45 305 #define CM_FONT90 306 #define CM_FONT180 307 #define CM_FONT270 308 #define CM_FONT360 309 #define CM_FONT00 310 #define CM_FDECOR 311 #define CM_FMODERN 312 #define CM_FROMAN 313 #define CM_FSCRIPT 314 #define CM_FSWISS 315
Меню определено в файле ресурсов приложения (листинг 5.3).
Листинг 5.3. Файл fontview/fontview.rc #include "fontview.hpp" APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", CM_FILEEXIT END POPUP "F&ont" BEGIN MENUITEM "FF_DECORATIVE", CM_FDECOR MENUITEM "FF_MODERN", CM_FMODERN MENUITEM "FF_ROMAN", CM_FROMAN MENUITEM "FF_SCRIPT", CM_FSCRIPT MENUITEM "FF_SWISS", CM_FSWISS MENUITEM SEPARATOR MENUITEM "&Select Font...",CM_FONTSEL END POPUP "&Orientation" BEGIN MENUITEM "0", CM_FONT00 MENUITEM "30",CM_FONT30 MENUITEM "45",CM_FONT45 MENUITEM "90",CM_FONT90 MENUITEM "180",CM_FONT180 MENUITEM "270",CM_FONT270 MENUITEM "360",CM_FONT360 END POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END
Файл определения модуля приложения FONTVIEW приведен в листинге 5.4.
Листинг 5.4. Файл fontview/fontview.def ; ============================= ; Файл определения модуля ; ============================= NAME FONTVIEW DESCRIPTION 'Приложение FONTVIEW, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Метрики шрифта
Рис 5.5. Метрики шрифта
Отсчет всех размеров выполняется от так называемой базовой линии шрифта. Для размеров используются логические единицы, которые зависят от режима отображения, установленного в контексте устройства.
Общая высота букв находится в поле tmHeight структуры TEXTMETRIC. Эта высота складывается из двух компонент - tmAscent и tmDescent. Компонента tmAscent представляет собой высоту букв от базовой линии с учетом таких элементов, как тильда в букве "Й". Компонента tmDescent определяет пространство, занимаемое буквами ниже базовой линии. Сумма tmAscent и tmDescent в точности равна tmHeight.
Величина tmInternalLeading определяет размер выступающих элементов букв и может быть равна нулю.
Величина tmExternalLeading определяет минимальный межстрочный интервал, рекомендуемый разработчиком шрифта. Ваше приложение может игнорировать межстрочный интервал, однако в этом случае строки будут соприкасаться друг с другом, что не улучшит внешнего вида окна.
Для ширины букв в структуре TEXTMETRIC есть два поля с именами tmAveCharWidth и tmMaxCharWidth. Поле tmAveCharWidth содержит среднее значение ширины строчных букв шрифта. Это значение приблизительно соответствует ширине латинской буквы "x". Поле tmMaxCharWidth определяет ширину самой широкой буквы в шрифте. Для шрифта с фиксированной шириной букв поля tmAveCharWidth и tmMaxCharWidth содержат одинаковые значения, которые зависят от самого шрифта.
Поле tmWeight определяет жирность шрифта. Может находиться в пределах от 0 до 1000. Файл windows.h содержит определение символических констант для этого поля:
Диалоговая панель "Print"
Рис 6.1. Диалоговая панель "Print"
В верхней части диалоговой панели "Print" в поле "Printer" указано название принтера, который будет использован для печати. Вы можете выбрать другой принтер, если нажмете кнопку "Setup...".
С помощью группы органов управления "Print Range" вы можете выбрать диапазон страниц, которые должны быть распечатаны. Можно распечатать все или отдельные страницы, выделенный фрагмент текста или страницы в указанном диапазоне номеров страниц (поля "From" и "To").
Можно задать качество печати (поле "Print Quality"), указать количество копий (поле "Copies"), выполнить печать в файл (поле "Print to File").
С помощью переключателя "Collate Copies" можно выбрать порядок печати отдельных листов многостраничного документа, который должен быть напечатан в нескольких копиях. Если этот переключатель находится во включенном состоянии, вначале следует напечатать все страницы первой копии, затем - второй, и так далее. Если же этот переключатель выключен, вначале печатается несколько копий первой страницы, затем несколько копий второй страницы и так до конца документа. Последний режим печати удобен для лазерных принтеров, где время подготовки одной страницы для печати больше времени печати готовой страницы.
Если нажать на кнопку "Setup...", на экране появится диалоговая панель "Print Setup" (Рисунок 6.2).
Диалоговая панель "Print Setup"
Рис 6.2. Диалоговая панель "Print Setup"
В группе органов управления "Printer" вы можете выбрать для печати либо принтер, принятый по умолчанию ("Default Printer"), либо выбрать другой принтер из списка "Specific Printer".
С помощью переключателей группы "Orientation" вы можете выбрать вертикальное ("Portrait") либо горизонтальное ("Landscape") расположение текста на листе бумаги.
Группа "Paper" содержит два списка, с помощью которых вы можете выбрать размер бумаги (список "Size") или устройство подачи бумаги ("Source").
Нажав в этой диалоговой панели кнопку "Options...", вы сможете выполнить настройку параметров принтера при помощи диалоговой панели "Options" (Рисунок 6.3).
Диалоговая панель "Options" для лазерного принтера HP LaserJet III
Рис 6.3. Диалоговая панель "Options" для лазерного принтера HP LaserJet III
Внешний вид диалоговой панели "Options" зависит от драйвера принтера, так как эта панель формируется функцией DeviceMode или ExtDeviceMode, расположенными в соответствующем драйвере принтера. Для сравнения на Рисунок 6.4 представлен внешний вид диалоговой панели "Options" для матричного принтера Epson FX-850.
Если вас не устраивает внешний
Рис 6.4. Диалоговая панель "Options" для матричного принтера Epson FX-850
Если вас не устраивает внешний вид диалоговых панелей "Print" и "Print Options", вы можете использовать вместе с функцией PrintDlg свои собственные шаблоны диалоговых панелей. Можно также подключить функцию фильтра для обеспечения дополнительной обработки сообщений.
Приведем прототип функции PrintDlg, описанный в файле commdlg.h: BOOL PrintDlg(PRINTDLG FAR* lppd);
При успешном завершении функция возвращает значение TRUE. В случае ошибки, отмены печати или отмены выбора принтера (если функция PrintDlg используется только для выбора принтера) функция возвращает значение FALSE.
Главное окно приложения PRNFILE
Рис 6.5. Главное окно приложения PRNFILE
Исходный основного файла приложения приведен в листинге 6.1.
Листинг 6.1. Файл prnfile/prnfile.cpp // ---------------------------------------- // Редактор текстовых файлов с возможностью печати // ---------------------------------------- #define STRICT #include <windows.h> #include <commdlg.h> #include <mem.h> #include <string.h> #include <stdlib.h> // Идентификатор редактора текста #define ID_EDIT 1 // Идентификаторы кнопок #define ID_NEW 2 #define ID_OPEN 3 #define ID_SAVE 4 #define ID_PRINT 5 #define ID_EXIT 6 // Прототипы функций BOOL InitApp(HINSTANCE); LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM); HFILE OpenFile(void); HFILE OpenSaveFile(void); int PrintFile(HWND, NPSTR, WORD); // Имя класса окна char const szClassName[] = "TEditAppClass"; // Заголовок окна char const szWindowTitle[] = "Text Editor"; // Идентификатор копии приложения HINSTANCE hInst; // Флаг изменений в тексте BOOL bUpdate; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения // Инициализируем приложение if(!InitApp(hInstance)) return FALSE; // Сохраняем идентификатор копии приложения // в глобальной переменной hInst = hInstance; // После успешной инициализации приложения создаем // главное окно приложения 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.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszMenuName = (LPSTR)NULL; wc.lpszClassName = (LPSTR)szClassName; // Регистрация класса aWndClass = RegisterClass(&wc); return (aWndClass != 0); } // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Идентификатор редактора текста static HWND hEdit; // Идентификаторы кнопок static HWND hButtNew; static HWND hButtOpen; static HWND hButtSave; static HWND hButtPrint; static HWND hButtExit; // Идентификаторы файлов static HFILE hfSrcFile, hfDstFile; switch (msg) { case WM_CREATE: { // Создаем редактор текста hEdit = CreateWindow("edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | WS_HSCROLL | WS_VSCROLL | ES_LEFT | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE, 0, 0, 0, 0, hwnd, (HMENU) ID_EDIT, hInst, NULL); // Устанавливаем максимальную длину // редактируемого текста, равную 32000 байт SendMessage(hEdit, EM_LIMITTEXT, 32000, 0L); // Сбрасываем флаг обновления текста bUpdate = FALSE; // Создаем кнопки hButtNew = CreateWindow("button", "New", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0, 0, 80, 20, hwnd, (HMENU) ID_NEW, hInst, NULL); hButtOpen = CreateWindow("button", "Open", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 80, 0, 80, 20, hwnd, (HMENU) ID_OPEN, hInst, NULL); hButtSave = CreateWindow("button", "Save", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 160, 0, 80, 20, hwnd, (HMENU) ID_SAVE, hInst, NULL); hButtPrint = CreateWindow("button", "Print", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 240, 0, 80, 20, hwnd, (HMENU) ID_PRINT, hInst, NULL); hButtExit = CreateWindow("button", "Exit", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 320, 0, 80, 20, hwnd, (HMENU) ID_EXIT, hInst, NULL); return 0; } case WM_SIZE: { // Устанавливаем размер органа управления // (текстового редактора) в соответствии // с размерами главного окна приложения MoveWindow(hEdit, 0, 20, LOWORD(lParam), HIWORD(lParam) - 20, TRUE); return 0; } // Когда главное окно приложения получает // фокус ввода, отдаем фокус редактору текста case WM_SETFOCUS: { SetFocus(hEdit); return 0; } case WM_COMMAND: { // Обработка извещений текстового редактора if(wParam == ID_EDIT) { // Ошибка if(HIWORD(lParam) == EN_ERRSPACE) { MessageBox(hwnd, "Мало памяти", szWindowTitle, MB_OK); } // Произошло изменение в редактируемом // тексте else if(HIWORD(lParam) == EN_UPDATE) { // Устанавливаем флаг обновления текста bUpdate = TRUE; } return 0; } // Нажата кнопка сохранения текста else if(wParam == ID_SAVE) { WORD wSize; HANDLE hTxtBuf; NPSTR npTextBuffer; // Открываем выходной файл hfDstFile = OpenSaveFile(); if(!hfDstFile) return 0; // Определяем размер текста wSize = GetWindowTextLength(hEdit); // Получаем идентификатор блока памяти, // в котором находится редактируемый текст hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L); // Фиксируем блок памяти и получаем указатель // на него npTextBuffer = (NPSTR)LocalLock(hTxtBuf); // Записываем содержимое блока памяти в файл if(wSize != _lwrite(hfDstFile, npTextBuffer, wSize)) { // При ошибке закрываем файл и выдаем сообщение _lclose(hfDstFile); MessageBox(hwnd, "Ошибка при записи файла", szWindowTitle, MB_OK); return 0; } // Закрываем файл _lclose(hfDstFile); // Расфиксируем блок памяти LocalUnlock(hTxtBuf); // Так как файл был только что сохранен, // сбрасываем флаг обновления bUpdate = FALSE; SetFocus(hEdit); return 0; } // Создание нового файла else if(wParam == ID_NEW) { // Проверяем флаг обновления if(bUpdate) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) return 0; } // Сбрасываем содержимое текстового редактора SetWindowText(hEdit, "\0"); // Сбрасываем флаг обновления bUpdate = FALSE; SetFocus(hEdit); return 0; } // Загрузка файла для редактирования else if(wParam == ID_OPEN) { LPSTR lpTextBuffer; DWORD dwFileSize, dwCurrentPos; // Проверяем флаг обновления if(bUpdate) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) return 0; } // Открываем входной файл. hfSrcFile = OpenFile(); if(!hfSrcFile) return 0; // Определяем размер файла dwCurrentPos = _llseek(hfSrcFile, 0L, 1); dwFileSize = _llseek(hfSrcFile, 0L, 2); _llseek(hfSrcFile, dwCurrentPos, 0); // Размер файла не должен превосходить 32000 байт if(dwFileSize >= 32000) { _lclose(hfSrcFile); MessageBox(hwnd, "Размер файла больше 32000 байт", szWindowTitle, MB_OK); return 0; } // Заказываем память для загрузки файла lpTextBuffer = (LPSTR)malloc(32000); if(lpTextBuffer == NULL) return 0; // Загружаем текст из файла в буфер _lread(hfSrcFile, lpTextBuffer, dwFileSize); // Закрываем буфер двоичным нулем lpTextBuffer[(WORD)dwFileSize] = '\0'; // Закрываем файл _lclose(hfSrcFile); // Переносим содержимое буфера в // текстовый редактор SetWindowText(hEdit, lpTextBuffer); // Освобождаем буфер free((void *)lpTextBuffer); // сбрасываем флаг обновления bUpdate = FALSE; SetFocus(hEdit); return 0; } // ------------------------------------------ // Печать текста // ------------------------------------------ else if(wParam == ID_PRINT) { WORD wSize; HANDLE hTxtBuf; NPSTR npTextBuffer; // Определяем размер текста wSize = GetWindowTextLength(hEdit); // Получаем идентификатор блока памяти, // в котором находится редактируемый текст hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L); // Фиксируем блок памяти и получаем указатель // на него npTextBuffer = (NPSTR)LocalLock(hTxtBuf); PrintFile(hwnd, npTextBuffer, wSize); // Расфиксируем блок памяти LocalUnlock(hTxtBuf); SetFocus(hEdit); return 0; } else if(wParam == ID_EXIT) { // Проверяем флаг обновления if(bUpdate) { if(IDYES == MessageBox(hwnd, "Файл был изменен. Желаете сохранить?", szWindowTitle, MB_YESNO | MB_ICONQUESTION)) return 0; } // Посылаем в функцию главного окна // сообщение WM_CLOSE SendMessage(hwnd, WM_CLOSE, 0, 0L); return 0; } return 0; } case WM_DESTROY: { PostQuitMessage(0); return 0; } } return DefWindowProc(hwnd, msg, wParam, lParam); } // ------------------------------- // Функция OpenFile // Сохранение файла // ------------------------------- HFILE OpenFile(void) { // Структура для выбора файла OPENFILENAME ofn; // Буфер для записи пути к выбранному файлу char szFile[256]; // Буфер для записи имени выбранного файла char szFileTitle[256]; // Фильтр расширений имени файлов char szFilter[256] = "Text Files\0*.txt;*.doc\0Any Files\0*.*\0"; // Идентификатор открываемого файла HFILE hf; // Инициализация имени выбираемого файла // не нужна, поэтому создаем пустую строку szFile[0] = '\0'; // Записываем нулевые значения во все поля // структуры, которая будет использована для // выбора файла memset(&ofn, 0, sizeof(OPENFILENAME)); // Инициализируем нужные нам поля // Размер структуры ofn.lStructSize = sizeof(OPENFILENAME); // Идентификатор окна ofn.hwndOwner = NULL; // Адрес строки фильтра ofn.lpstrFilter = szFilter; // Номер позиции выбора ofn.nFilterIndex = 1; // Адрес буфера для записи пути // выбранного файла ofn.lpstrFile = szFile; // Размер буфера для записи пути // выбранного файла ofn.nMaxFile = sizeof(szFile); // Адрес буфера для записи имени // выбранного файла ofn.lpstrFileTitle = szFileTitle; // Размер буфера для записи имени // выбранного файла ofn.nMaxFileTitle = sizeof(szFileTitle); // В качестве начального каталога для // поиска выбираем текущий каталог ofn.lpstrInitialDir = NULL; // Определяем режимы выбора файла ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; // Выбираем входной файл if (GetOpenFileName(&ofn)) { // Открываем выбранный файл hf = _lopen(ofn.lpstrFile, OF_READ); // Возвращаем идентификатор файла return hf; } // При отказе от выбора возвращаем // нулевое значение else return 0; } // ------------------------------- // Функция OpenSaveFile // Выбор файла для редактирования // ------------------------------- HFILE OpenSaveFile(void) { OPENFILENAME ofn; char szFile[256]; char szFileTitle[256]; char szFilter[256] = "Text Files\0*.txt\0Any Files\0*.*\0"; HFILE hf; szFile[0] = '\0'; memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = NULL; ofn.lpstrFilter = szFilter; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = NULL; ofn.Flags = OFN_HIDEREADONLY; // Выбираем выходной файл if (GetSaveFileName(&ofn)) { // При необходимости создаем файл hf = _lcreat(ofn.lpstrFile, 0); return hf; } else return 0; }
Подробное описание этого файла вы найдете в 12 томе, здесь же для экономии места мы расскажем только о фрагменте, выполняющем печать.
Когда вы нажимаете кнопку "Print", соответствующий обработчик определяет размер текста, загруженного в редактор, вызывая функцию GetWindowTextLength: wSize = GetWindowTextLength(hEdit);
Далее он получает адрес блока памяти, содержащий текст, фиксируя его: hTxtBuf = (HANDLE) SendMessage(hEdit, EM_GETHANDLE, 0, 0L); npTextBuffer = (NPSTR)LocalLock(hTxtBuf);
После этого вызывается функция печати PrintFile, определенная в файле print.cpp (листинг 6.2): PrintFile(hwnd, npTextBuffer, wSize);
В качестве параметров этой функции передаются идентификатор окна приложения, адрес буфера, содержащего печатаемый текст, и размер этого буфера в байтах.
После выполнения печати буфер расфиксируется, после чего редактор текста получает фокус ввода: LocalUnlock(hTxtBuf); SetFocus(hEdit); return 0;
Все функции, предназначенные для работы с принтером, мы вынесли в отдельный файл (листинг 6.2).
Листинг 6.2. Файл prnfile/print.cpp // ---------------------------------------------------- // Функции для работы с принтером // ---------------------------------------------------- #define STRICT #include <windows.h> #include <windowsx.h> #include <commdlg.h> #include <string.h> #include "prnfile.hpp" // Прототипы функций BOOL CALLBACK _export AbortDlgFunc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL CALLBACK _export AbortFunc(HDC hdc, int nCode); HDC GetPrinterDC(HWND); // Внешние глобальные переменные extern HWND hdlgAbort; extern BOOL fAbort; BOOL fAbort = FALSE; HWND hdlgAbort = 0; static PRINTDLG pd; // ---------------------------------------------------- // Функция PrintFile // Печать файла // ---------------------------------------------------- BOOL PrintFile(HWND hwnd, NPSTR npBuff, WORD wSize) { HDC hdc; int cyPage; int cyChar, yPos, nLength; int i; WORD wCurPos = 0; TEXTMETRIC tm; ABORTPROC lpAbortFunc; BOOL fDone; char abBuffer[256]; DOCINFO docinfo; DLGPROC lpAbortDlgFunc; HINSTANCE hInst; int rc; // Получаем контекст устройства для принтера hdc = GetPrinterDC(hwnd); // Определяем разрешение принтера по вертикали cyPage = GetDeviceCaps(hdc, VERTRES); // Определяем метрики текста GetTextMetrics(hdc, &tm); // Вычисляем высоту шрифта cyChar = tm.tmHeight + tm.tmExternalLeading; // Создаем переходник для функции AbortFunc hInst = GetWindowInstance(hwnd); lpAbortFunc = (ABORTPROC)MakeProcInstance((FARPROC)AbortFunc, hInst); // Устанавливаем функцию AbortProc rc = SetAbortProc(hdc, lpAbortFunc); if(rc <= 0) { DeleteDC(hdc); return FALSE; } // Создаем переходник для функции диалога lpAbortDlgFunc = (DLGPROC)MakeProcInstance((FARPROC)AbortDlgFunc, hInst); // Создаем диалог для отмены печати hdlgAbort = CreateDialogParam ( hInst, MAKEINTRESOURCE(IDD_ABORT), hwnd, lpAbortDlgFunc, NULL) ; if(!hdlgAbort) { FreeProcInstance((FARPROC)lpAbortFunc); DeleteDC(hdc); return FALSE; } // Отображаем созданную диалоговую панель ShowWindow(hdlgAbort, SW_SHOWNORMAL); UpdateWindow(hdlgAbort); // Переводим окно приложения в неактивное // состояние EnableWindow(hwnd, FALSE); // Заполняем структуру docinfo docinfo.cbSize = sizeof(docinfo); docinfo.lpszDocName = NULL; docinfo.lpszOutput = NULL; // Начинаем печать документа rc = StartDoc(hdc, &docinfo); if(rc <= 0) { DestroyWindow(hdlgAbort); FreeProcInstance((FARPROC)lpAbortFunc); FreeProcInstance((FARPROC)lpAbortDlgFunc); DeleteDC(hdc); return FALSE; } // Флаг завершения печати документа fDone = FALSE; // Цикл печати страниц документа while(!fDone && !fAbort) { // Начинаем печать страницы документа StartPage(hdc); // Начальная позиция по вертикали yPos = 0; // Цикл по строкам страницы while(yPos + cyChar < cyPage) { // Проверка завершения печати страницы if(wCurPos > wSize) { fDone = TRUE; break; } i=0; nLength = 0; // Цикл по строке // Копируем строку в буфер abBuffer while((npBuff[wCurPos] != 0x0d) && (wCurPos < wSize)) { abBuffer[i] = npBuff[wCurPos]; i++; wCurPos++; nLength++; } // Рисуем одну строку текста TextOut(hdc, 0, yPos, abBuffer, nLength); // Переходим к следующей строке wCurPos += 2; yPos += cyChar ; } // Инициируем печать страницы rc = EndPage(hdc); if(rc < 0) { fAbort = TRUE; break; } } // При аварийном завершении печати вызываем // функцию AbortDoc, при нормальном - EndDoc if(fAbort) AbortDoc(hdc); else EndDoc(hdc); // Активизируем главное окно приложения EnableWindow(hwnd, TRUE); // Удаляем диалоговую панель DestroyWindow(hdlgAbort); // Освобождаем ресурсы FreeProcInstance((FARPROC)lpAbortFunc); FreeProcInstance((FARPROC)lpAbortDlgFunc); DeleteDC(hdc); return TRUE ; } // ---------------------------------------------------- // Функция AbortDlgFunc // Функция диалога для диалоговой панели, // позволяющей прервать процесс печати // ---------------------------------------------------- #pragma argsused BOOL CALLBACK _export AbortDlgFunc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { // Инициализируем флаги case WM_INITDIALOG: { fAbort = FALSE; hdlgAbort = hdlg; return TRUE; } case WM_COMMAND: { // Устанавливаем флаг аварийного завершения печати if (wParam == IDOK || wParam == IDCANCEL) { fAbort = TRUE; return TRUE; } return FALSE; } case WM_DESTROY: { hdlgAbort = 0; return FALSE; } } return FALSE; } // ---------------------------------------------------- // Функция AbortFunc // Обеспечивает возможность работы других // приложений во время печати // ---------------------------------------------------- #pragma argsused BOOL CALLBACK _export AbortFunc(HDC hdc, int nCode) { MSG msg; // Второй цикл обработки сообщений while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { if(!hdlgAbort || !IsDialogMessage (hdlgAbort, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return(!fAbort); } // ---------------------------------------------------- // Функция GetPrinterDC // Выводит на экран диалоговую панель "Print", // с помощью которой можно выбрать принтер. // Возвращает идентификатор контекста для // выбранного принтера // ---------------------------------------------------- HDC GetPrinterDC(HWND hwnd) { BOOL fResult; // Инициализируем структуру PRINTDLG memset(&pd, 0, sizeof(PRINTDLG)); pd.lStructSize = sizeof(PRINTDLG); pd.hwndOwner = hwnd; pd.Flags = PD_RETURNDC; // Отображаем диалоговую панель fResult = PrintDlg(&pd); // При необходимости освобождаем память, полученную // функцией PrintDlg для структур DEVMODE и DEVNAMES if(pd.hDevMode != 0) GlobalFree (pd.hDevMode); if(pd.hDevNames != 0) GlobalFree (pd.hDevNames); // В случае успешного завершения возвращаем // контекст принтера if(fResult) return pd.hDC; else return 0; }
Функция PrintFile выполняет печать файла, загруженного в текстовый редактор.
Она получает контекст печати, вызывая функцию GetPrinterDC, определенную в этом же файле. Функция GetPrinterDC выводит на экран стандартную диалоговую панель "Print", позволяющую выбрать принтер и задать параметры для выбранного принтера.
Далее определяется разрешение принтера по вертикали, метрики текста для шрифта, выбранного в контекст принтера, вычисляется высота шрифта. Вся эта информация необходима для правильного расположения строк текста на листе бумаги.
После этого функция PrintFile создает переходник для функции отмены печати и подключает последнюю, вызывая функцию SetAbortProc.
Далее создается переходник для функции диалога отмены печати, затем создается и выводится на экран диалоговая панель отмены печати. Шаблон диалоговой панели определен в файле ресурсов и содержит единственную кнопку с надписью "Cancel".
После отображения этой панели главное окно приложения переводится в неактивное состояние для передачи фокуса ввода диалоговой панели отмены печати.
Перед началом печати заполняется структура DOCINFO и вызывается функция StartDoc.
В цикле печати страниц документа выполняется проверка глобального флага отмены печати, а также флага завершения печати fDone.
Перед началом печати каждой страницы вызывается функция StartPage.
Далее выполняется построчное копирование текста из буфера текстового редактора в буфер abBuffer с последующим выводом содержимого этого буфера на принтер функцией TextOut.
Печать страницы выполняется после вызова функции EndPage.
При нормальном завершении процесса печати вызывается функция EndDoc, а при аварийном - AbortDoc.
После нормального или аварийного завершения печати документа активизируется главное окно приложения, а диалоговая панель отмены печати удаляется. Вслед за этим освобождаются созданные переходники и контекст принтера.
Идентификатор диалоговой панели определен в файле prnfile.hpp (листинг 6.3).
Листинг 6.3. Файл prnfile/prnfile.hpp #define IDD_ABORT 25
Шаблон диалоговой панели отмены печати определен в файле описания ресурсов приложения (листинг 6.4).
Листинг 6.4. Файл prnfile/prnfile.rc #include <g:\tcwin\include\windows.h> #include "prnfile.hpp" IDD_ABORT DIALOG 50, 30, 89, 43 STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU CAPTION "Печать..." BEGIN CONTROL "Cancel", IDCANCEL, "BUTTON", WS_GROUP, 29, 23, 32, 14 CTEXT "Cancel - отмена печати", -1, -1, 8, 90, 8, WS_CHILD | WS_VISIBLE | WS_GROUP END
Файл определения модуля приведен в листинге 6.5.
Листинг 6.5. Файл prnfile/prnfile.def ; ============================= ; Файл определения модуля ; ============================= NAME PRNFILE DESCRIPTION 'Приложение PRNFILE, (C) 1994, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 5120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple
Рисование DIB
Рисование DIB
Если отображаемый bmp-файл содержит таблицу цветов, и на предыдущем этапе была создана палитра, ее следует выбрать в контекст отображения и реализовать: hOldPal = SelectPalette(hdc, hPal, FALSE); RealizePalette(hdc);
После этого вы можете нарисовать DIB одним из двух способов.
Первый способ рисования заключается в предварительном преобразовании изображения DIB в изображение DDB с последующим рисованием изображения DDB. Вы уже умеете рисовать изображение DDB, для этого его следует выбрать в специально созданный контекст памяти и затем отобразить функцией BitBlt или StretchBlt.
Для преобразования DIB в DDB вы должны использовать функцию SetDIBits : int WINAPI SetDIBits( HDC hdc, // контекст отображения HBITMAP hbmp, // изображение DDB UINT uStartScan, // номер первой строки UINT uScanLines, // количество строк const void FAR* lpvBits, // биты изображения BITMAPINFO FAR* lpbmi, // заголовок изображения UINT fuColorUse); // содержимое таблицы цветов
Параметр hdc должен содержать идентификатор контекста отображения, в котором будет отображаться полученное изображение DDB.
Через параметр hbmp следует передать идентификатор битового изображения DDB, совместимого с контекстом hdc. Его можно создать при помощи функции CreateCompatibleBitmap. После преобразования это изображение можно будет использовать для рисования функциями BitBlt или StretchBlt.
Параметр uStartScan задает номер строки сканирования битового изображения, начиная с которого будет выполняться преобразование. Если вам нужно нарисовать все изображение целиком, для этого параметра следует задать значение0.
Параметр uScanLines определяет количество строк сканирования, участвующих в преобразовании. Если нужно преобразовать все изображение, для этого параметра следует указать высоту изображения, взятую из заголовка BITMAPINFOHEADER.
Через параметр lpvBits следует передать указатель на область памяти, содержащую биты изображения в формате DIB.
В процессе преобразования функция SetDIBits использует заголовок bmp-файла BITMAPINFO, указатель на который следует передать через параметр lpbmi.
Последний параметр fuColorUse указывает функции на содержимое таблицы цветов, которая расположена сразу после структуры BITMAPINFOHEADER. Возможны два значения - DIB_RGB_COLORS и DIB_PAL_COLORS.
Если указано значение DIB_RGB_COLORS , таблица цветов содержит RGB-цвета, которые можно использовать для создания палитры. Если же указано значение DIB_PAL_COLORS , таблица цветов содержит 16-битовые ссылки на элементы системной палитры.
Если вы загрузили bmp-файл в память, таблица цветов обычно содержит именно RGB-цвета, поэтому для преобразования и последующего рисования изображения вы должны указать значение DIB_RGB_COLORS.
Возвращаемое функцией SetDIBits значение равно количеству преобразованных строк сканирования или нулю при ошибке.
Поясним процесс рисования на простом примере.
Пусть мы загрузили изображение DIB с шириной wWidth и высотой wHeight. Создаем изображение DDB, совместимое с контекстом отображения hdc и имеющее те же размеры. Для этого воспользуемся функцией CreateCompatibleBitmap : hbmp = CreateCompatibleBitmap(hdc, wWidth, wHeight);
Создадим также контекст памяти, совместимый с контекстом отображения: hMemDC = CreateCompatibleDC(hdc);
Далее вызываем функцию SetDIBits, которая преобразует биты изображения DIB и запишет их в созданное нами изображение DDB с идентификатором hbmp: SetDIBits(hdc, hbmp, 0, wHeight, lpDibBits, (LPBITMAPINFO)lpih, DIB_RGB_COLORS);
Теперь нам нужно нарисовать полученное изображение DDB. Для этого выбираем его в контекст памяти и переносим в контекст отображения, например, функцией BitBlt: hbmp = (HBITMAP)SelectObject(hMemDC, hbmp); BitBlt(hdc, x, y, wWidth, wHeight, hMemDC, 0, 0, SRCCOPY);
Все! Изображение нарисовано. Теперь можно удалить контекст памяти, не забыв перед этим выбрать в него старое битовое изображение (размером 1х1 пиксел): DeleteObject(SelectObject(hMemDC, hbmp)); DeleteDC(hMemDC);
Второй способ нарисовать DIB немного проще: StretchDIBits(hdc, x, y, wWidth, wHeight, 0, 0, wWidth, wHeight, lpDibBits, (LPBITMAPINFO)lpih, DIB_RGB_COLORS, SRCCOPY);
Прототип функции StretchDIBits выглядит несколько громоздко, однако эта функция дополнительно позволяет масштабировать рисуемое изображение. Функция имеет параметры, аналогичные параметрам функций StretchBlt и SetDIBits: BOOL WINAPI StretchDIBits( HDC hdc, // контекст для рисования int nXDest, // x-координата верхнего левого угла // области рисования int nYDest, // y-координата верхнего левого угла // области рисования int nWidthDest, // новая ширина изображения int nHeightDest, // новая высота изображения int nXSrc, // x-координата верхнего левого угла // исходной области int nYSrc, // y-координата верхнего левого угла // исходной области int nWidthSrc, // ширина исходного изображения int nHeightSrc, // высота исходного изображения const void FAR* lpvBits, // биты изображения BITMAPINFO FAR* lpbmi, // заголовок изображения UINT fuColorUse, // содержимое таблицы цветов DWORD dwRop); // код растровой операции
Возвращаемое значение равно количеству преобразованных строк сканирования или нулю при ошибке.
Рисование дуги эллипса
Рисование дуги эллипса
К сожалению, возможности рисования кривых линий при помощи функций GDI ограничены - единственная функция Arc позволяет нарисовать дугу эллипса или окружности: BOOL WINAPI Arc( HDC hdc, // идентификатор контекста отображения int nxLeft, int nyTop, // верхий левый угол int nxRight, int nyBottom, // правый нижний угол int nxStart, int nyStart, // начало дуги int nxEnd, int nyEnd); // конец дуги
Первый параметр этой функции определяет контекст отображения, в котором будет нарисована дуга. Для объяснения назначения остальных параметров обратимся к Рисунок 2.13.
Рисование изображения DDB
Рисование изображения DDB
Итак, мы загрузили битовое изображение в память и определили его параметры. Теперь наша задача заключается в том, чтобы нарисовать загруженное изображение в окне приложения.
Как мы уже говорили, в программном интерфейсе Windows (а точнее, в программном интерфейсе GDI) нет функции, предназначенной для рисования битовых изображений. Как же быть?
Используется следующая последовательность действий.
Прежде всего надо создать контекст памяти, совместимый с контекстом отображения реального устройства вывода. Для этого следует воспользоваться функцией CreateCompatibleDC.
Далее необходимо выбрать предварительно загруженное битовое изображение в контекст памяти с помощью функции SelectObject, указав ей в качестве параметров идентификатор контекста памяти и идентификатор загруженного изображения.
Затем нужно скопировать биты изображения из контекста памяти в контекст отображения, вызвав функцию BitBlt или StretchBlt. При этом изображение будет нарисовано на устройстве вывода, которое соответствует контексту отображения.
Рассмотрим реализацию этой последовательности действий на примере функции DrawBitmap , которую мы использовали в приложениях, описанных в предыдущих томах "Библиотеки системного программиста": // ====================================================== // Рисование изображения типа bitmap // ====================================================== #define STRICT #include <windows.h> void DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap) { HBITMAP hbm, hOldbm; HDC hMemDC; BITMAP bm; POINT ptSize, ptOrg; // Создаем контекст памяти, совместимый // с контекстом отображения hMemDC = CreateCompatibleDC(hDC); // Выбираем изображение bitmap в контекст памяти hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap); // Если не было ошибок, продолжаем работу if (hOldbm) { // Для контекста памяти устанавливаем тот же // режим отображения, что используется в // контексте отображения SetMapMode(hMemDC, GetMapMode(hDC)); // Определяем размеры изображения GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&bm); ptSize.x = bm.bmWidth; // ширина ptSize.y = bm.bmHeight; // высота // Преобразуем координаты устройства в логические // для устройства вывода DPtoLP(hDC, &ptSize, 1); ptOrg.x = 0; ptOrg.y = 0; // Преобразуем координаты устройства в логические // для контекста памяти DPtoLP(hMemDC, &ptOrg, 1); // Рисуем изображение bitmap BitBlt(hDC, x, y, ptSize.x, ptSize.y, hMemDC, ptOrg.x, ptOrg.y, SRCCOPY); // Восстанавливаем контекст памяти SelectObject(hMemDC, hOldbm); } // Удаляем контекст памяти DeleteDC(hMemDC); }
В качестве параметров этой функции передается идентификатор контекста отображения hDC, в котором необходимо нарисовать изображение, координаты x и y верхнего левого угла прямоугольной области, в которой будет нарисовано изображение, а также идентификатор самого изображения hBitmap.
Прежде всего функция DrawBitmap создает контекст памяти, совместимый с контекстом отображения, передаваемого через параметр hDC: hMemDC = CreateCompatibleDC(hDC);
В качестве параметра для этой функции можно использовать значение NULL. В этом случае будет создан контекст памяти, совместимый с экраном видеомонитора. Функция возвращает идентификатор созданного контекста или NULL при ошибке.
Далее функция DrawBitmap выбирает изображение в созданный контекст памяти: hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap);
Функция SelectObject возвращает идентификатор битового изображения, которое было выбрано в контекст памяти раньше. Так как мы только что создали контекст памяти, мы получим идентификатор битового изображения, выбранного в контекст памяти по умолчанию. Это монохромное изображение, состоящее из одного пиксела. Контекст памяти необходимо удалить после использования. Перед удалением мы должны выбрать в него изображение, которое было выбрано при создании, т. е. изображение с идентификатором hOldbm.
Теперь мы выбрали наше изображение в контекст памяти и готовы выполнить копирование в контекст отображения. Однако перед этим необходимы некоторые подготовительные действия.
Прежде всего нужно сделать так, чтобы в контексте памяти использовался тот же режим отображения, что и в контексте отображения. По умолчанию при создании контекста памяти (как и любого другого контекста) устанавливается режим отображения MM_TEXT. Однако в контексте отображения, идентификатор которого передается функции DrawBitmap, может быть установлен любой режим отображения, например, метрический. Для обеспечения соответствия режимов отображения удобно использовать функции GetMapMode и SetMapMode : SetMapMode(hMemDC, GetMapMode(hDC));
Функция GetMapMode возвращает код режима отображения, установленного в контексте отображения. Этот код передается в качестве второго параметра функции SetMapMode, которая устанавливает такой же режим отображения в контексте памяти.
Так как функции BitBlt , копирующей биты изображения, необходимо указать координаты прямоугольной области, занимаемой изображением в контексте памяти, следует определить размеры изображения. Это можно сделать с помощью функции GetObject, которую мы только что описали: GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm);
В данном случае нас интересуют ширина и высота изображения в пикселах: ptSize.x = bm.bmWidth; // ширина ptSize.y = bm.bmHeight; // высота
Так как в контексте отображения может быть выбран любой режим отображения, размеры изображения необходимо преобразовать в логические, вызвав функцию DPtoLP: DPtoLP(hDC, &ptSize, 1);
Об этой функции мы рассказывали в разделе, посвященной режимам отображения.
Для копирования битов изображения из контекста памяти в контекст отображения функция DrawBitmap использует функцию BitBlt (читается как "бит-блит"): BOOL WINAPI BitBlt( HDC hdcDest, // контекст для рисования int nXDest, // x-координата верхнего левого угла // области рисования int nYDest, // y-координата верхнего левого угла // области рисования int nWidth, // ширина изображения int nHeight, // высота изображения HDC hdcSrc, // идентификатор исходного контекста int nXSrc, // x-координата верхнего левого угла // исходной области int nYSrc, // y-координата верхнего левого угла // исходной области DWORD dwRop); // код растровой операции
Функция копирует битовое изображение из исходного контекста hdcSrc в контекст отображения hdcDest. Возвращаемое значение равно TRUE при успешном завершении или FALSE при ошибке.
Размеры копируемого изображения задаются парамерами nWidth и nHeight. Координаты левого верхнего угла изображения в исходном контексте определяются параметрами nXSrc и nYSrc, а в контексте, куда копируется изображение, параметрами nXDest и nYDest.
Последний параметр dwRop определяет растровую операцию, используемую для копирования.
Отметим, что размеры и координаты необходимо задавать в логических единицах, соответствующих выбранному режиму отображения.
В нашем случае изображение копируется из точки (0,0) в физической системе координат в точку (x,y) в логической системе координат. Поэтому перед копированием изображения необходимо выполнить преобразование физических координат (0,0) в логические, вызвав функцию DPtoLP: ptOrg.x = 0; ptOrg.y = 0; DPtoLP(hMemDC, &ptOrg, 1);
После этого можно вызывать функцию BitBlt: BitBlt(hDC, x, y, ptSize.x, ptSize.y, hMemDC, ptOrg.x, ptOrg.y, SRCCOPY);
Эта функция копирует битовое изображение, имеющее размеры ptSize, из контекста памяти hMemDC в контекст отображения hDC. При этом логические координаты верхнего левого угла изображения в контексте памяти находятся в структуре ptOrg. Координаты верхнего левого угла прямоугольной области в контексте отображения, куда будет копироваться изображение, передаются через параметры x и y.
После рисования битового изображения функция DrawBitmap выбирает в контекст памяти первоначально выбранное при его создании изображение, состоящее из одного монохромного пиксела, и затем удаляет контекст памяти: SelectObject(hMemDC, hOldbm); DeleteDC(hMemDC);
В качестве кода растровой операции используется константа SRCCOPY. При этом цвет пикселов копируемого изображения полностью замещает цвет соответствующих пикселов контекста отображения.
Мы уже говорили об использовании растровых операций для рисования линий и закрашивания областей. При рисовании битовых изображений вы также можете использовать растровые операции, причем цвет полученного изображения в зависимости от выбранной растровой операции может определяться цветом исходного изображения, цветом поверхности, на которой выполняется рисование, и цветом кисти, выбранной в контекст отображения.
Чаще всего используется код растровой операции SRCCOPY. В этом случае цвет кисти, выбранной в контекст отображения, не имеет значения, так как ни цвет кисти, ни цвет фона не влияют на цвет нарисованного изображения.
Однако вы можете использовать и другие коды растровых операций (всего их 256). В этом случае для вычисления цвета полученного после рисования пиксела можно выбрать практически любое логическое выражение, учитывающее цвет фона, цвет кисти и цвет пиксела изображения.
В файле windows.h описаны константы для наиболее полезных кодов растровых операций. Мы опишем эти константы вместе с соответствующими логическими выражениями. При этом символом S мы будем обозначать цвет исходного изображения, символом D - цвет фона на котором выполняется рисование, и P - цвет кисти, выбранной в контекст отображения.
Рисование эллипса
Рисование эллипса
Для рисования эллипса вы можете использовать функцию Ellipse : BOOL WINAPI Ellipse( HDC hdc, // идентификатор контекста отображения int nxTL, // координата x верхнего левого угла int nyTL, // координата y верхнего левого угла int nxBR, // координата x правого нижнего угла int nyBR); // координата y правого нижнего угла
Первый параметр этой функции указывает идентификатор контекста отображения, остальные - координаты верхнего левого и правого нижнего углов прямоугольника, в который должен быть вписан эллипс (Рисунок 2.19).
Рисование линий
Рисование линий
Приложения Windows могут рисовать прямые и ломаные линии, а также дуги эллипса (и окружности, как частного случая эллипса). Параметры этих линий определяются несколькими атрибутами контекста отображения. Это режим отображения, влияющий на используемую систему координат, цвет фона, режим фона (прозрачный или непрозрачный), режим рисования, цветовая палитра (в режимах, использующих цветовую палитру), перо (может иметь различный цвет, толщину и стиль).
Рисование линий произвольного стиля
Рисование линий произвольного стиля
Как мы уже говорили, вы не можете создать перо для рисования пунктирных, штрих-пунктирных или штриховых линий толщиной больше одного пиксела. Однако в некоторых случаях у вас может возникнуть необходимость в рисовании таких линий.
В программном интерфейсе GDI есть функция с именем LineDDA, которая позволяет рисовать любые линии (правда, основная работа по рисованию линий при этом будет возложена на программиста).
Функция LineDDA имеет следующий прототип: void WINAPI LineDDA( int nxStart, int nyStart, // начальная точка int nxEnd, int nyEnd, // конечная точка LINEDDAPROC lnddaprc, // адрес функции для рисования LPARAM lParam); // дополнительные параметры
Первые четыре параметра этой функции определяют координаты начальной и конечной точки, между которыми надо нарисовать линию.
Через параметр lnddaprc передается указатель на функцию рисования, которая является функцией обратного вызова, определяемой программистом. Эта функция получает управление много раз, она вызывается для каждой точки рисуемой линии.
Для режима STRICT тип LINEDDAPROC определен в файле windows.h следующим образом: typedef void (CALLBACK* LINEDDAPROC)(int, int, LPARAM);
Последний параметр предназначен для передачи дополнительных данных в функцию рисования.
Приведем прототип функции рисования (для функции можно использовать любое имя): void CALLBACK _export LineProc(int xPos, int yPos, LPARAM lParam);
Первые два параметра представляют собой координаты точки, для рисования которых вызвана функция. Последний параметр соответствует последнему параметру функции LineDDA и содержит передаваемое этой функции значение.
Пример использования функции LineDDA вы можете найти ниже в разделе "Приложение DASHLINE".
Рисование ломаной линии
Рисование ломаной линии
Функции Polyline , предназначенной для рисования ломаных линий, следует передать идентификатор контекста отображения hdc, указатель lppt на массив структур POINT, в котором должны находится координаты начала ломаной линии, координаты точек излома и координаты конца ломаной линии, а также размер этого массива cPoints: BOOL WINAPI Polyline( HDC hdc, // идентификатор контекста отображения const POINT FAR* lppt,// указатель на массив структур POINT int cPoints); // размер массива
Функция Polyline возвращает TRUE при нормальном завершении или FALSE при ошибке. Она не использует текущую позицию пера и не изменяет ее.
Если ломаная линия не замкнута, ее последняя точка не рисуется (Рисунок 2.12).
Рисование многоугольников
Рисование многоугольников
Рисование многоугольников (Рисунок 2.22) выполняется функцией Polygon , аналогичной по своим параметрам функции Polyline, с помощью которой рисуются ломаные линии: BOOL WINAPI Polygon( HDC hdc, // идентификатор контекста отображения const POINT FAR* lppt,// указатель на массив структур POINT int cPoints); // размер массива
Через параметр hdc передается идентификатор контекста отображения.
Параметр lppt указывает на массив структур POINT, в котором должны находится координаты вершин многоугольника. Параметр cPoints определяет размер этого массива.
Функция Polygon возвращает TRUE при нормальном завершении или FALSE при ошибке. Она не использует текущую позицию пера и не изменяет ее.
Рисование прямой линии
Рисование прямой линии
Для того чтобы нарисовать прямую линию, приложение должно воспользоваться функцией LineTo : BOOL WINAPI LineTo(HDC hdc, int xEnd, int yEnd);
Эта функция рисует линию из текущей позиции пера, установленной ранее функцией MoveTo или MoveToEx, в точку с координатами (xEnd,yEnd). После того как линия будет нарисована, текущая позиция пера станет равной (xEnd,yEnd).
Функция LineTo возвращает TRUE при нормальном завершении или FALSE при ошибке.
Таким образом, для того чтобы нарисовать прямую линию, приложение должно сначала с помощью функции MoveToEx установить текущую позицию пера в точку, которая будет началом линии, а затем вызвать функцию LineTo, передав ей через параметры xEnd и yEnd координаты конца линии.
Особенностью функции LineTo является то, что она немного не дорисовывает линию - эта функция рисует всю линию, не включая ее конец, т. е. точку (xEnd,yEnd). Это иллюстрируется на Рисунок 2.11.
Рисование прямоугольника
Рисование прямоугольника
Простейшая функция, с помощью которой можно нарисовать прямоугольник, называется Rectangle : BOOL WINAPI Rectangle( HDC hdc, // идентификатор контекста отображения int nxTL, // координата x верхнего левого угла int nyTL, // координата y верхнего левого угла int nxBR, // координата x правого нижнего угла int nyBR); // координата y правого нижнего угла
Функция Rectangle рисует прямоугольник для контекста отображения hdc, возвращая значение TRUE в случае успеха или FALSE при ошибке.
Назначение остальных параметров иллюстрируется Рисунок 2.17.
Рисование сегмента эллипса
Рисование сегмента эллипса
Сегмент эллипса (Рисунок 2.20) можно нарисовать при помощи функции Chord : BOOL WINAPI Chord( HDC hdc, // идентификатор контекста отображения int nxLeft, int nyTop, // верхий левый угол int nxRight, int nyBottom, // правый нижний угол int nxStart, int nyStart, // начало дуги int nxEnd, int nyEnd); // конец дуги
Параметры этой функции аналогичны параметрам рассмотренной нами ранее функции Arc.
Рисование сектора эллипса
Рисование сектора эллипса
Для рисования сектора эллипса (Рисунок 2.21) следует использовать функцию Pie , аналогичную по своим параметрам функциям Arc и Chord: BOOL WINAPI Pie( HDC hdc, // идентификатор контекста отображения int nxLeft, int nyTop, // верхний левый угол int nxRight, int nyBottom, // правый нижний угол int nxStart, int nyStart, // начало дуги int nxEnd, int nyEnd); // конец дуги
Рисование точки
Рисование точки
Функция рисования точки SetPixel устанавливает цвет точки с заданными координатами: COLORREF WINAPI SetPixel( HDC hdc, // контекст отображения int nXPos, // x-координата точки int nYPos, // y-координата точки COLORREF clrref); // цвет точки
Установка первых трех параметров этой функции не должна вызывать у вас никаких затруднений. Параметр hdc определяет контекст отображения, для которого необходимо изменить цвет точки. Параметры nXPos и nYPos определяют координаты точки в системе координат, которая зависит от установленного для контекста hdc режима отображения.
Последний параметр clrref определяет новый цвет точки. О том, как "раскрасить" окно приложения, вы узнаете из третьей главы нашей книги. Тем не менее мы опишем самый простой способ для функции SetPixel.
В файле windows.h есть описание макрокоманды RGB , позволяющей сконструировать цвет в формате COLORREF из отдельных компонент красного (r), зеленого (g) и голубого (b) цвета: #define RGB(r,g,b) \ ((COLORREF)(((BYTE)(r)|((WORD)(g)<<8)) | \ (((DWORD)(BYTE)(b))<<16)))
Вы можете использовать эту макрокоманду совместно с функцией SetPixel для установки, например, красного цвета точки, расположенной в начале системы координат (0,0), следующим образом: SetPixel(hdc, 0, 0, RGB(0xff, 0, 0));
Три параметра макрокоманды RGB позволяют задать любой из примерно 16 млн. цветов и оттенков, однако это не означает, что вы получите на экране точно такой цвет, какой был задан при помощи этой макрокоманды. Скорее всего вы сможете воспользоваться одним из 20 системных цветов. Причины этого вы узнаете в третьей главе. Там же мы расскажем о том, как в некоторых случаях можно расширить используемую гамму цветов.
Функция SetPixel возвращает цвет, который фактически был использован для рисования точки. Как мы только что заметили, возвращенное значение может отличаться от заданного параметром clrref. В случае ошибки оно будет равно -1.
Функция GetPixel позволяет узнать цвет точки, заданной идентификатором контекста отображения и координатами: COLORREF WINAPI GetPixel(HDC hdc, int nXPos, int nYPos);
С помощью следующих трех макрокоманд, определенных в файле windows.h, вы можете определить отдельные цветовые компоненты для значения, возвращаемого функциями SetPixel и GetPixel: #define GetRValue (rgb) ((BYTE)(rgb)) #define GetGValue (rgb) ((BYTE)(((WORD)(rgb)) >> 8)) #define GetBValue (rgb) ((BYTE)((rgb)>>16))
Функции SetPixel и GetPixel используются достаточно редко, так как для построения графических изображений есть более мощные функции.
Рисование замкнутых фигур
Рисование замкнутых фигур
Помимо линий, приложения Windows могут использовать функции GDI для рисования замкнутых закрашенных или незакрашенных фигур, таких как прямоугольники, эллипсы, многоугольники с прямыми и скругленными углами и т. д.
Для закрашивания внутренней области замкнутых фигур используется кисть, задаваемая как атрибут контекста отображения. Внешний контур фигуры обводится пером, которое также выбирается в контекст отображения. Учитываются и остальные атрибуты, установку которых мы рассмотрели для функций рисования линий, такие, как режим отображения, режим фона, код растровой операции.
Мы начнем изучение функций GDI, предназначенных для рисования замкнутых фигур, с функций рисования прямоугольников.
Родительский контекст отображения
Родительский контекст отображения
Родительский контекст отображения используется для дочерних окон. Он позволяет дочерним окнам "унаследовать" атрибуты контекста отображения у родительского окна, что во многих случаях упрощает процедуру настройки этих атрибутов. Например, дочернее окно может использовать для вывода текста тот же шрифт и цвета, что и родительское окно.
Для использования родительского контекста отображения в классе, на базе которого создается дочернее окно, перед регистрацией необходимо указать стиль CS_PARENTDC : wc.style = CS_PARENTDC;
Шрифт
Шрифт
Контекст отображения содержит информацию о том, какой шрифт (font ) используется для вывода текста. По умолчанию текст выводится системным шрифтом с переменной шириной букв в кодировке ANSI.
С помощью функций CreateFont , CreateFontIndirect и SelectObject приложение может выбрать для вывода текста любой другой шрифт, установленный (зарегистрированный) в операционной системе. Для установки шрифта, как вы знаете, следует использовать приложение Control Panel.