16+
ComputerPrice
НА ГЛАВНУЮ СТАТЬИ НОВОСТИ О НАС




Яндекс цитирования


Версия для печати

Модуль поиска не установлен.

Файловые форматы в ОС Windows Дисковые файлы с расширениями ICO и CUR

23.09.2003

Александр Теут


Общие сведения

В данной статье мы рассмотрим, пожалуй, наиболее распространённые во всех последних версиях Windows дисковые файлы с расширениями ICO и CUR. Как известно, они представляют собой довольно простые хранилища ресурсов иконок и курсоров и могут содержать некоторое количество изображений этих значков различного размера и цветовой глубины.

По своей структуре подобные файлы наиболее близки к другим не менее широко распространенным файлам, имеющим расширение BMP. Принципиальное отличие от последних - в наличии растра маски, который позволяет однозначно реализовать прозрачность значка на экране монитора. Чтобы наглядно представить, из каких растров формируется изображение иконки или курсора, достаточно посмотреть на рисунок 1, на котором в увеличенном виде изображены: сама иконка, растр маски и её цветовой растр.

Даже такая мощная библиотека компонент, как VCL, имея в своём составе целый класс для работы с иконками - TIcon, не позволяет сохранять их в файл более чем с 16 цветами. Поэтому чёткое представление о внутреннем формате файла позволяет при работе с ним достичь максимальной эффективности и преодолеть те ограничения, которые накладываются стандартными библиотеками.

Дальнейшее изложение, главным образом, будет касаться файлов с расширением ico. Что касается cur-файлов, то их структура полностью идентична. Все отличия состоят лишь в численном значении одного поля в заголовочной структуре, и интерпретации численных значений двух других полей этой же структуры, о чём в соответствующих местах будет указано дополнительно. В качестве среды программирования, при демонстрации кода, используется Borland C++ Builder v.5.0.

Внутренняя структура

Внутренняя структура ico-файла схематично представлена на рисунке 2, из которого видно, что устроены подобные файлы действительно просто и до определённой степени стандартно. Сначала идёт заголовочная часть файла, затем собственно сами ресурсы значков. Заголовочная часть, в свою очередь, состоит из блока с информацией о типе файла и количестве ресурсов в нём, и следующими друг за другом блоками, частично описывающими отдельные ресурсы. Каждый ресурс значка, если проводить аналогию с другим очень важным файловым форматом BMP, по сути, представляет собой упакованный DIB (Device-Independent Bitmap - аппаратно-независимый растр), дополненный растром маски.

Основные типы данных и функции для работы с ними

Согласно [1], типы данных, характерные для внутреннего устройства рассматриваемых в данной статье файлов, можно представить с помощью следующих структур: ICONDIR, ICONDIRENTRY, ICONIMAGE. Сразу отметим, что объявление подобных типов не входит ни в один заголовочный файл, поставляемый с Delphi, С++ Builder или Visual C++. Поэтому их следует определять так же, как и пользовательские типы, например, в h-файле формы или в отдельном h-файле, специально созданном для подобных целей.

Вся заголовочная часть файла (рис. 1) заключена в одной структуре - ICONDIR:

typedef struct
{
WORD idReserved;
WORD idType;
WORD idCount;
ICONDIRENTRY idEntries[1];
}ICONDIR, *LPICONDIR;

Первые три поля этой структуры определяют самые общие характеристики файла.

Значением поля idReserved всегда должен быть 0. Его следует использовать, наряду со следующим полем, для проверки принадлежности файла к ico (cur) формату.

Поле idType для файла, описывающего иконку, равно 1, а для файла, описывающего курсор, - 2.

Подчеркнём лишний раз - профессионально написанная программа для работы с подобными ресурсами обязана проверять первые два поля на соответствие с вышеприведёнными значениями, а не полагаться только на тип файлового расширения.

Из следующего поля - idCount - можно узнать, какое количество иконок содержит файл. Одновременно оно определяет размер массива idEntries, элементами которого являются структуры ICONDIRENTRY:

typedef struct
{
BYTE bWidth;
BYTE bHeight;
BYTE bColorCount;
BYTE bReserved;
WORD wPlanes;
WORD wBitCount;
DWORD dwBytesInRes;
DWORD dwImageOffset;
}ICONDIRENTRY, *LPICONDIRENTRY;

Рассмотрим её поля подробнее.

Поля bWidth, bHeight определяют геометрические размеры значка в пикселах.

Численным значением поля bColorCount является количество цветов, использующихся в значке.

Поле bReserved, как следует из названия, зарезервировано для будущих версий Windows и всегда равно 0.

