Заголовок независимого от устройства битмапа
Непосредственно после заголовка файла битмапа следует информация, описывающая характеристики битмапа — его размеры, количество цветов, используемую палитру, режим сжатия изображения и многое другое. Как уже было отмечено, информация о независимых от устройства битмапах условно делится на две структуры данных: 1) описание самого битмапа и 2) описание используемых битмапом цветов. В разных версиях Windows были предусмотрены разные форматы описания битмапов, по счастью совместимые снизу–вверх. Имеет смысл обзорно ознакомиться с возможностями битмапов разных версий и изменениями, произошедшими в их описании.
Формат OS/2
В ранних версиях GDI для описания битмапов применялись структуры, совместимые с ранним форматом OS/2. Для описания информации о битмапе применялась структура BITMAPCOREHEADER, а для описания используемых цветов — палитры — массив структур RGBTRIPLE (он необязателен):
Сначала рассмотрим структуру BITMAPCOREHEADER, описывающую битмап: Поле bcSize содержит размер этой структуры (sizeof (BITMAPCOREHEADER)), его значение должно быть равно 12. Поля bcWidth и bcHeight задают размеры данного битмапа. Так как для задания размеров используется целое число со знаком, то максимальный размер битмапа в этого формата равен 32767x32767 пикселей. Поле bcPlanes указывает количество цветовых планов (плоскостей), используемых битмапом. Его значение для независимого от устройства битмапа всегда должно быть равно 1. Поле bcBitCount указывает количество бит, используемых для задания цвета пикселя. Возможно одно из следующих значений:
1 — монохромный битмап 4 — 16ти цветный битмап 8 — 256ти цветный битмап 24 — битмап в истинных цветах (TrueColor). Все остальные значения для полей bcPlanes и bcBitCount являются недопустимыми. Если битмап имеет 2, 16 или 256 цветов, то непосредственно после структуры BITMAPCOREHEADER следует палитра (palette) — таблица определения цветов в виде массива из 2, 16 или 256 записей типа RGBTRIPLE. Считается, что изображение такого битмапа содержит логические номера цветов для каждого пикселя, а соответствие логического номера истинному цвету задается соответствующей записью в палитре. Каждая запись RGBTRIPLE задает интенсивности красной (r ed), зеленой (g reen) и синей (b lue) компонент цвета пикселя, в виде числа от 0 до 255. Таким образом возможно описание 16 777 216 возможных цветов из которых строится палитра, используемая битмапом. Последний вариант, когда битмап имеет 24 бита на пиксель, предполагает, что 24х битовый номер цвета пикселя соответствует истинному цвету, то есть записи из трех компонент основных цветов RGB (структура RGBTRIPLE сама имеет размер 24 бита). Понятно, что в этом случае палитра становится не нужна и в заголовок битмапа она не помещается вовсе. Часто для удобства вместо структур BITMAPCOREHEADER и массива записей RGBTRIPLE используют объединенную структуру BITMAPCOREINFO, которая просто описывает в качестве полей структуру BITMAPCOREHEADER и массив из одной записи RGBTRIPLE. typedef struct _BITMAPCOREINFO { BITMAPCOREHEADER bmciHeader; RGBTRIPLE bmciColors[1]; } BITMAPCOREINFO; Такая структура несколько упрощает доступ к описанию битмапа по указателю: при использовании BITMAPCOREHEADER и RGBTRIPLE необходимо манипулировать с двумя указателями, а при использовании BITMAPCOREINFO достаточно только одного — указывающего на начало заголовка. Например, вместо такого фрагмента кода: LPBITMAPCOREHEADER lpbmch =...; // считаем, что указатель на заголовок нам дан LPRGBTRIPLE lprgbt; lprgbt = (LPRGBTRIPLE) (lpbmch + 1); // получаем указатель на палитру // для доступа к полям заголовка используем, например lpbmch->bcWidth
// для доступа к палитре используем, например lprgbt[i].rgbtRed; Можно использовать чуть более простой фрагмент, в котором применяется только один указатель: LPBITMAPCOREINFO lpbmci =...; // считаем, что указатель на заголовок нам дан // для доступа к полям заголовка lpbmci->bmciHeader.bcWidth // для доступа к палитре lpbmci->bmciColors[i].rgbtRed; Однако использовать структуру BITMAPCOREINFO при загрузке битмапа не слишком удобно, так как ее полный размер может быть различным, смотря по количеству цветов битмапа (причем он может быть либо меньше, либо больше, чем sizeof (BITMAPCOREINFO) и заведомо не равен ему). Его можно вычислить как размер структуры BITMAPCOREHEADER (или значение поля bcSize) плюс размер таблицы определения цветов: нуль, если поле bcBitCount равно 24, или число цветов, умноженное на размер структуры RGBTRIPLE: UINT uSizeCoreInfo; LPBITMAPCOREHEADER lpbmch; uSizeCoreInfo = lpbmch->bcSize + ( lpbmch->bcBitCount==24? 0: (1 << lpbmch->bcBitCount) * sizeof (RGBTRIPLE)); Непосредственно вслед за структурой BITMAPCOREINFO следуют собственно данные изображения. Их можно найти в DIB–файле как по значению поля bfOffBits заголовка файла BITMAPFILEHEADER, так и считывая их непосредственно после таблицы определения цветов. Анализируя заголовок битмапа можно определить и необходимый размер области для хранения изображения. Изображение хранится по строкам развертки, в каждой строке для задания цвета пикселя отводится bcBitCount последовательных бит. Полная длина строки выравнивается в сторону завышения до ближайшей границы, кратной двойному слову (в зависимых от устройства битмапах строка выравнивалась до четного размера, а в случае DIB — кратного четырем). Строки развертки перечисляются снизу–вверх. Для вычисления размера изображения можно воспользоваться таким фрагментом: DWORD dwSizeImage; LPBITMAPCOREHEADER lpbmch; // считаем, что указатель на заголовок нам дан dwSizeImage = ((lpbmch->bcWidth * lpbmch->bcBitCount + 31) >> 3) & ~3L; dwSizeImage *= lpbmch->bcHeight; В этом фрагменте выполняются следующие действия: сначала вычисляется длина строки развертки в битах (lpbmch->bcWidth * lpbmch->bcBitCount), далее нам надо получить эту длину в двойных словах (то есть деленную на 32) и округленную в большую сторону; затем пересчитать из двойных слов в байты — умножить на 4. Этот процесс можно несколько ускорить — пересчет в число двойных слов с округлением в большую сторону легко проделать по формуле (x + 31)/32, или, используя более быстрые операции, (x+31)>>5, так как 32 это 25. Далее надо умножить на 4, то есть ((x+31)>>5)*4 = ((x+31)>>5)<<2), или, в окончательном варианте, ((x+31)>>3)& (~3): так как при умножении на 4 (сдвиге влево на 2 бита), младшие 2 разряда будут обнулены, то заменяя деление с умножением на сдвиг вправо, мы должны сбросить два младших бита в 0.
Формат Windows
Достаточно быстро Microsoft решил расширить возможности битмапов, в связи с чем появилась новые версии структур, описывающих битмап: для описания заголовка BITMAPINFOHEADER и для описания палитры RGBQUAD:
Первое поле структуры BITMAPINFOHEADER — biSize совпадает по назначению и размеру с полем bcSize структуры BITMAPCOREHEADER. Это поле содержит размер структуры, описывающей данный заголовок. Таким образом, анализируя это поле, можно легко определить, какая версия заголовка используется. Однако здесь имеется один подводный камень — в некоторых ранних источниках времен Windows 3.x утверждается, что все поля этой структуры, начиная с поля biCompression, могут быть пропущены. Собственно в документации, сопровождающей компиляторы есть только одно косвенное упоминание об этом: там строго предупреждается, что для определения размера заголовка битмапа надо обязательно использовать поле biSize, а не sizeof (BITMAPINFOHEADER). Таким образом размер структуры BITMAPINFOHEADER может изменяться от 16 до 40 байт; но в любом случае он превышает размер структуры BITMAPCOIREHEADER (12 байт), что позволяет различать заголовки в разных форматах. На практике мне только один раз встретился битмап с неполным заголовком. Следует заметить также, что в результате проверки оказалось, что все графические пакеты, с которыми я имел дело, отказываются воспринимать такой битмап и сообщают о неверном формате файла; аналогично реагируют на подобные битмапы и современные системы (проверено для Windows–95, Windows–98, Windows NT 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 и 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 << lpbmih->biBitCount)) ) * 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ти и 32х битовых битмапов появились стандартные маски цветов, которые будут использоваться по умолчанию, если в самом битмапе эти маски не определены; в этом случае поле biCompression задается равным BI_RGB, а не BI_BITFIELDS. Теперь режим BI_BITFIELDS не обязательно должен устанавливаться для 16ти и 32х битовых битмапов, он используется только в том случае, если заголовок битмапа содержит маски. Если маски присутствуют, то они перечисляются сразу за заголовком битмапа (BITMAPINFOHEADER) в приведенном в таблице порядке — красный, зеленый и синий цвета. Кроме того, в случае 16ти, 24х или 32х бит на пиксель и режима BI_RGB появилась возможность задавать палитру (поле biClrUsed должно быть ненулевым — палитра для максимально допустимого числа цветов в этих форматах чересчур громоздка). Смысл включения палитры теперь связан не с необходимостью задавать соответствие номеров цветов реальным цветам, а с возможностью оптимизировать процесс отображения битмапа, задавая рекомендуемую для него палитру. Это реально может иметь место при воспроизведении HiColor или TrueColor битмапов на устройствах, поддерживающих палитру — для повышения качества цветопередачи такому устройству целесообразно назначить палитру, оптимизированную для этого битмапа. Если этого не сделать, то все множество цветов битмапа будет приводится к той палитре, которая уже используется устройством — скорее всего это будет системная палитра. Еще позже в документации появились указания о том, что возможно одновременное задание масок цветов и палитры — после заголовка сначала размещаются маски цветов и только затем палитра, размер которой определяется полем biClrUsed заголовка битмапа. Помимо этого, также можно найти замечание о том, что маски могут быть заданы для всех режимов, в которых число бит/пиксель равно или превышает 16, включая TrueColor (24 бита/пиксель). На практике эти расширения большинством приложений не поддерживаются. Для проверки использовался стандартный MS Paint, который сам по возможности не выполняет анализа изображений и всю работу старается передать GDI. Это позволяет использовать его в качестве теста на возможности GDI. Для разных платформ Windows были получены следующие результаты:
Однако, при работе в 256ти цветном режиме осталось впечатление, что необязательная для 16ти, 24х и 32х бит/пиксель битмапов палитра просто игнорируется, даже если присутствует. Однако это особенность MS Paint, а не GDI. К сожалению, остальные проверенные приложения (например, Photo Shop 5.0) вообще отказались работать с HiColor форматами (16 и 32 бит/пиксель). Это значит, что для экспорта изображений приложение должно использовать по возможности старые, проверенные и широко распространенные форматы 1, 4, 8 бит/пиксель с полной палитрой (в случае OS/2 приходилось наблюдать ошибки при чтении битмапов с сокращенной палитрой); либо TrueColor в стандартном варианте — без палитры и без масок цветов. А вот при чтении битмапа целесообразно допускать все эти возможные варианты, что обеспечит совместимость с битмапами, создаваемыми другими приложениями, даже в ближайшем будущем. Таким образом для битмапов Win32 надо обращать внимание на: возможно отрицательную высоту битмапа; режим сжатия BI_BITFILEDS — если он задан, то после заголовка есть 3 двойных слова с масками цветовых компонент; если же задан режим BI_RGB, BI_RLE4 или BI_RLE8, то масок нет (предполагаются стандартные маски 5–5–5 или 8–8–8); для форматов 1, 4 и 8 бит на пиксель палитра обязательна, а для 16, 24 и 32 бит на пиксель палитра может отсутствовать (то есть нулевое значение biClrUsed интерпретируется либо как максимальный размер палитры, либо как ее отсутствие — смотря по числу бит на пиксель). Для HiColor или TrueColor режимов палитра является лишь рекомендуемой, облегчающей процесс отображения полноцветного битмапа на устройстве, поддерживающем палитры. Именно поэтому в примере на странице 57 при определении размера палитры значение поля biBitCount сравнивалось с 8, а не проверялось строгое равенство 24 битам на пиксель — максимальный размер палитры определен только для 2х, 16ти и 256ти цветных битмапов, а для форматов с 16тью, 24мя и 32мя битами на пиксель для задания палитры необходимо задать поле biClrUsed. По умолчанию в HiColor и TrueColor битмапах палитра отсутствует. Если задан и режим BI_BITFIELDS, и biClrUsed не равен 0, то палитра размещается непосредственно после масок.
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|