лекции / Shchupak_Yu._Win32_API_Razrabotka_prilozheniy_dlya_Windows
.pdf
Растры |
181 |
|
|
|
|
Для несжатых растров DIB, у которых поле biCompression имеет значение BI_RGB, обращение к отдельным пикселам массива является простой операцией, которая реализуется достаточно эффективно. Массив пикселов aBitmapBits можно объя вить как двумерный, что отражало бы семантику хранения в нем строк развертки изображения. Однако основные функции отображения DIB в контексте устрой ства получают в качестве аргумента адрес одномерного массива. Поэтому удоб ней объявлять aBitmapBits как одномерный массив, а прямой доступ к пикселам осуществлять посредством преобразования пары индексов «логического» двумер ного массива в эквивалентный индекс «физического» одномерного массива. При мер прямого доступа к пикселам приведен в листинге 3.5.
Упакованный аппаратно-независимый растр
Если есть файл DIB, который следует загрузить в приложение, то можно считать его непосредственно в выделенный блок памяти. Такой блок называют упакованным (packed) DIB растром. Он содержит все компоненты файла DIB, кроме заголовка bmfHeader. Таким образом, упакованный DIB растр начинается с заголовка информа ционного блока bmiHeader, за которым следуют массив масок, если он нужен, цветовая таблица (если она существует) и массив пикселов. В качестве указателя на упакован ный DIB растр в GDI обычно используется указатель на структуру BITMAPINFO. Сле дует отметить, что термин «упакованный» в данном случае не имеет никакого отно шения к упаковке пикселов в строке развертки. Он просто означает, что компоненты DIB следуют друг за другом в смежных ячейках памяти.
Довольно большое количество функций, входящих в состав Win32 API, полу чает и возвращает упакованные DIB растры. Кроме того, упакованные DIB рас тры используются в работе буфера обмена Windows.
Отображение DIB в контексте устройства
Для вывода DIB растра на поверхность графического устройства предназначены функции StretchDIBits и SetDIBitsToDevice.
Функция StretchDIBits имеет следующий прототип:
int StretchDIBits( |
|
HDC hdc, |
// дескриптор контекста устройства |
int XDest, |
// x-координата приемного прямоугольника |
int YDest, |
// y-координата приемного прямоугольника |
int nDestWidth, |
// ширина приемного прямоугольника |
int nDestHeight, |
// высота приемного прямоугольника |
int XSrc, |
// x-координата исходного прямоугольника |
int YSrc, |
// y-координата исходного прямоугольника |
int nSrcWidth, |
// ширина исходного прямоугольника |
int nSrcHeight, |
// высота исходного прямоугольника |
CONST VOID* lpBits, |
// массив пикселов |
CONST BITMAPINFO* lpBitsInfo, // заголовок информационного блока
UINT iUsage, |
// интерпретация цветовой таблицы |
DWORD dwRop |
// код растровой операции |
); |
|
Координаты и размеры исходного прямоугольника1, относящегося к DIB раст ру, задаются в пикселах. Координаты и размеры приемного прямоугольника,
1 Термины «x координата» и «y координата» относятся к левому верхнему углу прямоугольника.
182 |
Глава 3. GDI. Палитры, растры, метафайлы |
|
|
находящегося на поверхности графического устройства, задаются в логических еди ницах.
Манипулируя этими параметрами, можно получать различные преобразования при выводе изображения. Введем следующие обозначения:
wBmp — ширина растра в пикселах;
hBmp — высота растра в пикселах;
xs — значение аргумента для XSrc;
ys — значение аргумента для YSrc;
ws — значение аргумента для nSrcWidth;
hs — значение аргумента для nSrcHeight;
xd — значение аргумента для XDest;
yd — значение аргумента для YDest;
wd — значение аргумента для nDestWidth;
hd — значение аргумента для nDestHeight.
Исходный прямоугольник определяет, какая часть растра выводится и осуще ствляются ли при этом зеркальные отражения по горизонтали и по вертикали. Возможные значения для соответствующих параметров приведены в табл. 3.6.
Таблица 3.6. Интерпретация параметров исходного прямоугольника
Значения для [xs, ys, ws, hs] |
Исходный прямоугольник |
|
|
0, 0, wBmp, hBmp |
Все изображение, исходная ориентация |
wBmp, 0, –wBmp, hBmp |
Все изображение, зеркальное отражение по горизонтали |
0, –hBmp, wBmp, hBmp |
Все изображение, зеркальное отражение по вертикали |
wBmp, hBmp, –wBmp, –hBmp |
Все изображение, зеркальное отражение по горизонтали |
|
и по вертикали |
0, 0, wBmp, 1 |
Первая строка развертки |
0, 0, 1, hBmp |
Первый столбец развертки |
|
|
Приемный прямоугольник, задаваемый четверкой [xd, yd, wd, hd], определяет место на поверхности графического устройства, в котором размещается выводи мая часть растра, а также указывает, нужно ли масштабировать изображение. Для приемного прямоугольника также могут быть определены зеркальные отражения по горизонтали и по вертикали, которые осуществляются по правилам, аналогич ным приведенным в табл. 3.6. Таким образом, окончательная ориентация зависит как от исходного, так и от приемного прямоугольника.
Если приемный прямоугольник совпадает по размерам с исходным прямоу гольником, то вывод растра осуществляется без масштабирования. В противном случае изображение либо увеличивается, либо уменьшается. Увеличение реали зуется простым повторением пикселов, что обычно ведет к заметному ухудше нию качества изображения. При уменьшении изображения несколько исходных пикселов преобразуются в один пиксел приемного прямоугольника. Алгоритм преобразования зависит от текущего режима масштабирования растра (bitmap stretching mode), являющегося атрибутом контекста устройства. Для изменения режима масштабирования растра предназначена функция SetStretchBltMode:
int SetStretchBltMode(HDC hdc, int iStretchMode);
Растры |
183 |
|
|
|
|
Параметр iStretchMode может принимать одно из значений, указанных в табл. 3.7.
Таблица 3.7. Режимы масштабирования растра
Константа (прежнее имя) |
Êîä |
Описание |
|
|
|
STRETCH_ANDSCANS |
0x1 |
Значение по умолчанию. Пикселы комбинируются |
(BLACKONWHITE) |
|
поразрядной логической операцией И |
STRETCH_ORSCANS |
0x2 |
Пикселы комбинируются поразрядной логической |
(WHITEONBLACK) |
|
операцией ИЛИ |
STRETCH_DELETESCANS |
0x3 |
Сохраняется один пиксел, а остальные удаляются |
(COLORONCOLOR) |
|
|
STRETCH_HALFTONE (HALFTONE) |
0x4 |
Вычисляется средний цвет по нескольким пикселам |
|
|
|
Продолжим рассмотрение параметров функции StretchDIBits.
Параметр lpBits задает адрес массива пикселов. Обратите внимание на то, что он определен как адрес одномерного массива.
Параметру lpBitsInfo передается адрес заголовка информационного блока. Здесь он имеет тип указателя на структуру BITMAPINFO, которая определена в Win32 GDI следующим образом:
typedef struct tagBITMAPINFO { BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[1];
} BITMAPINFO;
Обратите внимание на то, что в этой структуре резервируется место лишь для одного элемента цветовой таблицы. Поэтому будьте осторожны при ее использо вании в каком то другом контексте.
Параметр iUsage определяет интерпретацию цветовой таблицы и обычно прини мает значение DIB_RGB_COLORS, когда таблица содержит RGB составляющие цветов, либо DIB_PAL_COLORS, когда в таблице хранятся индексы логической палитры.
Последний параметр, dwRop, содержит код тернарной растровой операции. Эти операции будут рассматриваться позже, а пока мы будем использовать простей шую растровую операцию SRCCOPY, которая просто копирует пикселы источника в приемник.
Есть и еще одна функция для вывода аппаратно независимых растров — SetDIBitsToDevice. Она обеспечивает вывод DIB растра полностью или частично, с сохранением исходной ориентации и масштаба. Можно было бы сказать, что возможности рассмотренной выше функции StretchDIBits полностью перекрыва ют возможности SetDIBitsToDevice. Однако интерфейс последней функции позво ляет копировать исходный растр частями, строка за строкой, что позволяет зна чительно экономить оперативную память, если это необходимо. Правила использования этой функции приведены в MSDN.
Примеры работы с растром DIB
В этом разделе приводятся два приложения, демонстрирующие технику работы с аппаратно независимыми растрами. В первом приложении растр формируется программно, после чего сохраняется в BMP файле. Во втором приложении растр загружается из BMP файла, а затем воспроизводится в окне программы.
Подробности работы с DIB инкапсулированы в класс C++, который мы назвали KDib. Этот класс предназначен для работы только с несжатыми растрами.
184 |
Глава 3. GDI. Палитры, растры, метафайлы |
|
|
В него включен тот минимум средств, которого хватает для решения рассматрива емых двух задач. Конечно, при желании можно легко расширить класс KDib, доба вив поля и методы, необходимые для решения других подзадач.
ГенераторBMP-растрадляANSI-символа
Программа, приведенная в листинге 3.5, осуществляет генерацию битового образа ANSI символа, заданного десятичным кодом, после чего сохраняет сгенерирован ный образ символа в BMP файле. Битовый образ ANSI символа формируется в формате True Color с кодировкой 24 бит/пиксел. Цветовая таблица в растре отсут ствует.
В программе используется шрифт, содержащийся в контексте устройства по умолчанию, то есть SYSTEM_FONT. Конечно, в будущем вы можете усложнить код программы, чтобы обеспечить выбор любого шрифта и любого размера сим волов, а также формировать не отдельный символ, а полный набор из 256 симво лов1.
Код класса KDib, как обычно, размещается в двух файлах. Интерфейс содержит ся в файле KDib.h, а его реализация — в файле KDib.cpp.
Листинг 3.5. Проект CreateCharBmp
//////////////////////////////////////////////////////////////////////
// KDib.h
#include <windows.h> #include <fstream> #include <string> using namespace std;
class KDib { public:
KDib();
~KDib();
BOOL CreateDib24(int w, int h, const char* fileName); void StoreDib24();
BOOL LoadFromFile(const char* fileName); void SetPixel(int x, int y, COLORREF color);
int Draw(HDC hdc, int xDst, int yDst, int wDst, int hDst, int xSrc, int ySrc, int wSrc, int hSrc, DWORD rop);
int GetWidth() { return width; } int GetHeight() { return height; }
const char* GetError() { return error.c_str(); }
private: |
|
int width; |
|
int height; |
|
int bytesPerLine; |
|
BITMAPFILEHEADER fileHead; |
// заголовок растрового файла |
BITMAPINFOHEADER infoHead; |
// заголовок информационного блока |
BITMAPINFOHEADER* pInfoHead; |
|
BYTE* aBitmapBits; |
// массив пикселов |
int fileHeadSize; |
|
int infoHeadSize; |
|
1Для создания более продвинутой программы желательно сначала ознакомиться с главами, посвя щенными работе с меню и диалоговыми окнами.
Растры |
185 |
|
|
|
|
int imageSize; string error;
ifstream inpFile; ofstream outFile;
};
//////////////////////////////////////////////////////////////////////
// KDib.cpp #include "KDib.h"
KDib::KDib() {
fileHeadSize = sizeof(BITMAPFILEHEADER); fileHead.bfType = 0x4d42;
aBitmapBits = NULL;
}
//==================================================================== KDib::~KDib() {
if (pInfoHead) |
delete [] pInfoHead; |
if (aBitmapBits) delete [] aBitmapBits; |
|
if (outFile) |
outFile.close(); |
} |
|
//==================================================================== |
|
BOOL KDib::CreateDib24(int w, int h, const char* fileName) { width = w;
height = h;
bytesPerLine = ((width * 24 + 31) / 32) * 4; imageSize = bytesPerLine * height;
infoHeadSize = sizeof(BITMAPINFOHEADER);
fileHead.bfSize = fileHeadSize + infoHeadSize + bytesPerLine * height; fileHead.bfOffBits = fileHeadSize + infoHeadSize;
infoHead.biSize = infoHeadSize; infoHead.biWidth = width; infoHead.biHeight = height; infoHead.biPlanes = 1; infoHead.biBitCount = 24; infoHead.biCompression = BI_RGB; infoHead.biSizeImage = imageSize;
aBitmapBits = new BYTE[imageSize]; memset(aBitmapBits, 0, imageSize);
outFile.open(fileName, ios::out | ios::binary | ios::trunc); if (!outFile) return FALSE;
else return TRUE;
}
//==================================================================== BOOL KDib::LoadFromFile(const char* fileName) {
inpFile.open(fileName, ios::in | ios::binary); if (!inpFile) {
error = "Неверное имя файла или каталога."; return FALSE;
}
inpFile.read((char*)&fileHead, fileHeadSize); if (fileHead.bfType != 0x4d42) {
error = "Ýòî íå BMP-ôàéë";
продолжение
186 |
Глава 3. GDI. Палитры, растры, метафайлы |
|
|
Листинг 3.5 (продолжение)
return FALSE;
}
infoHeadSize = fileHead.bfOffBits - fileHeadSize; int fileSize = fileHead.bfSize;
imageSize = fileSize - (fileHeadSize + infoHeadSize);
pInfoHead = (BITMAPINFOHEADER*)(new BYTE [infoHeadSize]); inpFile.read((char*)pInfoHead, infoHeadSize);
width = pInfoHead->biWidth; height = pInfoHead->biHeight;
aBitmapBits = new BYTE[imageSize]; inpFile.read((char*)aBitmapBits, imageSize); return true;
}
//==================================================================== int KDib::Draw(HDC hdc, int xDst, int yDst, int wDst, int hDst,
int xSrc, int ySrc, int wSrc, int hSrc, DWORD rop) {
return StretchDIBits(hdc, xDst, yDst, wDst, hDst, xSrc, ySrc, wSrc, hSrc, aBitmapBits, (CONST BITMAPINFO*)pInfoHead, DIB_RGB_COLORS, rop);
}
//==================================================================== void KDib::SetPixel(int x, int y, COLORREF color) {
int |
row |
= |
y; |
|
|
|
|
int |
col |
= |
3 * x; |
|
|
|
|
aBitmapBits[row*bytesPerLine + col] |
= GetBValue(color); |
||||||
aBitmapBits[row*bytesPerLine |
+ |
col+1] = |
GetGValue(color); |
||||
aBitmapBits[row*bytesPerLine |
+ |
col+2] |
= |
GetRValue(color); |
|||
}
//==================================================================== void KDib::StoreDib24() {
//Запись заголовка BMP-файла outFile.write((char*)&fileHead, fileHeadSize); outFile.write((char*)&infoHead, infoHeadSize);
//Запись массива пикселов outFile.write((char*)aBitmapBits, imageSize);
}
//////////////////////////////////////////////////////////////////////
// CreateCharBmp.cpp #include <windows.h> #include "KWnd.h"
#include "KDib.h"
#define ANSI_CODE 65
#define FILE_NAME "symbol.bmp"
KDib bmp;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //==================================================================== int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
Растры |
187 |
|
|
|
|
|
KWnd mainWnd("CreateCharBmp", |
hInstance, nCmdShow, WndProc); |
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);
}
return msg.wParam;
}
//==================================================================== LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HDC hDC; PAINTSTRUCT ps; TEXTMETRIC tm; SIZE sz;
static int width, height; COLORREF color;
int x, y;
BOOL isFileCreated; static char line[2];
switch (uMsg)
{
case WM_CREATE:
hDC = GetDC(hWnd); GetTextMetrics(hDC, &tm); line[0] = ANSI_CODE; line[1] = 0;
GetTextExtentPoint32(hDC, line, 1, &sz); width = sz.cx;
height = tm.tmHeight;
isFileCreated = bmp.CreateDib24(width, height, FILE_NAME); if (!isFileCreated)
MessageBox(hWnd, "Файл " FILE_NAME " не создан.", "Error", MB_OK);
ReleaseDC(hWnd, hDC); break;
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps); SetBkColor(hDC, RGB(0,0,0)); SetTextColor(hDC, RGB(255,255,255)); TextOut(hDC, 0, 0, line, 1);
//Сканирование изображения символа (от последней строки к первой)
//Запись в массив пикселов DIB
for (y = 0; y < height; ++y) { for (x = 0; x < width; ++x) {
color = GetPixel(hDC, x, height-1-y); bmp.SetPixel(x, y, color);
}
}
bmp.StoreDib24();
EndPaint(hWnd, &ps);
продолжение
188 |
Глава 3. GDI. Палитры, растры, метафайлы |
|
|
Листинг 3.5 (продолжение)
break;
case WM_DESTROY: PostQuitMessage(0); break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
//////////////////////////////////////////////////////////////////////
Конечно, требуется сделать несколько пояснений по интерфейсу и реализа ции класса KDib. Мы надеемся, что большая часть кода понятна благодаря тому, что вы внимательно прочли теоретический материал по DIB.
Такие поля класса, как width, height, bytesPerLine, fileHead, infoHeadи aBitmapBits, не требуют особых комментариев. Но для чего нужен указатель на BITMAPINFOHEADER, который хранится в поле pInfoHead? Чуть позже, рассматривая метод LoadFromFile, мы увидим, что это поле используется для хранения адреса блока памяти, выделя емого из кучи при помощи оператора new. Выделенный блок памяти предназначен для заголовка информационного блока и всего того, что может следовать за ним, напри мер цветовых масок и/или цветовой таблицы. Дело в том, что формат загружаемого файла может быть любым, и поэтому заранее неизвестно, сколько памяти потребует ся для указанных компонентов.
Кроме традиционных для класса методов, таких как конструктор и деструк тор, класс KDib содержит методы, перечисленные в следующем списке:
Метод CreateDib24 создает в памяти 24 разрядный растр с заданными шириной
ивысотой. В процессе создания осуществляется инициализация полей заголовка растрового файла и заголовка информационного блока. После этого выделяется память для хранения массива пикселов. В завершение открывается дисковый файл с заданным именем, предназначенный для сохранения в нем создаваемого растра.
Метод SetPixel осуществляет прямой доступ к пикселам в 24 разрядном растре. Цветовая информация для синей, зеленой и красной составляющих цвета пик села записывается в соответствующие элементы массива aBitmapBits. Адреса эле ментов вычисляются через номер строки развертки y и номер пиксела в строке x.
Метод StoreDib24 сохраняет созданный растр, записывая его на диск в формате BMP.
Метод LoadFromFile загружает BMP файл с заданным именем, считывая его с диска в оперативную память. В процессе загрузки сначала читается заголовок растрового файла, затем вычисляются размер заголовка информационного бло ка в совокупности с цветовой таблицей, который указывается в поле infoHeadSize. Помимо этого в поле imageSize указывается вычисленный размер изображения в байтах. Полученные величины используются для указания размеров динами чески выделяемой памяти под заголовок информационного блока pInfoHead
ипод массив пикселов aBitmapBits. После выделения указанной памяти соответ ствующие компоненты растра читаются из файла.
Метод Draw выводит растр на поверхность графического устройства, используя функцию StretchDIBits.
Растры |
189 |
|
|
|
|
Теперь можно перейти к рассмотрению модуля CreateCharBmp.cpp. Обратите вни мание на то, что в глобальной области видимости имеются следующие объявления:
Объект bmp класса KDib.
Макрос ANSI_CODE, определяющий десятичный код ANSI символа. Значение 65 соответствует прописной букве «A» английского алфавита.
Макрос FILE_NAME, задающий имя BMP файла, сохраняемого в текущем ката логе программы.
Разбирая код оконной процедуры WndProc, обратите внимание на следующие ее особенности:
В блоке обработки сообщения WM_CREATE текстовые метрики текущего шрифта читаются в переменную tm при помощи функции GetTextMetrics. Из всех метрик потребуется только высота символа, которая запоминается в переменной height. После этого формируется C строка line, предназначенная для последующего ото бражения в клиентской области окна программы. В первый байт строки записы вается код ANSI_CODE, во второй — нуль.
Теперь можно определить ширину символа, заданного кодом ANSI_CODE. Для этого вызывается функция GetTextExtentPoint32 с передачей ей в качестве второго ар гумента строки line. Искомая ширина запоминается в переменной width. В завер шение при помощи метода bmp.CreateDib24 создается DIB растр в памяти при ложения.
В блоке обработки сообщения WM_PAINT установливаются белый цвет текста и черный цвет фона графических элементов. Затем вызывается функция TextOut, которая отвечает за прорисовку нужного символа.
После этого изображение символа построчно сканируется от последней стро ки к первой при помощи функции GetPixel. Получаемый цвет пиксела заносится в растр вызовом метода bmp.SetPixel. Заполненный растр сохраняется на диске вызовом метода bmp.StoreDib24.
Теперь проверьте работу приложения, откомпилировав и запустив проект1. Про грамма должна создать файл symbol.bmp, содержащий изображение, показанное на рис. 3.6. Изображение на рисунке увеличено в 8 раз при помощи редактора MS PAINT. Сетка на рисунке также нанесена инструментами этого редактора.
Рис. 3.6. Изображение из файла symbol.bmp
В рассмотренной программе используются не все методы класса KDib. Так, мето ды LoadFromFile и Draw будут применены только во втором примере.
1 Не забудьте добавить к проекту файлы KWnd.h и KWnd.cpp, код которых приведен в листинге 1.2.
190 |
Глава 3. GDI. Палитры, растры, метафайлы |
|
|
ПросмотрщикBMP-файла
Программа, приведенная в листинге 3.6, является примитивным просмотрщиком BMP файлов. Спектр ее возможностей действительно не слишком велик. Она может воспроизводить только несжатые файлы. Это раз. У ее окна нет полос про крутки, и поэтому приложение может показывать только те файлы, которые по мещаются на экране дисплея. Это два. И наконец, имя файла задается при помо щи макроса в тексте программы, что говорит о сугубо учебном характере этого проекта.
Впрограмме используется тот же класс KDib, с которым вы познакомились
впредыдущем приложении. Естественно, код этого класса не будет приводиться повторно. Для сборки приложения вам просто нужно взять файлы KDib.h и KDib.cpp из предыдущего проекта и подключить их к данному проекту.
Листинг 3.6. Проект BmpFileViewer
//////////////////////////////////////////////////////////////////////
// BmpFileViewer.cpp #include <windows.h> #include "KWnd.h"
#include "KDib.h"
#define FILE_NAME "YoungHacker.bmp"
KDib bmp;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //==================================================================== int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
KWnd mainWnd("BmpFileViewer", hInstance, nCmdShow, WndProc);
while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);
}
return msg.wParam;
}
//==================================================================== LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HDC hDC; PAINTSTRUCT ps; RECT rect;
int dX, dY;
int ws, hs, wd, hd; static BOOL isFileLoaded;
switch (uMsg)
{
case WM_CREATE:
hDC = GetDC(hWnd);
isFileLoaded = bmp.LoadFromFile(FILE_NAME);