Интерпретация следующих двух полей зависит от типа ресурса.

В случае с файлами, которые имеют расширение ico, поле wPlanes задаёт количество цветовых плоскостей и, как правило, равняется 1, а поле wBitCount определяет битовый формат пиксела, а именно: количество бит, которое требуется для описания цвета одного пиксела. Для cur-файлов эти два поля содержат координаты так называемой "горячей точки" (hotspot) курсора, в первом из них задаётся его X-, а во втором - Y-координата. На этом все отличия между файлами курсоров и иконок заканчиваются.

В поле dwBytesInRes записывается размер соответствующего ресурса значка в байтах.

Поле dwImageOffset задаёт смещение в байтах от начала файла до соответствующего ресурса в нём.

Завершают подобные файлы ряд аппаратно-независимых растров со своими масками. Их можно представить с помощью структуры ICONIMAGE:

typedef struct
{
BITMAPINFOHEADER icHeader;
RGBQUAD icColors[1];
BYTE icXOR[1];
BYTE icAND[1];
} ICONIMAGE, *LPICONIMAGE;

Первые два поля, в свою очередь, также описываются структурами, которые определяют размеры и цветовые характеристики изображения. Третий и четвёртый члены ICONIMAGE являются массивами пикселов цветного растра и растра маски соответственно.

Пожалуй, самыми важными структурами в Win32 GDI (Graphic Device Interface), которые используются для работы с растровыми изображениями, являются BITMAPINFOHEADER и RGBQUAD. Они объявляются в заголовочном файле wingdi.h.

Объявление BITMAPINFOHEADER выглядит следующим образом:

