Смекни!
smekni.com

Основы графического вывода (стр. 20 из 26)

Формат Windows

Достаточно быстро Microsoft решил расширить возможности битмапов, в связи с чем появилась новые версии структур, описывающих битмап: для описания заголовка BITMAPINFOHEADER и для описания палитры RGBQUAD:

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; typedef struct tagRGBQUAD {BYTE rgbBlue;BYTE rgbGreen;BYTE rgbRed;BYTE rgbReserved;} RGBQUAD;

Первое поле структуры BITMAPINFOHEADER — biSize совпадает по назначению и размеру с полем bcSize структуры BITMAPCOREHEADER. Это поле содержит размер структуры, описывающей данный заголовок. Таким образом, анализируя это поле, можно легко определить, какая версия заголовка используется. Однако здесь имеется один подводный камень — в некоторых ранних источниках времен Windows 3.x утверждается, что все поля этой структуры, начиная с поля biCompression, могут быть пропущены. Собственно в документации, сопровождающей компиляторы есть только одно косвенное упоминание об этом: там строго предупреждается, что для определения размера заголовка битмапа надо обязательноиспользовать поле biSize, а не sizeof (BITMAPINFOHEADER). Таким образом размер структуры BITMAPINFOHEADER может изменяться от 16 до 40 байт; но в любом случае он превышает размер структуры BITMAPCOIREHEADER (12 байт), что позволяет различать заголовки в разных форматах.

На практике мне только один раз встретился битмап с неполным заголовком. Следует заметить также, что в результате проверки оказалось, что все графические пакеты, с которыми я имел дело, отказываются воспринимать такой битмап и сообщают о неверном формате файла; аналогично реагируют на подобные битмапы и современные системы (проверено для Windows–95, Windows–98, WindowsNT 4.0). Фактически можно с достаточной надежностью предполагать, что заголовок будет всегда полным. Такое допущение не принесет сколько–нибудь заметных ограничений в использовании битмапов, созданных другими приложениями. Однако в некоторых случаях можно учесть эту особенность практически без усложнения исходного текста; например, чтение заголовка можно представить таким образом:

// пусть файл с битмапом уже открыт и его хендл = hFile

union {

BIMAPCOREHEADER bmch;

BITMAPINFOHEADER bmih;

} bmh;

DWORD dwSizeHeader;

memset (&bmh, 0, sizeof (bmh));

