47952 (597372), страница 17
Текст из файла (страница 17)
// пусть файл с битмапом уже открыт и его хендл = 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 и Windows NT 4.0.
Коротко познакомимся с остальными полями структуры BITMAPINFOHEADER: Поля biWidth и biHeight задают размеры битмапа. Похоже, что максимальный размер в 32 767 x 32 767 пикселей показался разработчикам Windows слишком скромным, поэтому для задания размеров используются двойные слова со знаком (до 2 147 483 647 x 2 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;
RGBQUAD bmiColors[1];
} BITMAPINFO;
Полный размер структуры BITMAPINFO можно определить исходя из размера заголовка (обязательно надо брать значение поля biSize, а не sizeof (BITMAPIFOHEADER)) и размера палитры, вычисляемого с учетом поля biClrUsed:
UINT uSizeDibInfo;
LPBITMAPINFOHEADER lpbmih;
uSizeDibInfo = lpbmih->biSize + (
lpbmih->biClrUsed ? lpbmih->biClrUsed : (
lpbmih->biBitCount > 8 ? 0 : (1 ) * sizeof (RGBQUAD); Теоретически, этот фрагмент кода лишь относительно корректен — поле biClrUsed может отсутствовать в структуре BITMAPINFOHEADER. По идее надо сначала проверить значение поля biSize, и только если поле biClrUsed присутствует в структуре, использовать его значение. Однако этот фрагмент может оказаться совершенно корректным, если осуществлять загрузку заголовка битмапа в специально выделенную для этого структуру BITMAPINFOHEADER, с предварительным обнулением всех полей (примерно так, как в примере на странице 56). Следует еще раз напомнить, что битмапы с неполным заголовком — современными системами не поддерживаются, так что в принципе не будет ошибки, если посчитать заголовок присутствующим полностью. В то же время битмапы с неполной палитрой — почти типичный случай; например обои Windows–95 часто представлены именно в таком виде, поэтому учитывать возможность задания поля biClrUsed необходимо. Иногда бывает удобно воспользоваться собственным заменителем структуры BITMAPINFO: struct { BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[ 256 + 3 ]; } bitmapheader; В этой структуре резервируется достаточное пространство для удержания заголовка битмапа и палитры плюс еще некоторая информация (3 дополнительные записи RGBQUAD = 12 байт), о которой будет рассказано ниже, в разделе «Формат Win32 (Windows NT 3.x)». Определение размера области данных для хранения изображения осуществляется точно также, как и в случае OS/2, за небольшой оговоркой — полученный размер является максимальным. Если используются сжатые битмапы (biCompression равно BI_RLE4 или BI_RLE8), то реальное изображение может оказаться существенно меньшим. Вообще говоря, определение размера сжатого изображения возможно только после того, как это изображение полностью построено — так как возможность сжатия данных и степень сжатия очень сильно зависят от характера самих данных. Таким образом, при выделении пространства под вновь создаваемые битмапы стоит выделять максимально необходимый объем пространства, а при сохранении в сжатом виде этот размер вам вернет GDI, так как собственно сжатие осуществляется именно им. DWORD dwSizeImage; LPBITMAPCOREHEADER lpbmih; // считаем, что указатель на заголовок нам дан dwSizeImage = ( (lpbmih->biWidth * lpbmih->biBitCount + 31) >> 3) & ~3L; dwSizeImage *= lpbmih->biHeight; Формат Win32 (Windows NT 3.x) При расширении возможностей битмапов, реализованных в Win32 API (ранние версии Windows–95, Windows NT 3.x) удалось обойтись без изменения размера заголовка битмапа; изменения коснулись только способов задания некоторых полей и описания цветов. Всего можно перечислить несколько новшеств: перечисление строк развертки как снизу–вверх, так и сверху–вниз; добавление двух новых цветовых форматов: 16 и 32 бита на пиксель (так называемые HiColor); в случае форматов 16 и 32 бита на пиксель новый способ описания цветов — вместо палитры задаются маски цветов. Рассмотрим эти новшества подробнее. Во–первых, для того, что бы разобраться в том, какой порядок перечисления строк развертки используется, надо обратить внимание на поле biHeight. Если строки развертки перечисляются сверху–вниз, то это поле будет представлено отрицательной величиной. В связи с этим при определении размеров изображения необходимо использовать абсолютную величину поля biHeight. DWORD dwSizeImage; LPBITMAPCOREHEADER lpbmih; // считаем, что указатель на заголовок нам дан dwSizeImage = ( (lpbmih->biWidth * lpbmih->biBitCount + 31) >> 3) & ~3L; dwSizeImage *= abs (lpbmih->biHeight); // 1 Во–вторых, новые цветовые форматы (16 и 32 бита на пиксель) первоначально (Windows NT 3.x) требовали нескольких одновременных изменений в битмапе: палитра отсутствует, так как изображение сохраняется практически в истинных цветах (даже 16 бит на пиксель дает возможность описать 65 536 разных цветов); вместо палитры записываются три двойных слова, представляющего соответственно маски красной, зеленой и синей компонент (эти маски позволяют GDI разобраться, какие биты в 16ти или 32х битовом номере цвета передают соответствующую компоненту цвета); поле biCompression задается равным BI_BITFIELDS, что бы подчеркнуть отсутствие палитры и наличие вместо нее масок цветов. Это значение позволяет старым приложениям распознать неподдерживаемый формат битмапа до того, как возникнет ошибка, связанная с попыткой прочитать палитру. Все три изменения осуществлялись одновременно и были обязательны для HiColor битмапа. Однако по мере развития в этот формат были внесены некоторые изменения. Так, существенно упрощенный GDI в Windows–95 потребовал задания фиксированных масок цветов, работать как в Windows NT с произвольными масками было чересчур сложно7. В Windows–95 разрешено применять следующие маски цветов: Формат Красный Зеленый Синий 16 бит/пиксель, 5–5–5 (32 768 цветов): 0x00007C00L 0x000003E0L 0x0000001FL 16 бит/пиксель, 5–6–5 (65 536 цветов) 8: 0x0000F800L 0x000007E0L 0x0000001FL 32 бит/пиксель, 8–8–8 (16 777 216 цветов): 0x00FF0000L 0x0000FF00L 0x000000FFL Таким образом для 16ти и 32х битовых битмапов появились стандартные маски цветов, которые будут использоваться по умолчанию, если в самом битмапе эти маски не определены; в этом случае поле biCompression задается равным BI_RGB, а не BI_BITFIELDS. Теперь режим BI_BITFIELDS не обязательно должен устанавливаться для 16ти и 32х битовых битмапов, он используется только в том случае, если заголовок битмапа содержит маски. Если маски присутствуют, то они перечисляются сразу за заголовком битмапа (BITMAPINFOHEADER) в приведенном в таблице порядке — красный, зеленый и синий цвета. Кроме того, в случае 16ти, 24х или 32х бит на пиксель и режима BI_RGB появилась возможность задавать палитру (поле biClrUsed должно быть ненулевым — палитра для максимально допустимого числа цветов в этих форматах чересчур громоздка). Смысл включения палитры теперь связан не с необходимостью задавать соответствие номеров цветов реальным цветам, а с возможностью оптимизировать процесс отображения битмапа, задавая рекомендуемую для него палитру. Это реально может иметь место при воспроизведении HiColor или TrueColor битмапов на устройствах, поддерживающих палитру — для повышения качества цветопередачи такому устройству целесообразно назначить палитру, оптимизированную для этого битмапа. Если этого не сделать, то все множество цветов битмапа будет приводится к той палитре, которая уже используется устройством — скорее всего это будет системная палитра.