typedef struct tagBITMAPINFOHEADER
{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

Поле biSize задаёт размер в байтах данной структуры и подлежит обязательной инициализации перед объявлением экземпляра структуры.

Поля biWidth и biHeight описывают размеры растра в пикселах, соответственно его ширину и высоту. Очень важным моментом, который присущ и иконкам, и курсорам, является тот факт, что численным значением поля biHeight является удвоенная высота значка - это объясняется наличием двух растров в ресурсе.

Поле biPlanes указывает на количество цветовых плоскостей. Начиная с 32-битных версий Windows, оно всегда равно 1.

Поле biBitCount определяет формат (цветовую глубину) пиксела. Его значениями могут быть только следующие числа: 1, 4, 8, 16, 24, 32. По формуле [2]

1<<biBitCount

вычисляется максимальное количество цветов, которое может содержать изображение. Другим важнейшим параметром, который определяется с помощью этого поля, является количество байт в строке развёртки:

(biWidth * biBitCount+31) / 32 * 4

Как и в случае с "большими" DIB, количество байт в строке должно быть выровнено по границе двойного слова (кратно четырём).

Поле biCompression хранит информацию об алгоритме сжатия байтов перевёрнутого (bottom-up) растра - только такой растр может быть сжат. Допустимыми значениями для него являются: BI_RGB - для несжатого изображения, BI_RLE8 и BI_RLE4 - для сжатого с использованием алгоритма RLE, BI_BITFIELDS - для несжатого изображения с кодировкой 16 и 32 бит/пиксел и наличием трёх битовых масок, расположенных перед цветовой таблицей.

Для полноты картины укажем, что, начиная с Windows 98/2000, появилась возможность присваивать данному полю ещё два значения - BI_JPEG и BI_PNG, указывающие на то, что массив пикселов содержит внедрённое изображение в формате JPEG и PNG соответственно.

Что касается рассматриваемых нами файлов, то значением этого поля всегда является BI_RGB (численный эквивалент - 0).

В поле biSizeImage записывается размер массива пикселов изображения в байтах. Впрочем, при использовании значения BI_RGB для поля biCompression, оно может равняться 0.

Оставшиеся 4 поля этой структуры, для ico(cur)-файлов, согласно [1], должны равняться 0.

Структура RGBQUAD описывает отдельный элемент цветовой таблицы:

typedef struct tagRGBQUAD
{
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;

Поля rgbBlue, rgbGreen, rgbRed задают интенсивности соответственно голубой, зелёной и красной составляющей пиксела.

Поле rgbReserved может содержать численное значение альфа-канала пиксела. В том случае, если для вывода изображения используется функция AlphaBlend(), его величина задаёт степень прозрачности пиксела на приёмной поверхности. Рис.3 демонстрирует отличия в изображениях 32-битной иконки с альфа-каналами, которые получаются при различных способах вывода её на экран монитора. При этом левая иконка копировалась функцией BitBlt(), то есть без поддержки альфа-наложения, а правая - с применением AlphaBlend().

В GDI существует ещё одна чрезвычайно важная структура - BITMAPINFO:

typedef struct tagBITMAPINFO
{
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[1];
} BITMAPINFO, *PBITMAPINFO;

Как видно из объявления этой структуры, её членами являются две только что рассмотренные структуры.

Поскольку указатель на экземпляр BITMAPINFO используется в качестве параметра многими функциями, представляется логичным, при чтении или записи файлов, использовать именно эту структуру вместо ICONIMAGE.

Для примера изобразим внутреннее устройство конкретного ico-файла, содержащего две иконки, через типы данных, описанные в этом разделе (рис. 4):

Функции для чтения из файла и вывода изображения

Имея общее представление о внутренней структуре ico-файлов и типах данных, с помощью которых можно её описать, выберем функции, позволяющие извлекать, по мере необходимости, всю информацию.

На самом деле, выбор этот достаточно широк и не критичен, и зависит, главным образом, от личных пристрастий. Чтобы до некоторой степени придать универсальность дальнейшему изложению, будем использовать набор функций из API, а именно: CreateFile(), ReadFile(), SetPointer(), CloseHandle(). Справочная информация по ним доступна как в Delphi, так и в C++ Builder, поэтому мы не будем здесь останавливаться на их описании.

Сосредоточим своё внимание на функциях из GDI API. Поскольку ico-файл содержит в себе, по крайней мере, два аппаратно-независимых растра, для работы с ними нам необходимы функции, позволяющие выводить соответствующие им изображения, например, на экран монитора. Как ни странно, таких функций существует всего две - StretchDIBits() и SetDIBitsToDevice(). По непонятным причинам первая функция в Windows XP не всегда корректно отображает цвета иконок, поэтому мы будем использовать вторую.

Она объявляется следующим образом:

int SetDIBitsToDevice (HDC hdc, int XDest, int YDest, DWORD dwWidth,
DWORD dwHeight, int XSrc, int YSrc,
UINT uStartScan, UINT cScanLines,
CONST VOID *lpvBits,CONST BITMAPINFO *lpbmi,
UINT fuColorUse);

Через её первый параметр - hdc - передаётся манипулятор контекста устройства приёмной поверхности. Следующие четыре параметра - XDest, YDest, dwWidth, dwHeight - описывают геометрические размеры прямоугольника для вывода изображения на приёмную поверхность в пикселах. Шестой и седьмой параметры - XSrc, YSrc - относятся уже к поверхности источника и определяют координату нижнего левого угла его прямоугольной области в пикселах. Параметр uStartScan задаёт строку в растре, с которой будет начинаться копирование, а cScanLines - количество таких строк. Далее следуют параметры, относящиеся к массиву с DIB - lpvBits и его заголовку с цветовой таблицей - lpbmi. Завершает всё параметр - fuColorUse, который указывает, какие значения содержит в себе член bmiColors структуры BITMAPINFO. Если они представляют собой индексы логической палитры, то он равен DIB_PAL_COLORS, если же RGB-значения, то DIB_RGB_COLORS.

Наряду с этой функцией, нам понадобится функция для копирования аппаратно-зависимых растров (DDB) - BitBlt(). Её прототип выглядит так:

BOOL BitBlt (HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight,
HDC hdcSrc, int nXSrc, int nYSrc, DWORD dwRop);

Первые пять параметров этой функции - hdcDest, nXDest, nYDest, nWidth, nHeight - относятся к приёмной поверхности, три следующих - hdcSrc, nXSrc, nYSrc - к источнику. Последний параметр dwRop определяет тип растровой операции или, другими словами, способ копирования прямоугольного массива пикселов с одного графического устройства на другое. Очень часто он равен SRCCOPY, что соответствует простой замене пикселов приёмника пикселами источника. Забегая вперёд, отметим, что в нашем случае, чтобы обеспечить прозрачность в изображении, механизм копирования должен проходить в два этапа [3]. Сначала на приёмную поверхность будет копироваться чёрно-белая составляющая изображения с операцией SCRAND (итоговый пиксел - результат бинарной операции AND между пикселами источника и приёмника). А затем - его цветная часть с операцией SCRINVERT (итоговый пиксел формируется применением бинарной операции XOR между пикселами источника и приёмника).

Для поддержки альфа-наложения при копировании аппаратно-зависимых растров в Win GDI используется функция AlphaBlend(). Она импортируется из файла msimg32.dll:

BOOL AlphaBlend (HDC hdcDest, int iLeftDest, int iTopDest, int iWidthDest,
int iHeightDest, HDC hdcSource, int iLeftSource,
int iTopSource, int iWidthSource, int iHeightSource,
BLENDFUNCTION alphaBlend);

Через первый её параметр - hdcDest - передаётся манипулятор приёмного контекста устройства. Следующие четыре параметра - iLeftDest, iTopDest, iWidthDest, iHeightDest описывают геометрические размеры области на приёмной поверхности, а именно: координаты верхнего левого угла, ширину и высоту.

Затем идут в том же порядке точно такие же пять параметров, но уже относящиеся к поверхности источника - hdcSource, iLeftSource, iTopSource, iWidthSource, iHeightSource. Завершает всё экземпляр - alphaBlend, структуры BLENDFUNCTION:

typedef struct _BLENDFUNCTION
{
BYTE BlendOp;
BYTE BlendFlags;
BYTE SourceConstantAlpha;
BYTE AlphaFormat;
} BLENDFUNCTION;

Поле BlendOp всегда равняется AC_SRC_OVER и поэтому особых пояснений не требует.

Поле BlendFlags должно быть равно 0, оно зарезервировано для будущих версий Windows.

Поле SourceConstantAlpha определяет численное значение альфа-коэффициента в интервале 0 - 255.

Поле AlphaFormat может принимать только два значения: это 0, когда для всех пикселов растра-источника предполагается постоянный альфа-коэффициент, и AC_SRC_ALPHA, когда пикселы растра-источника содержат данные альфа-канала.

Пример 1: как это сделано в IrfanView?

Чтобы продемонстрировать технику работы с ico-файлами, попытаемся реализовать такую возможность, как выбор произвольного ресурса в файле, с последующим его отображением. В качестве аналога возьмём широко распространённую программу для просмотра изображений - IrfanView (http://www.irfanview. com). Если расширение ico связано на компьютере с этой программой, то при двойном щелчке по такому файлу, в случае, если он содержит несколько иконок, появляется следующее диалоговое окно (рис. 5).

С его помощью можно выводить для просмотра изображения иконок, которые содержат данный файл, выбирая строчки в ListBox.

Для создания подобного пользовательского интерфейса необходимы следующие компоненты: ListBox, Panel, PaintBox, Button и несколько Label. Их следует поместить на модальную форму, которая будет вызываться из главной формы, к примеру, после успешного завершения диалога открытия файла.

Последовательность наших действий при написании программного кода будет основываться на следующем алгоритме: сначала мы откроем файл, выбранный через OpenDialog, найдём, сколько иконок он содержит, считаем всю заголовочную часть файла, попутно заполняя ListBox соответствующими значениями размера иконки. Одновременно мы будем сохранять для каждого ресурса ещё одну очень важную для нас величину - его смещение относительно начала файла, напомним, что оно хранится в поле dwImageOffset структуры ICONDIRENTRY. Далее при щелчке по какой-либо строке в ListBox будем выводить в PaintBox соответствующее ей изображение иконки.

Поскольку перед вами статья, а не код программы для последующей компиляции и компоновки, дальнейшее изложение носит упрощённый характер, и преследует одну цель: продемонстрировать приёмы работы с подобными файлами. Поэтому сознательно опускаются все проверки результатов, возвращаемых функциями, что в реальной программе, конечно, недопустимо.

Итак, предполагаем, что часть программы, ответственная за навигацию по файловому пространству, уже готова. Далее добавим к проекту файл с описанием структур:

#ifndef headerH
#define headerH
#include <windows.h>
//----------------------------------
typedef struct
{
BYTE bWidth;
BYTE bHeight;
BYTE bColorCount;
BYTE bReserved;
WORD wPlanes;
WORD wBitCount;
DWORD dwBytesInRes;
DWORD dwImageOffset;
}ICONDIRENTRY;
typedef struct
{
WORD idReserved;
WORD idType;
WORD idCount;
ICONDIRENTRY idEntries[1];
}ICONDIR;
//----------------------------
#endif

Создадим новую форму для нашего проекта и клонируем пользовательский интерфейс из IrfanView (см. рис. 5).

Объявим в секции private h-файла следующие переменные:

ICONDIR* icdir;
std::auto_ptr<Graphics::TBitmap>bitmap;

В графический объект bitmap будем загружать наше изображение перед выводом его на экран.

Далее, перейдём в cpp-файл. Чтобы использовать указатели на динамически создаваемые переменные, которые уничтожаются автоматически, при выходе из области их определения, подключим библиотеку из STL:

#include <memory>

В обработчике формы OnShow поместим код, инициализирующий экземпляр icdir структуры ICONDIR, а именно: сначала необходимо открыть файл для чтения с помощью функции CreateFile(), затем выделить минимальный размер памяти под экземпляр структуры ICONDIR (поскольку истинного размера мы ещё не знаем) и считать первые шесть байт из файла.

DWORD dwBytesRead;
HANDLE hf = ::CreateFile(Form1 -> OpenDialog1 -> FileName.c_str(),
GENERIC_READ, (DWORD)0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
icdir = (ICONDIR*) malloc (sizeof(ICONDIR));
::ReadFile (hf, icdir, 3 * sizeof(WORD), &dwBytesRead, NULL);

Только после этого вычислим действительный размер памяти, который необходимо выделить под экземпляр структуры ICONDIR, переопределим его и завершим инициализацию, считав из файла следующую порцию данных. Затем манипулятор файла следует освободить.

icdir = (ICONDIR*) realloc (icdir, (3 * sizeof(WORD)+sizeof(ICONDIRENTRY)*
icdir->idCount));
::ReadFile (hf,icdir->idEntries, sizeof (ICONDIRENTRY) *icdir->idCount,
&dwBytesRead, NULL);
CloseHandle (hf);
hf = NULL;
Label1 -> Caption = "This file contains " + (String)icdir -> idCount + " icons.";

Теперь мы обладаем полной информацией, чтобы заполнить ListBox:

ListBox1 -> Clear();

for(int i = 0; i < icdir->idCount; ++i)

{

AnsiString sinfo = (String)i+": "+(String)icdir -> idEntries[i].bWidth+" x "+

(String)icdir -> idEntries[i].bHeight;

ListBox1 -> Items -> AddObject(sinfo, (TObject*) icdir ->

idEntries[i].dwImageOffset);

}

Настало время освободить память, поскольку переменная icdir нам больше не нужна:

free (icdir);

Выделяем первую строку в ListBox и вызываем его обработчик события OnClick:

ListBox1 -> ItemIndex = 0;
ListBox1Click (NULL);

Данные действия позволят нам автоматически выводить изображение первой иконки при вызове данного окна.

Осталось написать собственно код, который позволит выводить такие изображения на канву компонента PaintBox. Сам вывод реализуем стандартно в его обработчике события OnPaint:

PaintBox1 -> Canvas -> Draw (0, 0, bitmap.get());

Таким образом, достаточно будет вызвать метод Repaint() компонента PaintBox, чтобы изображение, содержащееся в объекте bitmap, появилось на экране монитора.

Следующий довольно большой кусок кода необходимо разместить в теле обработчика события OnClick компонента ListBox.

Сначала мы, в очередной раз, откроем файл с иконками. Затем извлечём численное значение смещения, по которому находится соответствующий ресурс, из выбранной нами для этого строки в ListBox. Останется установить указатель в файле на начало этого ресурса.

DWORD dwBytesRead;
HANDLE hf = ::CreateFile(Form1 -> OpenDialog1 -> FileName.c_str(),
GENERIC_READ, (DWORD) 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);
int Offset=(int)reinterpret_ cast<TObject* >(ListBox1->Items->Objects
[ListBox1->ItemIndex]);
::SetFilePointer (hf, Offset, NULL, FILE_BEGIN);

Теперь можно приступить к считыванию структур отдельной иконки. Если снова обратиться к рисунку 4, то указатель в файле на данный момент расположен на начале структуры BITMAPINFO. Считаем её и переопределим поле biHeight, вспомнив, что оно содержит удвоенную высоту иконки.

BITMAPINFO* BmpInfo = (BITMAPINFO*) malloc(sizeof(BITMAPINFO));
::ReadFile(hf, BmpInfo, sizeof (BITMAPINFOHEADER), &dwBytesRead, NULL);
BmpInfo -> bmiHeader.biHeight = BmpInfo -> bmiHeader.biHeight >> 1;
int iWidth = BmpInfo -> bmiHeader.biWidth;
int iHeight = BmpInfo -> bmiHeader. biHeight;

Далее необходимо вычислить цветовую глубину пиксела, что позволит определить размер цветовой таблицы. Для простоты мы не рассматриваем значки с форматами пикселов (5-5-5) и (5-6-5) (а существуют ли такие?), единственное отличие в работе с которыми заключается в дополнительном извлечении битовых масок.

int iNumberColor;
if (BmpInfo->bmiHeader.biBitCount > 16) iNumberColor = 0;
else if (BmpInfo->bmiHeader.biBitCount < 16)
iNumberColor = 1 << BmpInfo->bmiHeader.biBitCount;
else
{
free (BmpInfo);
CloseHandle (hf);
hf = NULL;
return;
}
int iRgbTable = sizeof(RGBQUAD) * iNumberColor;

Переопределяем размер памяти, первоначально выделенный под экземпляр структуры BITMAPINFO, и считываем из файла цветовую таблицу:

BmpInfo = (BITMAPINFO*) realloc(BmpInfo, sizeof(BITMAPINFOHEADER)+
iRgbTable);
::ReadFile (hf, &BmpInfo->bmiColors, iRgbTable, &dwBytesRead, NULL);

Пока мы имеем информацию только о геометрических и цветовых характеристиках изображения иконки (точнее, цветного растра). Необходимо ещё определить размеры в байтах её цветного растра и растра маски, создать и инициализировать массивы для их описания:

int iSizeColor =(iWidth*BmpInfo->bmiHeader.biBitCount+31)/32*4*iHeight;
int iSizeMask = (iWidth+31)/32 * 4 * iHeight;
BYTE* pArrayColor = new BYTE [iSizeColor];
BYTE* pArrayMask = new BYTE [iSizeMask];
::ReadFile (hf, pArrayColor, iSizeColor, &dwBytesRead, NULL);
::ReadFile (hf, pArrayMask, iSizeMask, &dwBytesRead, NULL);

Теперь мы полностью подготовились к выводу цветного растра в контекст какого-нибудь устройства, а именно: у нас имеется структура - BITMAPINFO, и указатель на массив байтов - pArrayColor, значениями которого могут быть либо индексы цветовой таблицы, либо непосредственно сами значения цвета для каждого пиксела изображения.

В то же время для растра маски структуры типа BITMAPINFO мы не имеем, в файле места для неё просто не предусматривается. Поэтому создадим её экземпляр и инициализируем члены самостоятельно. Существует несколько особенностей при создании подобной структуры для растра маски. Поскольку он представляет собой черный силуэт изображения иконки на белом фоне (см. рис. 1), то поле biBitCount должно инициализироваться 1. И, следовательно, цветовая таблица для пикселов растра маски будет содержать всего два элемента: один из них описывает чёрный цвет, другой - белый.

BITMAPINFO* BmpInfoMask = (BITMAPINFO*) new
BYTE[sizeof(BITMAPINFOHEADER)
+ 2 * sizeof(RGBQUAD)];
::ZeroMemory(BmpInfoMask, sizeof (BITMAPINFOHEADER) + 2 *
sizeof(RGBQUAD));
BmpInfoMask -> bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
BmpInfoMask -> bmiHeader.biWidth = iWidth;
BmpInfoMask -> bmiHeader.biHeight = iHeight;
BmpInfoMask -> bmiHeader.biPlanes = 1;
BmpInfoMask -> bmiHeader.biBitCount = (iWidth+31)/32 * 4 * iHeight;
BmpInfoMask -> bmiColors[0].rgbReserved = 0;
BmpInfoMask -> bmiColors[0].rgbRed = 0;
BmpInfoMask -> bmiColors[0].rgbBlue = 0;
BmpInfoMask -> bmiColors[0].rgbGreen = 0;
BmpInfoMask -> bmiColors[1].rgbReserved = 0;
BmpInfoMask -> bmiColors[1].rgbRed = 255;
BmpInfoMask -> bmiColors[1].rgbBlue = 255;
BmpInfoMask -> bmiColors[1].rgbGreen =255;

Также нам понадобятся два объекта GDI DIB-секция. Хотя VCL объекты типа TBitmap и являются их оболочками, но непосредственное использование указателей на контекст устройств последних в функциях SetDIBitsToDevice() может вызывать, время от времени, сообщение об ошибке под Windows 98/ME, а под Windows XP иногда некорректно выводить цветовую составляющую изображения.

HDC hDC = ::GetDC(NULL);
PVOID pcolorBits = NULL;
HBITMAP colorBitmap = ::CreateDIBSection (hDC, BmpInfo,
DIB_RGB_COLORS,
(void **) &pcolorBits,
NULL, (DWORD) 0);
HDC hdcColor = ::CreateCompatibleDC(hDC);
::ReleaseDC (NULL,hDC);
HBITMAP hOldC = ::SelectObject (hdcColor,colorBitmap);
PVOID pmaskBits = NULL;
HBITMAP maskBitmap = ::CreateDIBSection (NULL, BmpInfoMask,
DIB_RGB_COLORS,
(void **)& pmaskBits,
NULL, (DWORD) 0);
HDC hdcMask = ::CreateCompatibleDC(NULL);
HBITMAP hOldM = ::SelectObject (hdcMask,maskBitmap);

Создадим временный объект Tempbitmap для хранения изображения иконки и определим размеры области для рисования.

std::auto_ptr<Graphics::TBitmap> Tempbitmap(new Graphics::TBitmap);
Tempbitmap->Width = iWidth;
Tempbitmap->Height = iHeight;
Tempbitmap->PixelFormat = pf32bit;
Tempbitmap->Canvas->Brush->Color=(TColor)RGB(254,254,254);
Tempbitmap->Canvas->FillRect(Rect (0,0,Tempbitmap->Width,
Tempbitmap->Height));
Tempbitmap->Transparent = true;
Tempbitmap->TransparentColor = (TColor) RGB (254,254,254);
PaintBox1->Width = iWidth;
PaintBox1->Height = iHeight;
Выводим последовательно растры в контекст устройства внутренней памяти, созданной на базе DIB-секции, а затем в Tempbitmap, используя растровые операции SRCAND и SRCINVERT.
::SetDIBitsToDevice (hdcMask,0,0,iWidth, iHeight,0,0,0,iHeight, pArrayMask,
BmpInfoMask,DIB_RGB_COLORS);
::BitBlt (Tempbitmap->Canvas->Handle, 0, 0, iWidth, iHeight, hdcMask, 0, 0, SRCAND);
::SetDIBitsToDevice (hdcColor,0,0,iWidth, iHeight,0,0,0, iHeight, pArrayColor,
BmpInfo, DIB_RGB_COLORS);
::BitBlt (Tempbitmap->Canvas->Handle, 0, 0, iWidth, Height, hdcColor, 0, 0, SRCINVERT);

Освобождаем задействованные объекты GDI и уничтожаем динамические массивы, чтобы избежать утечек ресурсов, выделяемых системой нашему приложению.

::SelectObject (hdcColor,hOldC);
::SelectObject (hdcMask,hOldM);
::DeleteObject (hdcMask);
::DeleteObject (hdcColor);
::DeleteObject (maskBitmap);
::DeleteObject (colorBitmap);
delete[ ] (BYTE*) BmpInfoMask;
delete[ ] (BYTE*) BmpInfo;
delete[ ] pArrayColor;
delete[ ] pArrayMask;
::CloseHandle (hf);
hf = NULL;

Осталось скопировать изображение в объект вitmap и перерисовать PaintBox:

вitmap = Tempbitmap;
PaintBox1 -> Repaint();

Теперь, при выборе строки в ListBox, изображение косвенно связанного с ним ресурса в файле будет отображаться на канве компонента PaintBox.

Пример 2: Иконки с альфа-каналами

С выходом OS Windows XP, всё чаще на архивах клипарта в Интернете можно обнаружить иконки с пиксельным форматом 32 (http://www.foood.net/icons/index.htm, http://www. beeicons.com). То есть, пиксел сам содержит полную информацию о своём цвете и дополнительно - значение альфа-канала, определяющее степень его прозрачности на приёмной поверхности. Мы уже демонстрировали пример (рис. 3), из которого хорошо видны отличия в изображениях одной и той же иконки, если выводить её на экран монитора без учёта и с учётом альфа-канала пикселов. Но если в том случае качество изображения левой иконки не слишком пострадало от игнорирования альфа-канала пикселов, то другой подобный пример, приведённый на рис. 6, убеждает в необходимости организации вывода соответствующих иконок с учётом всех заложенных в них параметров.

Как это ни покажется удивительным, вывод иконки с поддержкой альфа-наложения - задача более простая, чем та, которую мы рассмотрели в первом примере. Всё дело в том, что 32-битные иконки, пикселы которых содержат значения альфа-канала, не нуждаются в растре маски при организации своего вывода на экран монитора (это справедливо и для иконок с цветовой таблицей, лишь бы её RGB-элементы содержали значения альфа-канала).

Модифицируем код из предыдущего примера так, чтобы он позволял организовать вывод изображения иконки с учётом альфа-наложения. (Дополнительно необходимо подключить к проекту файл библиотеки импорта msimg32.lib из папки Borland\CBuilder5 (6)\Lib\PSDK.)

DWORD dwBytesRead;
HANDLE hf =::CreateFile(Form1->OpenDialog1->FileName.c_str(),
GENERIC_READ, (DWORD) 0,NULL, OPEN_ EXISTING,
FILE_ATTRIBUTE_NORMAL, (HANDLE) NULL);
int Offset = (int)reinterpret_cast<TObject* >(ListBox1->Items->
Objects[ListBox1->ItemIndex]);
::SetFilePointer (hf, Offset, NULL, FILE_BEGIN);
BITMAPINFO* BmpInfo = (BITMAPINFO*) malloc(sizeof(BITMAPINFO));
::ReadFile(hf,BmpInfo,sizeof(BITMAPINFOHEADER), &dwBytesRead,NULL);
BmpInfo -> bmiHeader.biHeight = BmpInfo->bmiHeader.biHeight >> 1;
int iWidth = BmpInfo>bmiHeader.biWidth;
int iHeight = BmpInfo->bmiHeader.biHeight;
int iNumberColor;
if (BmpInfo -> bmiHeader.biBitCount > 16) iNumberColor = 0;
else if (BmpInfo -> bmiHeader.biBitCount < 16)
else
{
free (BmpInfo);
CloseHandle (hf);
hf = NULL;
return;
}
iNumberColor = 1 << BmpInfo->bmiHeader. biBitCount;
int iRgbTable = sizeof(RGBQUAD) * iNumberColor;
BmpInfo = (BITMAPINFO*)realloc (BmpInfo, sizeof(BITMAPINFOHEADER)+iRgbTable);
::ReadFile (hf, &BmpInfo -> bmiColors, iRgbTable, &dwBytesRead, NULL);
int iSizeColor = (iWidth * BmpInfo->bmiHeader.biBitCount+31)/32*4*iHeight;
BYTE* pArrayColor = new BYTE [iSizeColor];
::ReadFile (hf, pArrayColor, iSizeColor, &dwBytesRead, NULL);
std::auto_ptr<Graphics::TBitmap> Tempbitmap (new Graphics::TBitmap);
Tempbitmap->Width = iWidth;
Tempbitmap->Height = iHeight;
Tempbitmap->PixelFormat = pf32bit;
Tempbitmap->Canvas->Brush->Color= (TColor)RGB(254,254,254);
Tempbitmap->Canvas->FillRect (Rect(0,0,Tempbitmap->Width,
Tempbitmap->Height));
Tempbitmap->Transparent=true;
Tempbitmap->TransparentColor = (TColor)RGB(254,254,254);
PaintBox1->Width = iWidth;
PaintBox1->Height = iHeight;
HDC hDC = ::GetDC(NULL);
PVOID pcolorBits=NULL;
HBITMAP colorBitmap = ::CreateDIBSection (hDC,BmpInfo,
DIB_RGB_COLORS,(void **)&pcolorBits,
NULL, (DWORD) 0);
HDC hdcColor = ::CreateCompatibleDC(hDC);
::ReleaseDC(NULL,hDC);
HBITMAP hOldC = ::SelectObject(hdcColor, colorBitmap);
::SetDIBitsToDevice (hdcColor,0,0,iWidth, iHeight,0,0,0,iHeight,pArrayColor,
BmpInfo, DIB_RGB_COLORS);
// Инициализируем структуру для учёта альфа-канала пиксела
BLENDFUNCTION blend={ AC_SRC_OVER, 0,255, AC_SRC_ALPHA);
// Не используем функцию BitBlt()!
::AlphaBlend (Tempbitmap ->Canvas->Handle,0,0,iWidth,iHeight,hdcColor,
0, 0, iWidth, iHeight, blend);
::SelectObject(hdcColor, hOldC);
::DeleteObject(hdcColor);
::DeleteObject(colorBitmap);
free(BmpInfo);
delete[ ] pArrayColor;
::CloseHandle(hf);
hf = NULL;
bitmap =Tempbitmap;
PaintBox1->Repaint();

Таким образом, мы полностью игнорировали растр маски при считывании данных из файла и при организации вывода изображения на экран монитора!

В заключение обратим внимание на следующее обстоятельство. Получив доступ как к цветовой таблице, так и к массиву пикселов любого ресурса в рассмотренных здесь типах файлов, можно эффективно производить над ним различные цветовые и геометрические преобразования, включая альфа-наложение.

Автор надеется, что обратная операция - создание и сохранение в файл изображений иконок с любым допустимым в 32-битной Windows форматом пикселов, для читателя будет уже вполне выполнимой.

1. "Icons in Win32" by John Hornick
http://msdn.microsoft.com/code/default.asp? URL=/code/sample.asp? url=/MSDN-FILES/026/002/707/msdncompositedoc.xml

2. "Programming Windows by Charles Petzold"
http://amazon.com

3. Фень Юань. Программирование графики для Windows. - СПб.: Питер, 2002.



статьи
статьи
 / 
новости
новости
 / 
контакты
контакты