if (_lread (hFile, &bmh, sizeof (DWORD)) == sizeof (DWORD)) {

dwSizeHeader = bmh.bmih.biSize - sizeof (DWORD);

if (_lread (hFile, &bmh.bmih.biWidth, dwSizeHeader) == dwSizeHeader) {

// заголовокуспешнопрочитан, всенеопределенныеполяобнулены if (bmh.bmih.biSize == sizeof (BITMAPCOREHEADER)) {

// OS/2 битмап, анализируем структуру bmh.bmch

} else {

// Windows битмап, анализируем структуру bmh.bmih}}}

Такой прием позволяет считывать битмапы как формата OS/2, так и формата Windows. С некоторым усложнением он может быть в дальнейшем распространен и на более новые форматы битмапов, появившиеся в Windows–95 и WindowsNT 4.0.

Коротко познакомимся с остальными полями структуры BITMAPINFOHEADER: Поля biWidth и biHeight задают размеры битмапа. Похоже, что максимальный размер в 32 767 x 32 767 пикселей показался разработчикам Windows слишком скромным, поэтому для задания размеров используются двойные слова со знаком (до 2 147 483 647x2 147 483 647 пикселей). Мне, например, битмап, превышающий 30 тысяч пикселей в ширину или высоту, пока еще не встречался.

Поля biPlanes и biBitCount используются так же, как и в заголовке битмапа OS/2, и имеют такие же значения: biPlanes всегда 1, а biBitCount может быть 1, 4, 8 или 24. Аналогично OS/2, если поле biBitCount имеет значение 24, то таблица определения цветов (палитра) пропущена.

Поле biCompression используется, если битмап представлен в сжатом виде, и в этом случае поле biSizeImage указывает реальный размер изображения в байтах. Если используется несжатый формат битмапа, то допустимо указание 0. Вместо чисел, естественно, используются символы BI_RGB (0), BI_RLE4 (1) или BI_RLE8 (2), в зависимости от используемого алгоритма сжатия (RLE–4 или RLE–8), либо несжатый битмап (BI_RGB). Подробнее об алгоритмах сжатия и анализе сжатых битмапов можно узнать из стандартной документации, например, из сопровождающей компиляторы системы помощи.

Поля biXPelsPerMeter и biYPelsPerMeter указывают на рекомендуемые характеристики устройства, на котором будет отображаться битмап. Они могут использоваться, например, для выбора наиболее адекватного битмапа, если предусмотрено несколько вариантов для разных разрешений. Обычно эти поля задают равными 0. Однако, если создаваемый битмап будет отображаться на каком–либо отличном от дисплея устройстве, то эти поля целесообразно задать соответствующими характеристикам устройства, равно как и размер самого битмапа определять исходя из разрешающей способности устройства. Далее такой битмап может легко обрабатываться программами верстки, которые, обнаружив ненулевое значение этих полей, включат его в макет сразу с такими размерами, как требуется.

При этом возникает небольшой нюанс, связанный с тем, что разрешение устройства возвращается функцией GetDeviceCaps в точках на дюйм, а нам требуется задавать в виде числа точек на метр. Возникает необходимость определить соотношение дюйма и метра. Когда я попробовал иметь дело с величиной 25.4 мм/дюйм, то с удивлением обнаружил, что битмап в макете отображается с некоторой погрешностью. Пришлось экспериментально вычислять значение дюйма, принятое в Microsoft (?!); оказалось, что наиболее точный результат дает величина 25.397 мм/дюйм. Фрагмент программы выглядит примерно так:

LPBITMAPINFOHEADER lpbmih = ...; // получаем указатель на BITMAPINFOHEADER

HDC hDC = ...; // контекст устройства вывода

// при вычислениях можно обойтись длинными целыми вместо плавающей запятой,

// пока разрешающая способность устройства не превышает 4294 точек на дюйм,

// а в ближайшем будущем так и будет.

lpbmih->biXPelsPerMeter = (LONG) (

(GetDeviceCaps (hDC, LOGPIXELSX) * 1000000UL) / 25397UL);

В принципе можно вычислить эти величины и другим способом, например так:

lpbmih->biXPelsPerMeter = (LONG) (

(GetDeviceCaps (hDC, HORZRES) * 1000UL) / GetDeviceCaps (hDC, HORZSIZE));

Какой из способов даст более точный результат и каким лучше пользоваться — на усмотрение разработчика. Первый способ использует так называемый «логический дюйм», который даст на устройствах с низким разрешением несколько завешенный результат, зато различимое изображение (особенно это касается текста); помимо этого для многих устройств часто можно выполнить специальную настройку (с помощью панели управления Windows), которая позволит прецизионно установить точные значения. Второй способ отталкивается от физических характеристик устройства и, если они заданы не совсем точно, результат также будет неточным, зато менее зависимым от настройки операционной системы. Например для различных дисплеев часто применяются одни и те–же драйвера, что приводит к тому, что разные дисплеи с разными электронно–лучевыми трубками и разными физическими размерами считаются совершенно одинаковыми. Может быть первый способ предпочтительнее для дисплеев, а второй — для принтеров, размер бумаги для которых стандартизирован куда жестче.

Поле biClrUsed задает количество цветов, задаваемых таблицей определения цветов. Это число может быть меньше, чем число возможных цветов. Если этого поля нет, или его значение 0, то таблица содержит 2, 16 или 256 записей, смотря по количеству бит, отведенных на один пиксель (biBitCount).

Поле biClrImportant определяет число цветов, которые должны быть по возможности точно переданы при отображении битмапа. Значение 0 предполагает, что все цвета должны передаваться как можно точнее. Этим полем можно воспользоваться, если вы сами разрабатываете палитру битмапа — тогда вы можете некоторые цвета (например, цвета, покрывающие большую часть изображения) объявить важными, перечислить их в палитре первыми и этим несколько сократить цветовые искажения при отображении битмапа на устройствах, использующих палитру.

Информация об используемых битмапом цветах размещается сразу после заголовка битмапа в виде массива от 2 до 256 записей типа RGBQUAD или пропущена вовсе, если битмап представлен в истинных цветах. Структура RGBQUAD отличается от RGBTRIPLE только тем, что она дополнена неиспользуемым байтом до границы двойного слова[6].

Аналогично формату OS/2 вводится дополнительная объединяющая структура BITMAPINFO, по смыслу эквивалентная структуре BITMAPCOREINFO.

typedef struct tagBITMAPINFO {

BITMAPINFOHEADER bmiHeader;

RGBQUADbmiColors[1];

} BITMAPINFO;

Полный размер структуры BITMAPINFO можно определить исходя из размера заголовка (обязательно надо брать значение поля biSize, а не sizeof (BITMAPIFOHEADER)) и размера палитры, вычисляемого с учетом поля biClrUsed:

UINT uSizeDibInfo;

LPBITMAPINFOHEADER lpbmih;

uSizeDibInfo = lpbmih->biSize + (

lpbmih->biClrUsed ? lpbmih->biClrUsed : (

lpbmih->biBitCount > 8 ? 0 : (1 << lpbmih->biBitCount))

) * sizeof (RGBQUAD);

Теоретически, этотфрагменткодалишьотносительнокорректен — полеbiClrUsedможетотсутствоватьвструктуреBITMAPINFOHEADER. По идее надо сначала проверить значение поля biSize, и только если поле biClrUsed присутствует в структуре, использовать его значение. Однако этот фрагмент может оказаться совершенно корректным, если осуществлять загрузку заголовка битмапа в специально выделенную для этого структуру BITMAPINFOHEADER, с предварительным обнулением всех полей (примерно так, как в примере на странице 56).

Следует еще раз напомнить, что битмапы с неполным заголовком — современными системами не поддерживаются, так что в принципе не будет ошибки, если посчитать заголовок присутствующим полностью. В то же время битмапы с неполной палитрой — почти типичный случай; например обои Windows–95 часто представлены именно в таком виде, поэтому учитывать возможность задания поля biClrUsed необходимо.

Иногда бывает удобно воспользоваться собственным заменителем структуры BITMAPINFO:

struct {

BITMAPINFOHEADER bmiHeader;

RGBQUAD bmiColors[ 256 + 3 ];