Основы графического вывода
Глобальные системы координат GDI. Отображение основных графических объектов. Основные и дополнительные средства для рисования линий. Растровые изображения и метафайлы. Обзор и создание зависимых и независимых от графического устройства битмапов.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | лекция |
Язык | русский |
Дата добавления | 24.06.2009 |
Размер файла | 498,8 K |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
LPSTR GetBitmapBits (hBitmap, dwMaxSize, lpBuffer);
Еще несколько функций могут использоваться совершенно специфичным способом:
DWORD GetBitmapDimension (hBmp);
BOOL GetBitmapDimensionEx (hBmp, lpSize);
DWORD SetBitmapDimension (hBmp, nWidth, nHeight);
BOOL SetBitmapDimensionEx (hBmp, nX, nY, lpSize);
Эти процедуры используются для задания/получения справочного размера битмапа, в единицах по 0.1 мм. Никакие иные функции GDI не используют эту информацию при работе с битмапами. Практически вы можете использовать эти размеры сами при необходимости передачи битмапов между устройствами с разной разрешающей способностью.
Получение зависимых от устройства битмапов как ресурсов приложения
Создание битмапов непосредственно в приложении -- случай сравнительно редкий. Обычно битмапы рисуются с помощью какого-либо графического редактора и затем загружаются из файла (в этом случае загружается не DDB, а DIB), либо битмап добавляется в ресурсы приложения и затем загружается из ресурсов с помощью функции LoadBitmap. В этом случае выполняется загрузка именно в виде зависимого от устройства битмапа, хотя в ресурсах приложения размещается DIB.
Интересный нюанс -- так как битмап из ресурсов приложения загружается как зависимый от устройства, то какому устройству будут соответствовать его характеристики? Microsoft считает, что такие битмапы будут отображаться преимущественно на дисплее, и, следовательно, DDB будет строиться под характеристики дисплея. Указать, для какого устройства битмап надо оптимизировать невозможно. В большинстве случаев этот подход вполне приемлем, однако при необходимости осуществлять вывод битмапа не только в окне, но и на иных устройствах (например на принтере или внедрять его в метафайл), лучше получить доступ непосредственно к самому DIB, записанному в ресурсах приложения (об этом -- в разделе «Загрузка независимых от устройства битмапов.»).
Подробнее о применении и описании ресурсов приложения -- см. раздел «Ресурсы приложения», здесь же будут представлены основные сведения об описании битмапов в качестве ресурсов приложения. Для описания битмапа в файле описания ресурсов принята следующая форма:
nameId |
уникальное имя или номер ресурса |
|
load-opt 0 |
режим загрузки ресурса: PRELOAD или LOADONCALL (по умолчанию) |
|
mem-opt 0 |
тип выделяемой памяти: FIXED, MOVEABLE (по умолчанию) или DISCARDABLE |
|
filename.bmp |
имя файла, содержащего битмап |
nameId BITMAP [load-opt] [mem-opt] filename.bmp (некоторые редакторы и компиляторы ресурсов, как, скажем, Borland WorkShop, позволяют описывать битмап непосредственно в файле описания ресурсов в текстовом виде. Тогда вместо filename.bmp используются структурные скобки BEGIN...END или {...} с заключенным между ними данными битмапа в виде списка шестнадцатеричных чисел. Этот способ не гарантирует переносимость ресурсов между разными средами разработки приложений.)
Часто режим загрузки ресурса и тип выделяемой памяти при описании битмапов не указывается -- предлагаемые по умолчанию значения (загрузка по требованию и перемещаемый блок памяти) как правило являются оптимальными. Примеры описания битмапов:
red_brick BITMAP rbrick.bmp
1 BITMAP firm.bmp
Считается, что лучше использовать уникальные номера ресурсов, а не имена, так как это обеспечивает более быстрый поиск ресурса в приложении и требует меньше памяти для описания ресурсов приложения.
Для загрузки битмапа из ресурсов приложения используются функции:
HBITMAP LoadBitmap (hInstance, lpszName);
HANDLE LoadImage (hInstance, lpszName, uType, cxDesired, cyDesired, fuLoad); 1
где hInstance -- хендл копии приложения, содержащего данный битмап, а lpszName -- имя ресурса битмапа. Имя ресурса может быть либо текстом -- тогда lpszName это обычная строка, оканчивающаяся символом `\0', либо номером -- тогда вместо lpszName может стоять или «#number», или MAKEINTRESOURCE (number). Например, для загрузки битмапов «red_brick» и «1» можно воспользоваться такими вызовами функций:
HBITMAP hbmpRedBrick = LoadBitmap (hInstance, red_brick);
HBITMAP hbmp1a = LoadBitmap (hInstance, #1);
HBITMAP hbmp1b = LoadBitmap (hInstance, MAKEINTRESOURCE (1));
Причем последний вариант является самым быстрым и компактным.
Функция LoadImage осуществляет загрузку битмапов, пиктограмм и курсоров. Теоретически она позволяет загружать требуемый ресурс из файла (для этого в fuLoad надо установить флаг LR_LOADFROMFILE и указать hInstance равным NULL). Однако такая операция поддерживается только в случае Windows-95, Windows NT 4.0 и более поздних. Предыдущие реализации Win32 API не поддерживают загрузку изображений из файлов.
Вы можете использовать стандартные битмапы, предоставляемые Windows. Их символические имена начинаются на OBM_... . Для того, что бы вы могли воспользоваться этими идентификаторами, необходимо перед директивой #include <windows.h> определить символ OEMRESOURCE, то есть:
#define OEMRESOURCE
#include <windows.h>
В таблице приведены изображения стандартных битмапов и их идентификаторы, в соответствии с их реализацией в Windows 3.x (Windows API) и Windows NT 3.x (Win32 API); в более поздних версиях (как, например, Windows-95, Windows NT 4.0) внешний вид стандартных битмапов несколько изменен. В таблице заполнены не все клетки просто из соображений построчной группировки схожих битмапов.
OBM_UPARROW |
OBM_UPARROWI |
OBM_UPARROWD |
OBM_OLD_UPARROW |
|||||
OBM_DNARROW |
OBM_DNARROWI |
OBM_DNARROWD |
OBM_OLD_DNARROW |
|||||
OBM_RGARROW |
OBM_RGARROWI |
OBM_RGARROWD |
OBM_OLD_RGARROW |
|||||
OBM_LFARROW |
OBM_LFARROWI |
OBM_LFARROWD |
OBM_OLD_LFARROW |
|||||
OBM_REDUCE |
OBM_REDUCED |
OBM_OLD_REDUCE |
||||||
OBM_ZOOM |
OBM_ZOOMD |
OBM_OLD_ZOOM |
||||||
OBM_RESTORE |
OBM_RESTORED |
OBM_OLD_RESTORE |
||||||
OBM_CLOSE |
OBM_OLD_CLOSE |
|||||||
OBM_MNARROW |
OBM_COMBO |
OBM_SIZE |
OBM_BTSIZE |
|||||
OBM_CHECK |
OBM_BTNCORNERS |
OBM_CHECKBOX |
Работа с зависимым от устройства битмапом
Небольшое замечание: так как битмап является объектом GDI, то вы обязаны удалить его, как только он станет ненужным. Это относится ко всем битмапам, как созданным с помощью функций CreateBitmap, CreateBitmapIndirect, CreateCompatibleBitmap, CreateDiscardableBitmap, так и к загруженным с помощью функции LoadBitmap. Освобождение неиспользуемых битмапов особенно важно, так как это едва-ли не самые большие объекты GDI, занимающие значительные ресурсы.
В GDI практически не содержится функций, использующих зависимые от устройства битмапы непосредственно. Исключение, разве что, функции для создания кисти по образцу, для задания графического образа пункта меню или для передачи изображения в независимый от устройства битмап (подробнее см. в соответствующих разделах):
HBRUSH hbrBrush = CreatePatternBrush (hBmp);
DeleteBitmap (hBmp); // 2
После того, как мы создали кисть, битмап можно удалять, так как его образ скопирован в кисть и больше не используется. Если битмап больше, чем 8x8 пикселей, то для создания кисти будет использован его верхний-левый уголок, размером 8x8.
Все остальные операции по работе с битмапами осуществляются посредством специально создаваемого контекста устройства, ассоциированного с этим битмапом. Для этого был разработан специальный вид контекстов устройства -- совместимый контекст (compatible device context, compatible DC, чаще называемый memory device context, memory DC). Такой разнобой в названиях контекста связан, с одной стороны, с названием функции, его создающей -- CreateCompatibleDC -- создающей контекст устройства, совместимого с другим, реально существующим устройством (см. раздел «Получение хендла контекста устройства»). А, с другой стороны, созданный таким образом контекст устройства не соответствует никакому физическому устройству, его область отображения -- некоторое растровое изображение, хранимое в памяти. Отсюда второе название -- memory DC.
Совместимость контекста не значит, что его цветовая организация совпадает с организацией реально существующего контекста, а только лишь то, что принципы выполнения операций над этим контекстом будут такими же, как для реального устройства -- например, при выводе на совместимый контекст может применяться графический акселератор, если для контекста реального устройства, указанного в качестве прототипа при создании совместимого, такой акселератор используется (конечно, это зависит еще и от возможностей самого акселератора).
Только что созданный совместимый контекст устройства имеет монохромную область отображения размером 1 пиксель. Так как нарисовать что-нибудь осмысленное в такой области нереально, вы должны принять меры к тому, что бы область отображения этого контекста была ассоциирована с хранимым в памяти растровым изображением -- зависимым от устройства битмапом. Это делается тривиально -- битмап выбирается в совместимый контекст устройства с помощью обычной функции SelectObject. После этого область отображения совместимого контекста будет совпадать с указанным битмапом -- как по размерам, так и по цветовой организации.
Внимание! GDI предполагает, что битмап может быть выбран только в совместимый контекст устройства. Если его выбрать в какой--либо контекст реально существующего устройства, то скорее всего такая попытка будет просто проигнорирована, хотя в зависимости от платформы и используемых драйверов устройств, реакция системы может быть и иной.
Общая схема при этом выглядит следующим способом:
HDC hCompatDC;
HBITMAP hBmp;
hCompatDC = CreateCompatibleDC (hDC);
// функция CreateCompatibleDC () создает совместимый
// контекст устройства, соответствующий одному монохромному пикселу
hBmp = LoadBitmap (hInstance, lpszName);
// для получения хендла битмапа мы могли воспользоваться любым
// способом - его загрузкой из ресурсов или созданием
SelectObject (hCompatDC, hBmp);
// теперь совместимый контекст устройства соответствует нашему битмапу.
// ... здесь мы можем выполнять любые операции по рисованию на нашем битмапе
// ... или передавать изображения между разными контекстами устройств.
DeleteDC (hCompatDC);
// после того, как мы выполнили все нужные операции над контекстом
// устройства, мы можем его удалить.
// ... При этом битмап как объект GDI остается и мы можем свободно
// ... применять его хендл. Например, для создания кисти, или для
// ... отображения пункта меню.
DeleteObject (hBmp);
// после того, как битмап стал нам не нужен, мы можем его уничтожить
Очень часто встречается частный случай этой схемы: при создании нового изображения битмап делается совместимым по цветовой организации с тем устройством, на котором он будет отображаться. В этом случае создание битмапа выглядит так:
HDC hCompatDC;
HBITMAP hBmp;
hCompatDC = CreateCompatibleDC (hDC);
hBmp = CreateCompatibleBitmap (hDC, 500, 300)
SelectObject (hCompatDC, hBmp);
PatBlt (hCompatDC, 0,0, 500,300, PATCOPY);
// ... здесь мы можем выполнять любые операции по рисованию на нашем битмапе
// ... или передавать изображения между разными контекстами устройств.
DeleteDC (hCompatDC);
// ... Работаем с битмапом как с объектом GDI
DeleteObject (hBmp);
В этом примере надо отметить два момента: Во-первых, при создании битмапа в качестве прототипа задается обязательно контекст реального устройства (с заданной цветовой организацией), а не совместимого (который соответствует одному монохромному пикселю). Битмап, совместимый с совместимым контекстом устройства будет монохромным! Во-вторых, созданный совместимый битмап содержит произвольные данные, поэтому перед его использованием изображение надо очистить. В этом примере функция PatBlt закрашивает битмап текущей кистью (операция PATCOPY), иногда для начальной закраски используют не текущую кисть (по умолчанию -- WHITE_BRUSH может быть не белой), а белый или черный цвета (операции WHITENESS, BLACKNESS). Это зависит от дальнейшего использования: фон битмапа должен совпадать с фоном окна или должен быть конкретного цвета.
Эта схема действительно удобна, если желательно, что бы цветовая организация битмапа соответствовала цветовой организации устройства, на котором он будет отображаться. Как правило это так и есть, кроме сравнительно редких случаев применения монохромных битмапов.
До тех пор, пока битмап остается выбран в совместимый контекст устройства, вы можете применять все функции GDI для рисования и коррекции изображения. Но одна из нужнейших задач -- отображение битмапа на нужном устройстве -- остается нерешенной. Специальных функций для отображения зависимых от устройства битмапов на контексте устройства в GDI нет, однако предусмотрен более универсальный и мощный механизм, обеспечивающий выполнение этой задачи -- механизм передачи растровых изображений между контекстами устройств.
Операции передачи образов
Рассматривая применение битмапов мы обратили внимание на специальный механизм, осуществляющих передачу растровых изображений между различными контекстами устройств. Этот механизм называется операции по обмену блоками бит (bit block transfer, BLT) или тернарными растровыми операциями (ternary raster operation).
Основная идея растровых операций (слово тернарные часто опускают, в отличие от слова бинарные -- см. раздел «Режим рисования», стр. 26) заключается в организации обмена данными между двумя контекстами устройств. Эти операции универсальны -- они работают с любыми контекстами устройств, поддерживающими обмен растровыми изображениями (например, устройства типа плоттера такими возможностями, естественно, не обладают). Таким образом вы можете осуществить передачу изображения и между битмапом, выбранным в совместимый контекст устройства и реальным устройством на котором хотите это изображение показать, между двумя битмапами или передать имеющееся изображение с реального устройства в битмап или на другое устройство.
GDI содержит 3 функции, осуществляющих такую передачу изображений -- PatBlt, BitBlt и StretchBlt (заметьте, что аббревиатура BLT произносится как БЛИТ):
BOOL PatBlt (
hDC, nX, nY, nWidth, nHeight, dwROP);
BOOL BitBlt (
hDestDC, nDestX, nDestY, nDestWidth, nDestHeight,
hSrcDC, nSrcX, nSrcY, dwROP);
BOOL StretchBlt (
hDestDC, nDestX, nDestY, nDestWidth, nDestHeight,
hSrcDC, nSrcX, nSrcY, nSrcWidth, nSrcHeight, dwROP);
Все три функции выполняют сходные операции -- они строят результирующее изображение на контексте-приемнике, используя в качестве исходных данных:
изображение, создаваемое на приемнике при закраске фона текущей кистью, выбранной в контекст-приемник (это называется образцом, pattern).
изображение, существующее на контексте-источнике (исходное изображение, source).
изображение, существующее в данный момент на контексте-приемнике (имеющееся изображение, destination).
В процессе выполнения растровой операции эти три исходных изображения (битовых последовательности) комбинируются и получается результирующее изображение. Так как в операции участвуют три исходных последовательности, то операция получила название тернарной (ternary).
Код выполняемой операции задается параметром dwROP -- индексом тернарной растровой операции.
В документации по SDK можно найти таблицу, перечисляющую индексы 256 возможных растровых операций, их имена и короткое пояснение к каждой операции. Причем имена присвоены только 15 наиболее употребляемым операциям. Таблица, представленная в документации имеет следующий вид:
Number |
Hex ROP |
Boolean function |
Common Name |
|
0 |
00000042 |
0 |
BLACKNESS |
|
... |
||||
0D |
000D0B25 |
PDSnaon |
||
... |
||||
Поле «Hex ROP» содержит индекс тернарной растровой операции, который вы должны использовать в качестве параметра dwROP. Поле «Boolean function» содержит пояснение к выполняемой операции, а поле «Common name» -- имя растровой операции, если оно назначено. Однако разобраться в том, какая конкретно операция выполняется в процессе переноса изображения не так-то просто.
Попробуем пояснить это на примере: операция с индексом 000D0B25 обозначает операцию PDSnaon. Это обозначение содержит в обратной польской записи логические операции над битами, выполняемые в процессе растровой операции. Сначала указаны большими буквами используемые компоненты:
P: образец, (кисть, pattern)
D: существующее изображение (destination)
S: исходное изображение (source),
в общем случае для обозначения компонент операции используются эти три больших буквы, но порядок их перечисления зависит от выполняемой операции. После перечисления компонентов следуют маленькие буквы, указывающие выполняемые операции:
n: инверсия, not; операция использует 1 аргумент
a: пересечение, and; операция использует 2 аргумента
o: объединение, or; операция использует 2 аргумента
x: исключающее ИЛИ, xor; операция использует 2 аргумента
Для того, что бы понять как выполняются эти операции представим, что у нас есть стек, и каждая буква в записи указывает операцию: большая буква выполняет запись в стек, маленькая -- операцию над нижними данными в стеке, причем они из стека извлекаются, а результат операции размещается в стеке. Посмотрим на примере:
Рисунок 17. Пример расшифровки обозначения растровой операции.
Таким образом, получаем последовательность операций для формирования результата. Однако использовать такую табличку со списком тернарных растровых операций может быть удобно только при получении справок. А вот если мы можем словами описать нужную последовательность действий, а для нее надо определить индекс растровой операции, то такая таблица совершенно неудобна (одного результата можно достичь, выполняя операции различным образом; даже если вы запишите требуемые действия в рассмотренной форме, нет никакой гарантии, что в таблице такая запись найдется).
Попробуем научиться как-то иначе получать индекс тернарной растровой операции.
Мы уже встречались с бинарными растровыми операциями (ROP2) когда рассматривали рисование линий. Сейчас мы воспользуемся примерно таким же подходом -- мы будем исходить из предположения монохромных контекстов устройств (для простоты) и попробуем составить табличку, аналогичную той, что применялась для бинарных растровых операций:
Образец, кисть (pattern) |
1 1 1 1 0 0 0 0 |
|
Исходное изображение (source) |
1 1 0 0 1 1 0 0 |
|
Существующее изображение (destination) |
1 0 1 0 1 0 1 0 |
Такая табличка позволяет описать все 256 тернарных операций, поэтому приводить ее целиком не имеет смысла. Однако нам будет удобно использовать подобную запись для определения индекса тернарной операции.
Попробуем, например, найти индекс растровой операции, в результате которой мы получим светлую точку, если:
а) контекст-источник имеет светлую точку
б) контекст-источник и контекст-приемник имеют темные точки
в) только в том случае, когда образец содержит темную точку
Имеется в виду операция ( (а) или (б)) и (в). Составим табличку:
Образец, кисть (pattern) |
1 1 1 1 0 0 0 0 |
|
Исходное изображение (source) |
1 1 0 0 1 1 0 0 |
|
Существующее изображение (destination) |
1 0 1 0 1 0 1 0 |
|
Желаемый результат |
0 0 0 0 1 1 0 1 |
Как и в случае бинарных растровых операций мы можем использовать этот результат как номер операции (и заодно как старшее слово индекса). Этот номер равен 0b00001101 = 0x0D. Это уже рассмотренная нами операция с индексом 0x000D0B25 (PDSnaon).
Разобравшись с растровыми операциями, самое время разобраться с функциями, выполняющими эти операции. Самая простая из трех рассмотренных -- функция PatBlt. Она не использует контекст-источник и выполняет операцию только над контекстом-приемником и образцом (фоном, полученным в результате закраски текущей кистью).
BOOL PatBlt (hDC, nX, nY, nWidth, nHeight, dwROP);
Эта функция может использоваться со всеми растровыми операциями, не применяющими контекст-источник. Из именованных растровых операций это:
BLACKNESS |
-- закрасить все черным |
|
DSTINVERT |
-- инвертировать изображение (сделать "негатив") |
|
PATCOPY |
-- закрасить кистью |
|
PATINVERT |
-- закрасить инвертированной кистью |
|
WHITENESS |
-- закрасить все белым |
Эта функция часто используется для начальной закраски областей (операции BLACKNESS, WHITENESS, PATCOPY) и для выделения фрагментов (DSTINVERT).
Следующая функция, которую мы рассмотрим:
BOOL BitBlt (
hDestDC, nDestX, nDestY, nDestWidth, nDestHeight,
hSrcDC, nSrcX, nSrcY, dwROP);
Она осуществляет передачу изображений между двумя контекстами устройств, при этом передается прямоугольный фрагмент, который на контексте-приемнике и на контексте-источнике имеет одинаковые размеры. При использовании этой функции надо быть достаточно осторожным -- для задания координат и размеров используется логическая система координат, и логический размер изображения в обеих системах может быть различным.
Отдельно надо рассмотреть случай, когда один из контекстов является цветным, а другой черно-белым -- при этом особым образом осуществляется преобразование цветов:
при переходе от монохромного к цветному цвет, закодированный 1, соответствует цвету фона (задаваемому функцией SetBkColor), а цвет 0 -- цвету текста (функция SetTextColor).
при переходе от цветного к монохромному считается, что если цвет точки совпадает с цветом фона, то эта точка кодируется цветом 1, иначе 0.
Самая мощная функция, выполняющая растровые операции:
BOOL StretchBlt (
hDestDC, nDestX, nDestY, nDestWidth, nDestHeight,
hSrcDC, nSrcX, nSrcY, nSrcWidth, nSrcHeight, dwROP);
позволяет не только передать изображение между разными контекстами, но и осуществить масштабирование изображения. При масштабировании возможно два случая:
изображение увеличивается, то некоторые строки (столбцы) будут дублироваться;
изображение уменьшается, то некоторые строки (столбцы) будут комбинироваться в одну строку (столбец).
Объединение строк (столбцов) при сжатии может осуществляться различными способами, которые выбираются с помощью функции
UINT SetStretchBltMode (hDC, nMode);
параметр nMode задает режим объединения строк:
BLACKONWHITE |
выполняется операция И (AND). В результате получается, что черный цвет имеет "приоритет" над белым -- сочетание черного с белым рассматривается как черный |
|
WHITEONBLACK |
выполняется операция ИЛИ (OR). При этом "приоритет" принадлежит белому над черным -- сочетание черного с белым дает белый |
|
COLORONCOLOR |
при этом происходит простое исключение строк (столбцов). |
|
HALFTONE 1 |
только в Win32 API; происходит усреднение цвета объединяемых точек. |
Независимые от устройства битмапы
У уже рассмотренных зависимых от устройства битмапов имеется один очень серьезный недостаток -- их организация отражает организацию видеопамяти того графического устройства, для которого они были спроектированы. При этом возникают большие сложности с переносом битмапов с одного устройства на другое, особенно при создании битмапов, которые будут сохраняться в виде файлов и позже переносится на другие компьютеры. Достаточно универсальным является только лишь монохромный битмап, который легко может быть отображен на любом цветном устройстве, однако такое ограничение является крайне неудобным для конечного пользователя.
Для цветных битмапов сложности возникают даже при их отображении на однотипных устройствах. Например, SVGA адаптеры часто используют логическую палитру, задающую набор воспроизводимых цветов. При отображении одного и того же цветного битмапа на устройствах, использующих разные палитры, результат будет различным.
Наличие этих сложностей привело к появлению новых видов битмапов, так называемых независимых от устройства битмапов (Device Independed Bitmap, DIB). Такой битмап отличается от обычного тем, что дополнительно содержит данные, определяющие соответствие цветов, используемых битмапом, реальным цветам. Благодаря этому независимый от устройства битмап может быть отображен практически на любом графическом устройстве, поддерживающем операции по обмену битовыми образами, с минимальными искажениями цвета.
На практике, начиная с версий Windows 3.x для хранения изображений (в виде .bmp файлов или ресурсов приложения) используются только независимые от устройства битмапы.
Формат независимого от устройства битмапа
Обычно приходится иметь дело с DIB, когда они представлены либо в виде файлов или ресурсов приложения. Поэтому знакомство с DIB мы начнем с формата файла, содержащего DIB.
Некоторые сложности связаны с наличием нескольких различных видов DIB-файлов. Первоначально (в самых ранних версиях Windows и OS/2 использовался битмап в его простейшем виде, называемом в документации форматом OS/2 По крайней мере современные версии OS/2 часто применяют битмапы в ином формате -- попытка их проанализировать, используя подобное описание приводит к ошибке, так что использованное название «битмап OS/2» выглядит по меньшей мере сильно устаревшим. Ключом в определении версии битмапа является размер его заголовка. Так, например, размер заголовка битмапа OS/2 (версия 1.2 -- формат, поддерживаемый также и Microsoft Windows) равен 12 байтам, размер заголовка Windows битмапа равен 40 байтам (не считая так называемых 4ой и 5ой версий битмапов); более современный формат битмапов OS/2 (версия 2.0) равен 64 байтам.. В дальнейшем, по мере развития GDI появился формат Windows, который для приложений Windows долгое время являлся фактически стандартом. Этот вид битмапов дожил до платформы Win32, когда к нему было добавлено несколько новых возможностей, правда без изменения заголовка. В дальнейшем развитие Windows битмапов пошло стремительно -- практически в каждой новой версии Windows добавляется что-то новое и в заголовках битмапов появляются новые поля. Так появились битмапы 4ой версии (для Windows-95 и Windows NT 4.0) и даже 5ой (для Windows NT 5.0). Скорее всего этот процесс так скоро не остановится.
Утешает в этом два соображения:
Первое: все старые форматы битмапов поддерживаются. Таким образом, если ваше приложение само создает битмап, то он будет корректно обрабатываться и в последующих версиях Windows.
Второе: при загрузке битмапа (а он может быть создан в системе, разработанной позже вашего приложения), можно так построить алгоритм, что анализировать заголовок не потребуется. В этом случае ваше приложение опять-таки может использовать новые форматы битмапов (по крайней мере до тех пор, пока вы не собираетесь самостоятельно анализировать изображение).
Рисунок 18. Структура независимого от устройства битмапа.
Собственно независимый от устройства битмап содержит несколько структур данных, описывающих его характеристики. Эти структуры следуют друг за другом непрерывно, без промежутков. Если говорить о структуре DIB в общем, не вдаваясь в подробности описания этих структур данных, то его формат сохраняется во всех существующих версиях Windows.
Заголовок битмапа содержит данные о его размере (размере всего битмапа в байтах) и расстояние от начала файла до хранимого в нем изображения. В таком виде битмап хранится либо в файле, либо в виде ресурсов приложения. Загрузка битмапа может выполняться двумя разными способами:
В простейшем случае все, кроме заголовка файла, помещается в одну область данных (в случае 16ти разрядных платформ надо учитывать, что размер может быть существенно больше 64К). Битмап, загруженный таким образом, называется упакованным (packed DIB).
В другом случае битмап располагается в двух областях -- в первой находится заголовок битмапа и данные о соответствии цветов (палитра, либо заменяющие ее данные), а во второй -- собственно изображение.
Первый способ удобен при считывании битмапа с диска или из ресурсов приложения, второй -- при создании нового битмапа в приложении, когда размер области данных для хранения изображения может быть заранее неизвестен (его можно узнать из заголовка битмапа). Многие функции GDI, работающие с независимыми от устройства битмапами, требуют задания двух указателей: на информацию о битмапе и на данные изображения. Однако некоторые функции ориентированы на использование упакованного битмапа, и тогда требуют задания хендла глобального блока памяти, содержащего упакованный DIB. С этой точки зрения первый способ (с использованием упакованного битмапа) универсален -- вы можете легко вычислить указатель на данные изображения внутри единого блока (например, исходя из данных в заголовке файла).
Загрузка независимых от устройства битмапов
Формат заголовка файла одинаков для всех версий битмапов; он описывается структурой BITMAPFILEHEADER:
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
Поле bfType, должно быть содержать две буквы "BM" (значение 0x4D42).
Поле bfSize указывает полный размер файла, включая этот заголовок. Обратите внимание на то, что размер задается двойным словом, так как может существенно превышать 64K. Например битмап 1280x1024, 24 бита/пиксель имеет размер более 3M. Вообще говоря, это поле может быть не заполнено; хотя и крайне редко, но может даже оказаться, что там указана некорректная величина, вместо правильного размера или 0. По крайней мере для битмапов OS/2 в поле bfSize может оказаться величина, равная размеру заголовка файла плюс заголовок битмапа (26). Во всех случаях лучше исходить не из этой величины, а из реального размера файла.
Поля bfReserved1 и bfReserved2 оба содержат 0. По крайней мере так считает Microsoft. В битмапах OS/2 часто эти поля содержат ненулевые данные.
Поле bfOffBits указывает адрес, с которого в данном файле размещаются собственно данные изображения. Этим полем удобно пользоваться для получения размера заголовка битмапа и данных о его цветах, а заодно для вычисления адреса начала данных изображения.
Так, благодаря наличию поля bfOffBits, можно сформулировать универсальный алгоритм загрузки битмапа в память, не зависящий от версии битмапа и его характеристик. В этом примере мы будем ориентироваться на работу с функциями Windows API, что позволяет сделать более компактный, переносимый код, помимо этого введем дополнительную структуру, описывающую DIB. Она будет удобна по двум причинам -- во-первых, после загрузки DIB удобно возвращать два указателя, которые могут понадобиться в дальнейшем, плюс хендл блока памяти, содержащего битмап; все это проще хранить в одной структуре. Во-вторых, эту же структуру мы сможем использовать еще раз, когда рассмотрим загрузку битмапов из ресурсов приложения. Подробнее обо всех указателях и их типах -- см. в разделе “Заголовок независимого от устройства битмапа”.
#define STRICT
#include <windows.h>
#include <windowsx.h>
// описываем структуру, содержащую информацию о битмапе
typedef struct _DIB {
HGLOBAL hglbDib; // хендл блока памяти или ресурса
LPBITMAPINFOHEADER lpDibHdr; // указатель на заголовок битмапа
LPSTR lpImage; // указатель на изображение
UINT uDibFlags; // флаг 1-загружен из файла, 2-из ресурса
} FAR* LP_DIB;
#define DIB_FILE 1
#define DIB_RESOURCE 2
#define DIB_SIGNATURE 0x4D42
#ifdef __NT__
#define _memcpy_ (to,from,sz) CopyMemory ((LPVOID) (to), (LPVOID) (from), (sz))
#else
#define _memcpy_ (to,from,sz) hmemcpy ((void huge*) (to), (void huge*) (from), (sz))
#endif
BOOL LoadDIBfromFile (LP_DIB lpDib, LPSTR lpszFileName)
{HFILE hFile;
DWORD dwSize;
BITMAPFILEHEADER bmfh;
// инициализируем возвращаемые данные:
lpDib->hglbDib = NULL;
lpDib->lpDibHdr = (LPBITMAPINFOHEADER)NULL;
lpDib->lpImage = (LPSTR)NULL;
lpDib->uDibFlags = 0;
// открываем файл с битмапом для чтения
hFile = _lopen (lpszFileName, READ);
if (hFile == HFILE_ERROR) return FALSE;
// определяем размер упакованного битмапа
dwSize = _llseek (hFile, 0L, 2); _llseek (hFile, 0L, 0);
if (dwSize >= sizeof (bmhf)) dwSize -= sizeof (bmhf);
// выделяем блок для хранения упакованного битмапа
lpDib->lpDibHdr = (LPBITMAPINFOHEADER)GlobalAllocPtr (GHND, dwSize);
if (lpDib->lpDibHdr != (LPBITMAPINFOHEADER)NULL) {
// считываем заголовок файла
if ( (_lread (hFile, &bmhf, sizeof (bmhf)) == sizeof (bmhf)) &&
(bmhf.bfType == DIB_SIGNATURE)) {
// если заголовок успешно считан, считываем сам битмап
if (_hread (hFile, lpDib->lpDibHdr, dwSize) == dwSize) {
// и устанавливаем нужные поля структуры _DIB:
lpDib->hglbDib = GlobalPtrHandle (lpDib->lpDibHdr);
lpDib->lpImage = (LPSTR) (
(char huge*) (lpDib->lpDibHdr) + bmhf.bfOffBits - sizeof (bmhf));
lpDib->uDibFlags = DIB_FILE;}}
// если где-то возникла ошибка - освобождаем память
if (lpDib->uDibFlags == 0) {
GlobalFreePtr (lpDib->lpDibHdr);
lpDib->lpDibHdr = (LPBITMAPINFOHEADER)NULL;}}
_lclose (hFile);
return lpDib->uDibFlags ? TRUE : FALSE;}
Следует обратить внимание на то, что в этой процедуре основная часть кода выполняет проверки или связана с несколько избыточным описанием структуры _DIB; в частных случаях вся процедура может свестись к выполнению 3х-4х функций.
По сути близкий к этому случай может быть связан с загрузкой независимых от устройства битмапов из ресурсов приложения. При рассмотрении зависимых от устройства битмапов было отмечено, что функция LoadBitmap, загружающая битмап из ресурсов приложения, возвращает зависимый от устройства битмап, предназначенный для воспроизведения на дисплее. Это может быть неудобно, если битмап должен отображаться, скажем, на принтере. По счастью в ресурсы приложения включается непосредственно независимый от устройства битмап, что позволяет получить к нему доступ с помощью функций FindResource и LoadResource. В результате вы получите указатель на блок памяти, содержащий целиком образ файла битмапа, включая структуру BITMAPFILEHEADER. Останется только вычислить адрес начала данных изображения и адрес информации о битмапе:
// включаемые заголовки и описание структуры _DIB - см. в предыдущем примере
BOOL LoadDIBfromResources (LP_DIB lpDib, HINSTANCE hInstance, LPSTR lpszResName)
{LPBITMAPFILEHEADER lpbmfh;
HRSRC hresDib;
// инициализируем возвращаемые данные:
lpDib->hglbDib = NULL;
lpDib->lpDibHdr = (LPBITMAPINFOHEADER)NULL;
lpDib->lpImage = (LPSTR)NULL;
lpDib->uDibFlags = 0;
// ищем нужный ресурс
hresDib = FindResource (hInstance, lpszResName, RT_BITMAP);
if (!hresDib) return FALSE;
// ресурс найден, получаем его хендл
lpDib->hglbDib = LoadResource (hInstance, hresDib);
if (! (lpDib->hglbDib)) return FALSE;
// получаем указатель на загруженный ресурс
lpbmfh = (LPBITMAPFILEHEADER)LockResource (lpDib->hglbDib);
if (lpbmfh != (LPBITMAPFILEHEADER)NULL) {
// заполняем остальные поля структуры _DIB:
lpDib->lpDibHdr = (LPBITMAPINFOHEADER) (lpbmfh + 1);
lpDib->lpImage = (char FAR*) (lpbmfh) + bmhf.bfOffBits;
lpDib->uDibFlags = DIB_RESOURCE;}
if (lpDib->uDibFlags == 0) {
#ifndef __NT__
FreeResource (lpDib->hglbDib);
#endif
lpDib->hglbDib = NULL;}
return lpDib->uDibFlags ? TRUE : FALSE;}
Заканчивая рассмотрение функций для загрузки независимых от устройства битмапов из файла или из ресурса приложения, приведем еще одну функцию, освобождающую выделенные ресурсы. Необходимость в этой функции возникает, как только вводится собственная структура _DIB, в которой содержатся хендлы и указатели на выделяемые ресурсы разного типа (блок памяти или ресурс приложения).
BOOL FreeDIB (LP_DIB lpDib)
{BOOL fResult = FALSE;
switch (lpDib->uDibFlags) {
case DIB_FILE:
if (lpDib->lpDibHdr) GlobalFreePtr (lpDib->lpDibHdr);
fResult = TRUE;
break;
case DIB_RESOURCE:
#ifndef __NT__
if (lpDib->hglbDib) {
UnlockResource (lpDib->hglbDib); // для NT не требуется
FreeResource (lpDib->hglbDib);
// для NT не требуется}
#endif
fResult = TRUE;
break;
default:
break;}
// инициализируем структуру _DIB:
lpDib->hglbDib = NULL;
lpDib->lpDibHdr = (LPBITMAPINFOHEADER)NULL;
lpDib->lpImage = (LPSTR)NULL;
lpDib->uDibFlags = 0;
return fResult;}
Заголовок независимого от устройства битмапа
Непосредственно после заголовка файла битмапа следует информация, описывающая характеристики битмапа -- его размеры, количество цветов, используемую палитру, режим сжатия изображения и многое другое. Как уже было отмечено, информация о независимых от устройства битмапах условно делится на две структуры данных: 1) описание самого битмапа и 2) описание используемых битмапом цветов.
В разных версиях Windows были предусмотрены разные форматы описания битмапов, по счастью совместимые снизу-вверх. Имеет смысл обзорно ознакомиться с возможностями битмапов разных версий и изменениями, произошедшими в их описании.
Формат OS/2
В ранних версиях GDI для описания битмапов применялись структуры, совместимые с ранним форматом OS/2. Для описания информации о битмапе применялась структура BITMAPCOREHEADER, а для описания используемых цветов -- палитры -- массив структур RGBTRIPLE (он необязателен):
typedef struct tagBITMAPCOREHEADER { DWORD bcSize; short bcWidth; short bcHeight; WORD bcPlanes; WORD bcBitCount; } BITMAPCOREHEADER; |
typedef struct tagRGBTRIPLE { BYTE rgbtBlue; BYTE rgbtGreen; BYTE rgbtRed; } 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 задает интенсивности красной (red), зеленой (green) и синей (blue) компонент цвета пикселя, в виде числа от 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:
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, 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 предполагает, что все цвета должны передаваться как можно точнее. Этим полем можно воспользоваться, если вы сами разрабатываете палитру битмапа -- тогда вы можете некоторые цвета (например, цвета, покрывающие большую часть изображения) объявить важными, перечислить их в палитре первыми и этим несколько сократить цветовые искажения при отображении битмапа на устройствах, использующих палитру.
Подобные документы
Разработка графического редактора для рисования двухмерной и трехмерной графики, используя язык программирования Java и интерфейсы прикладного программирования Java 2D и Java 3D. Создание графического редактора 3D Paint. Основные методы класса Graphics.
курсовая работа [197,5 K], добавлен 19.11.2009Сущность, задачи и особенности объектно-ориентированного программирования. Создание и редактирование графических файлов при помощи различных инструментов рисования. Основные требования к аппаратному и программному обеспечению. Руководство пользователя.
курсовая работа [270,9 K], добавлен 09.03.2009Структура организации графического интерфейса, объявление и создание слушателей событий с помощью анонимных классов. Представление данных для таблицы – класс AbstractTableModel. Визуализация ячеек таблицы. Два основных типа потоков ввода-вывода в Java.
лекция [685,3 K], добавлен 01.05.2014Разработка графического интерфейса для ввода начальных значений, отображения результатов и тестирования методов собственного класса на языке программирования С++. Подсветка цветом выбранных операндов в процессе их инициализации и вывода на дисплей.
курсовая работа [234,6 K], добавлен 27.12.2014Изучение особенностей растровых и векторных графических редакторов. Создание графического редактора: выбор языка программирования, разработка структуры программы и алгоритма работы. Описание интерфейса программы. Руководство программиста и пользователя.
курсовая работа [1,3 M], добавлен 28.07.2013Растровая и векторная графика. Растровые графические редакторы. Масштабирование растрового изображения. Средства хранения высокоточных графических объектов. Изменение масштаба без потери качества и практически без увеличения размеров исходного файла.
презентация [652,8 K], добавлен 11.03.2015Растровая графика, составление графических изображений из отдельных точек (пикселей). Растровые графические редакторы. Векторная графика - построение изображения из простых объектов. Достоинства, недостатки и применение растровой и векторной графики.
презентация [7,8 K], добавлен 06.01.2014Определение понятия "видеокарта". Рассмотрение режима работы устройства по преобразованию цифрового сигнала в аналоговый электрический. Изучение особенностей изображения графических и текстовых знаков. Описание графического ускорителя и акселератора.
презентация [1,2 M], добавлен 20.12.2015Методы вывода графических примитивов в программе Delphi. Основные методы, объявленные в классе TCanvas. Использование объектов Brush, Pen, Front. Примеры применения функции Round Rect. Отличия способов рисования прямоугольника Polyline и Polygon.
курсовая работа [834,1 K], добавлен 17.09.2014Создание с помощью графического редактора логотипа и баннера для образовательного сайта "Областные математические олимпиады". Типы логотипов, баннер как графический элемент страницы сайта. Обзор инструментов графического редактора Adobe Illustrator.
курсовая работа [2,9 M], добавлен 08.02.2014