Графические устройства

         

Анализ основных полей заголовка



Анализ основных полей заголовка









BMP является сокращением слова



BMP является сокращением слова Bitmap, в такой записи оно переводится как "точечное изображение", а запись bit map обычно переводят как "карта битов". В соответствии с описанием стандарта файл состоит из четырех частей:

Bitmap_file, Bitmap_info, RGB_QUAD И Bitmap. Смысл этих названий следующий — данные о файле, данные об изображении, палитра используемых цветов и образ рисунка. Условимся рассматривать две первые части как заголовок файла. В таком случае файл состоит из заголовка, палитры и образа рисунка.

Существует несколько разновидностей стандарта BMP, основными из них являются стандарты для Windows и для OS/2. Они различаются размером полей, их расположением в области Bitmap_info и способом записи кодов базовых цветов в палитре. Образ рисунка хранится в одинаковой форме.




Чтение заголовка файла и палитр



Чтение заголовка файла и палитр











Действия предшествующие построению



Действия, предшествующие построению рисунка, описаны ниже по шагам. На каждом шаге приводится обоснование действий и их программная реа-лизация. Это сделано для того, чтобы описание и программирование кон-кретных действий были расположены близко друг к другу.









Для окончания обработки заголовка



Для окончания обработки заголовка надо извлечь из него величины, необходимые для установки или преобразования палитры цветов.

Палитра применяется в тех случаях, когда цвет не указан в коде точки (biBitcnt = 1, 4 или 8), она содержит описание используемых в рисунке цветов и состоит из строк, количество которых изменяется от 2 до 256. Строка содержит полный код цвета и занимает 3 (формат OS/2) или 4 (формат Windows) байта. Базовые цвета расположены в строке в следующем порядке: синий, зеленый, красный, в формате Windows к ним добавлен пустой байт, т. е. строки хранятся в форматах bgr или bgr0.



Для построения на экране рисунка



Для построения на экране рисунка, хранящегося в вмр-файле, надо выпол-нить следующие действия:

ввести спецификацию и открыть файл для чтения; прочитать заголовок и выбрать из него данные о рисунке и файле; установить или преобразовать палитру используемых в рисунке цветов; записать образ рисунка в видеопамять.

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




Для построения рисунка надо знать



Для построения рисунка надо знать его ширину, высоту и размер строки в файле. Следует подчеркнуть, что ширина рисунка и размер строки в файле являются разными величинами и их значения чаще всего не совпадают.

Будем считать, что в разделе данных задачи описаны переменные:

iwidth dw 0 ; количество точек в строке рисунка
iheight dw 0 ; количество строк в рисунке
fwidth dw 0 ; количество байтов в строке в файле
rmndr dw 0 ; количество дополнительных байтов в строке файла
bitcnt db 0 ; количество разрядов в коде точки

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



Для упрощения программирования



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









Дополнепие к примеру А З



Дополнепие к примеру А.З.

Для выполнения описанных действий, в тексте примера А.З команда, выполняющая проверку размера кода точки mр ci, I8h), заменяется группой команд, приведенных в примере А.З.



Если задача выполнила действия



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

bperline - размер отображаемой на экране строки в байтах
bytppnt - размер кода точки, выраженный в байтах
wrdppnt - размер кода точки, выраженный в словах

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

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




Характеристики палитры



Характеристики палитры

Для работы с палитрой надо вычислить значения трех величин, которые мы обозначим как paddr — адрес начала палитры в буфере обмена, pniine — количество строк (цветов) в палитре и pbpi — количество байтов в строке палитры. Они определяются так:

Шаг 8. Вычисляем адрес начала палитры (paddr) как сумму значения поля isize и смещения этого поля (оно равно о Eh). Размер палитры в байтах вычисляется как разность содержимого поля bfOffBits (его смещение одь) и paddr. В зависимости от формата заголовка (Windows или OS/2) присваиваем переменной pbpi значение 4 или 3. Для определения формата заголовка проверяем значение paddr, если оно равно 36h, то это формат Windows, в противном случае — OS/2. Вычисляем pniine = paddr/pbpl.
Вычисление характеристик палитры показано в примере А.6.



Исходные предпосылки



Исходные предпосылки

Сразу отметим, что такой способ построения не является универсальным. Его можно использовать для воспроизведения рисунков небольшого размера, образы которых помещаются в одном сегменте оперативной памяти (не превышают 65 536 байтов).

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

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



Исторически стандарт BMP предназначался



Исторически стандарт BMP предназначался для Windows, а в ней при построении изображений "по умолчанию" начало координат расположено в нижнем левом углу экрана. Значения по оси х возрастают слева направо, а по оси Y — снизу вверх. На первый взгляд ничего особенного в этом нет, именно так расположены и направлены оси координат при черчении или рисовании различных графиков на бумаге. Однако это только на первый взгляд.

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

Разработчики BMP так и поступили — образ рисунка хранится в файле в перевернутом виде, сначала записана его последняя строка, за ней предпоследняя и так вплоть до первой строки, которая записана последней. В таком случае, для построения рисунка снизу вверх строки из файла считываются последовательно друг за другом.
Следует отметить, что в перевернутом виде изображение хранится во всех графических форматах, предназначенных для использования Windows и ее приложениями. В частности, в разделе данной книги уже говорилось, что так хранятся рисунки курсоров (файлы типа cur) и пиктограмм (файлы типа ico).

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

Упаковка кодов точек

Если в образе рисунка использовано 2 или 16 цветов, то для сокращения размера файла он хранится в упакованном виде.

У 16-цветных рисунков значения кодов точек изменяются от о до огь, поэтому в одном байте можно записать коды двух подряд расположенных точек. Код левой точки записывается в старшую тетраду, а код правой -в младшую тетраду байта.
У 2-цветных рисунков значения кодов точек изменяются от о до 1 и в одном байте можно записать коды восьми подряд расположенных точек. Код левой точки группы записывается в старший (седьмой), а код правой точки группы — в младший (нулевой) разряд байта.

Такой способ упаковки точек рисунков, содержащих небольшое количество цветов, применяется не только в формате BMP, но также в PCX, GIF и других форматах. В разделе были приведены примеры подпрограмм 3.17 и 3.18, выполняющих распаковку в процессе построения строки рисунка.



Манипуляции с палитрой



Манипуляции с палитрой









Образ рисунка в файле



Образ рисунка в файле









Общая характеристика стандарта



Общая характеристика стандарта









Общая схема обработки заголовка файла



Общая схема обработки заголовка файла









Описание двух первых шагов



Описание двух первых шагов

После того как файл открыт, в буфер обмена считываются его заголовок и палитра используемых цветов. В разделе мы условились, что сегмент, содержащий буфер обмена, хранится в переменной swpseg, а смешение в нем — в переменной swpoffs. Чтение порции данных в буфер обмена выполняет подпрограмма Readf, описанная в примере 3.23. Она располагает считанные данные в сегменте swpseg, начиная с адреса (смешения), указанного з Swpoffs. Чтение заголовка и палитры производится в два приема.

Шаг 1. Считываем из файла первые 16 байтов и располагаем их в начале буфера обмена, начиная с адреса о. Проверяем содержимое двух первых байтов буфера. В них должны находиться коды заглавных латинских букв вм 42h и 4оь). Если это не так, то файл не соответствует стандарту BMP и его обработка не имеет смысла.

Если в начале файла находятся буквы вм, то слово 10 (DAh) указывает сме-щение образа рисунка от начала файла (bioffBits). Напомним, что образ рисунка расположен после палитры, поэтому значение, указанное в сло-вe 10, можно использовать для вычисления размера второй порции данных.

Шаг 2. Читаем следующие 16 байтов (bioffBits) и располагаем их в буфере обмена начиная с 16-го байта (счет начинается с нуля), т. е. сразу после данных, прочитанных на первом шаге. После чтения в буфере находятся заголовок файла и палитра. В зависимости от принадлежности файла стандарту Windows или OS/2, размер и структура заголовка соответствуют табл. А.1 или А.2.

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



Описание предварительных действий



Описание предварительных действий

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

Адрес начала последней строки рисунка в видеопамяти вычисляется следующим способом. Расстояние между первой и последней строкой равно (iheight - i)*bperiine байтов. В общем случае произведение занимает два слова. Содержимое младшего слова прибавляется к адресу начала первой строки. Содержимое старшего слова преобразуется в номер окна, который прибавляется к номеру окна, в котором находится первая строка. Вычисленное значение окна надо установить.

Количество строк в порции считываемых данных (part) вычисляется как частное от деления числа 65 535 на размер строки в файле (f width). Умножив part на fwidth, получим размер порции для чтения в байтах.

Замечание
Вместо умножения можно вычесть остаток от деления из числа 65 535.

Константа переадресации строк, видеопамяти. После записи кодов точек текущей строки в видеопамять определяется адрес начала предыдущей. Для этого текущий адрес видеопамяти уменьшается на величину, вычисляемую по формуле (iwidth + horsize) *bytppnt. Если при вычитании вырабатывается признак переноса, то устанавливается предыдущее окно видеопамяти, в противном случае текущее окно не изменяется.



Описание следующего шага



Описание следующего шага

Расположение полей, содержащих значения для переменных iwidth, iheight и bitcnt, зависит от того, какому формату соответствует заголовок — Windows или OS/2. Напомним, что эти форматы различаются размером информационной части заголовка, указанным в поле bisize или bcsize (его смещение ОЕb).

Шаг 3. Выбираем содержимое полей заголовка biwidth, biHeight и biBitCnt с учетом значения, указанного в поле bisize, и сохраняем выбранные величины в переменных iwidth, iheight и bitcnt. В заключение проверяем значение bitcnt, и если оно равно ish (24), то обработка заголовка закончена, и выполнение подпрограммы Bitmap завершается. В соответствии со стандартом BMP поле biBitCnt может содержать значения 1, 4, 8 или ish. Последнее значение соответствует полноцветным рисункам, не использующим палитру цветов, поэтому никакие манипуляции с палитрой не требуются, что и позволяет просто завершить подпрограмму.

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



Открытие существующего файла



Открытие существующего файла

DOS исполняет специальную функцию, предназначенную для открытия существующего файла (open File). Она име-ет код 3D0h и вызывается через прерывание int 2ih. Перед вызовом функции в регистрах ds:dx указывается адрес начала строки, содержащей спецификацию файла. В регистр ai помещается код режима открытия: 0 — для чтения, \ — для записи, 2 — для чтения и записи (для редактирования), а в регистре ah указывается код функции 3Dh.

Если файл существует, то он открывается. При возврате из DOS признак переполнения (разряд carry регистра флагов) очищен, а в регистре ах нахо-дится идентификатор файла (file handle), который надо сохранить в специ-циально выделенной переменной (мы обозначали ее handle). Идентификатор нужен DOS для работы с конкретным файлом. По существу, это порядковый номер файла, открытого задачей.

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

Запомним, что спецификация должна содержать данные, необходимые для поиска файла на диске, например, C:\Windows\Clouds.bmp. При обработке спецификации DOS преобразует строчные буквы в заглавные, поэтому можно использовать буквы любого размера. Если путь для поиска не указан, то DOS ищет файл в текущем каталоге. Признаком конца спецификации является пустой байт.



Первые точечные рисунки хранились



Первые точечные рисунки хранились в аппаратно-зависимом формате DDB (Device Dependent Bitmap). Такое представление было крайне неудобным, и разработчики Windows 3.0 отказались от него. Для нас оно не представляет интереса, поскольку не предназначено для современных видеосистем.

Семейства Windows 3-Х, 9Х и 2000 используют аппаратно-независимый формат DIB (Device independent Bitmap), заимствованный из OS/2 с некоторыми изменениями. Структура заголовка показана в табл. А.1.



Подпрограмма BigBmp



Подпрограмма BigBmp

Текст подпрограммы, выполняющей построение рисунка описанным способом, приведен в примере А.10. Перед ее вызовом адрес левого верхнего угла рисунка помещается в регистр di и устанавливается окно видеопамяти, которому принадлежит этот адрес. Регистр ез должен содержать код видеосегмента. Если в тексте вместо call drawiine записана команда call bx, то в регистре bх указывается адрес подпрограммы построения строки (см. раздел).



Подпрограмма открытия файла



Подпрограмма открытия файла

Способ ввода спецификации зависит от установленного видеорежима и от формы диалога, поддерживаемого задачей. Предположим, что задача установила один из видеорежимов VESA, a для диалога с оператором используются информационные строки. То есть нa экран выводится подсказка оператору, и задача переходит в режим ввода данных с клавиатуры. Подпрограммы, необходимые для вывода информаци-онных строк и ввода данных с клавиатуры рассмотрены во второй части главы 5 данной книги. Поэтому мы возьмем за основу описанный там при-мep 5.29 и дополним его действиями, необходимыми для открытия файла.

Текст подпрограммы приведен в примере А.1. Перед ее вызовом в регистрах ds:si надо указать адрес подсказки оператору, содержащей текст типа Введите спецификацию файла >", он должен заканчиваться пустым байтом. После возврата из подпрограммы проверяется состояние С-разряда и со-держимое регистра ах. Если С-разряд очищен, а содержимое ах отлично от 0, то оно является идентификатором файла.



Подпрограмма Smlbmp



Подпрограмма Smlbmp

Текст подпрограммы, выполняющей построение небольшого рисунка формата BMP описанным способом, приведен в примере А.9. Перед ее вызовом адрес левого верхнего угла рисунка в видеопамяти помещается в регистр di и устанавливается окно, которому принадлежит этот адрес. Регистр es должен содержать код видеосегмента. В регистре si указывается размер образа рисунка в байтах.



Подпрограммы обработки строк



Подпрограммы обработки строк

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

В примере А.12 приведена подпрограмма, выполняющая построение строки рисунка, у которого код точки занимает 1 байт. Ее отличие от описанных ранее вариантов только в том, что для записи кода точки вызывается вспомогательная подпрограмма outpnt.



Построение рисунка снизу вверх



Построение рисунка снизу вверх









Построение рисунка сверху вниз



Построение рисунка сверху вниз









Построение рисунков использующих палитру



Построение рисунков, использующих палитру









Построение таблицы цветов



Построение таблицы цветов

Если задача работает в одном из режимов direct color, то регистры цвета видеокарты не используются. В таких случаях исходная палитра преобразуется в таблицу, содержащую коды базовых цветов точек в формате, соответствующем установленному видеорежиму. Другими словами, надо преобразовать строки исходной палитры, упаковав базовые цвета в 16-разрядное слово для режимов Hi-Color или в 32-разрядное слово для режимов True color.



Повидимому стандарты пишут для



По-видимому, стандарты пишут для того, чтобы их не соблюдали. Анализ большого количества файлов формата BMP приводит именно к такому печальному выводу. Потенциальные возможности для отклонений заложены в самом стандарте для Windows, который явно избыточен — многие величины можно просто вычислить исходя из значений других величин, а не выбирать их из полей заголовка. Именно так и поступают в большинстве случаев.

Наиболее пунктуально заполняют заголовок программы оцифровки изображений, например, выполняющие сканирование рисунков. Однако после оцифровки "оригинальные" файлы проходят достаточно сложный путь, связанный с различными преобразованиями. Очень часто рисунки редактируются с помощью графических редакторов, которые могут изменить заголовок файла по "своему усмотрению". Например редактор Paintbrush для Windows просто очищает в заголовке байты с 30-го по 50-й, что при этом теряется — можно увидеть в табл. АЛ. Если такой файл импортировать в CorelDraw 4.0, то рисунок окажется искаженным, но сам Paintbrush воспроизводит его нормально. Вероятно, разработчики CorelDraw не учли возможность несоответствия заголовка файла стандарту BMP.

"Оригинал" мог быть подготовлен в одном графическом стандарте, а затем преобразован в другой, например, в стандарт BMP какой-либо программой. Такое преобразование может оказаться причиной более тонких ошибок, связанных с различием способов хранения самого рисунка. Например, по требованию стандарта PCX количество точек в строке должно быть кратно 16. Такое требование не противоречит стандарту BMP явно, но и не оговорено в нем. Если при преобразовании в BMP сохранить указанную особенность формата PCX, что обычно и делается, то рисунок будет искажен.

Стандарт для OS/2 оставляет меньше возможностей для отклонений, но одна все таки есть, — в двойном слове bfsize должен храниться размер файла. Содержимое этого слова лучше не использовать в программе. Слишком часто встречаются файлы с "потерянной" старшей частью двойного слова.

Как учесть в программе максимум возможных отклонений от стандарта? Для этого надо использовать только те величины, которые указаны в табл. А.2 (кроме bfsize) и без которых воспроизвести рисунок просто невозможно, а все остальные вычислять. Способы вычислений описаны ниже.




При работе с BMPфайлами произвольного



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

Такой способ уже использовался для построения полноцветных рисунков формата BMP, он описан в разделе, а текст соответствующей подпрограммы показан в примере 7.25. Отличие рассматриваемого здесь случая только в том, образ рисунка использует палитру, а коды его точек могут занимать 1, 4 или 8 разрядов.



А 1 Подпрограмма ввода спецификации и открытия файла



Пример А.1. Подпрограмма ввода спецификации и открытия файла

GetSpec : push Cur win сохранение исходного значения Cur win
mov ax, Inflinw ax = номер окна информационной строки
mov Cur win, ax Cur win = ax
call Savinfo сохранение исходного фона
jmp short outstr переход на выборку первого символа
BUtl: call outsgn вывод на экран очередного символа
outstr: lodsb al = код очередного символа (al = ds : si)
or al, al конец выводимого текста ?
jne outl -> нет, переход на метку outl
call Inline ввод строки теста с клавиатуры
call Delinfo удаление информационной строки с экра на
pop Cur win восстановление исходного значения Cur win
call setwin восстановление исходного окна
mov al, Linbuf al = первый байт строки
or al, al спецификация введена?
jnz OpenFr -> нет, пустая строка
xor ah, ah очистка регистра ah
ret возврат из подпрограммы
OpenFr: lea dx, Linbuf dx = адрес начала спецификации файла
mov ax, SDOOh al = 0, ah = 3D — код функции
int 21h обращение к DOS для открытия файла
ret возврат из подпрограммы

Первые 13 команд подпрограммы повторяют текст примера 5.29. В этой части на экран выводится подсказка оператору, а в буфер Linbuf записываются вводимые с клавиатуры символы. После нажатия оператором на клавишу <Enter> в Linbuf записывается пустой байт, ввод прекращается, подсказка и ответ оператора удаляются с экрана, а на их месте восстанавливается исходное изображение.

Дополнительные действия начинаются с анализа содержимого первого байта буфера Linbuf. Если он пуст, то просто очищается регистр ах и происходит возврат из подпрограммы. Это предусмотрено на тот случай, если оператор раздумает вводить спецификацию и просто нажмет клавишу <Enter>.

Если первый байт Linbuf не пустой, то предполагается, что оператор ввел спецификацию. В таком случае в регистр dx загружается адрес Linbuf, в регистр ах записываются код функции 3Dh и признак открытия файла для чтения (оо). После этого происходит обращение к DOS через прерывание int 2ih и возврат из подпрограммы.
Если файл успешно открыт, то после возврата из подпрограммы С-разряд очищен, а содержимое ах отлично от нуля, его надо сохранить в переменной handle и можно читать заголовок файла.



А 2 Начало обработки заголовка BMPфайла



Пример А.2. Начало обработки заголовка BMP-файла

JitMap: mov ex, 16 ; размер порции для чтения
mov SwpOffs, 0 ; адрес начала считываемых данных
call Readf ; чтение первых 16-ти байтов файла
jnc FilType ; -> чтение без ошибрк ret ; возврат при ошибке чтения
i /рилижение А. Рисунки в сраилал и
FilType xor si, si очистка регистра si
mov fs, SwpSeg fs = сегмент буфера обмена
mov ax, fs: [si] ax = метка файла ("ВМ")
cmp ax, 4D42h файл типа BMP?
je bmpfil -> да
stc нет, установка С-разряда
ret возврат, файл не BMP
bmpfil: mov ex, fs: [si+OAh] сх = смещение области данных
sub ex , 16 сх = сх — 16, порция для чтения
mov SwpOffs, 16 адрес начала считываемых данных
call Readf чтение остатка заголовка и палитры
jnc part_2 -> чтение без ошибок
ret возврат при ошибке чтения

В примере А.2 выполняются достаточно простые действия, поэтому мы не будем останавливаться на их подробном описании.

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



А 4 Вычисление значений переменных fwidth и rmndr



Пример А.4. Вычисление значений переменных fwidth и rmndr

Part_3: xor cl, cl ; cl = 0, значение константы сдвига
add ax, 03 ; ax = ax + 3 (ax содержит iwidth)
and al, OFCh очищаем 2 младших разряда ах
cmp bitcnt, 08 bitcnt = 8 ?
je @F -> да, переход на локальную метку @@
add ах, 04 ах = ах + 4
and al, OF8h очищаем 3 младших разряда ах
inc cl cl = 1, значение константы сдвига
cmp bitcnt, 04 bitcnt = 4 ?
je @F -> да, переход на локальную метку @@
add ax, 18h ах = ах + 24
and al, OEOh очищаем 5 младших разрядов ах
mov cl, 03 cl = 3, значение константы сдвига
?: mov bx, ax bx = ах (округленное значение iwidth)
sub bx, iwidth bx = bx — \width
shr bx, cl сдвиг bx вправо на содержимое cl
mov rmndr, bx количество дополнительных байтов
shr ax, cl сдвиг ах вправо на содержимое cl
mov fwidth, ax сохраняем значение fwidth

Рисунок не помещается па экране. Вполне вероятно, что размеры (или один из размеров) рисунка превышают размеры рабочей области экрана, соответ-ствующие установленному видеорежиму. В таких случаях возможны, по крайней мере, три варианта действий, не считая отказа от построения рисунка:

выводится только часть рисунка, помещающаяся на экране; увеличивается логический размер строки (раздел, функция 4F06h); устанавливается видеорежим с большим геометрическим разрешением.

двух первых случаях на экране будет видна только часть, а в третьем случае — все изображение, если удастся подобрать подходящий видеорежим. Наиболее универсален второй способ, но для получения всех его преиму-щecTB в задачу надо включить механизм перемещения области видеопамяти, отображаемой на экране. В приложениях для Windows таким механизмом являются горизонтальный и вертикальный "лифты".
Изменять установленный видеорежим или его характеристики в описывае-мой здесь подпрограмме не целесообразно, это надо делать на более высоком уровне. Если же вас устраивает построение части рисунка, размер кото-рой зависит от установленного видеорежима, то в данной подпрограмме можно Принудительно изменитьЬ значения iwidth и iheight.



А 5 Ограничение значений переменных iwidth и iheight



Пример А.5. Ограничение значений переменных iwidth и iheight

mov bx, horsize bx = размер экрана по горизонтали
cmp bx, ax iwidth > horsize ?
jae @F -> нет
mov iwidth, bx iwidth = horsize (уменьшаем iwidth)
@@: mov bx, versize bx = размер экрана по вертикали
cmp bx, iheight iheight > versize ?
jae @F -> нет
mov iheight, bx iheight = versize (уменьшаем iheight)
@@: cmp cl, 18h cl = 18h ?

При построении рисунка с таким ограничением значений iwidth и iheight на экране будет видна его левая нижняя часть размером horsize*versize. Мы не включили эти команды в текст примера А.З потому, что ограничение размера рисунков не относится к основным действиям, выполняемым при обработке заголовка файла.



А 6 Определение характеристик палитры



Пример А.6. Определение характеристик палитры

mov di, fs: [si+OEh] di = размер Bitmap info
add di, OEh di = di + OEh, di содержит paddr
mov ax, fs: [si+OAh] ax = адрес начала образа рисунка
sub ax, di ах = размер палитры в байтах
mov bx, 04 полагаем pbpl = 4
cmp di, 36h заголовок формата Windows ?
jz @F -> да
dec Ы pbpl = 3
@ : xor dx, dx перед делением очищаем dx
div bx ах = количество строк в палитре
mov paddr, di ! ! сохраняем paddr (не обязательно)
mov pnline, ax !! сохраняем pnline (не обязательно)
mov pbpl, bx ! ! сохраняем pbpl (не обязательно)

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

После выполнения примера А.6 из заголовка получена информация, необходимая для построения рисунка и работы с палитрой. Если вы не планируете включить установку или преобразование палитры в описываемую десь подпрограмму, то в конец текста примера А.б добавьте команду ret.

Дальнейшие манипуляции с палитрой зависят от установленного задачей видеорежима. Если это один их режимов PPG, то ее надо установить, а если это один из режимов direct color, то преобразовать в таблицу цветов, кото-рая нужна для построения рисунка.

Установка палитры заключается в записи перечисленных в ней цветов в регистры цвета видеокарты (DAC-регистры). Эти регистры доступны только При работе в видеорежимах PPG. При простой установке в регистры записываются коды всех цветов без их предварительного анализа. Недостаток та-кого способа в том, что в результате установки палитры добавляемого рисунка небольшого размера могут измениться цвета точек основного изобра-жения, находящегося на экране. Для более рационального использования регистров видеокарты перед установкой палитры могут выполняться анализ и преобразование добавляемых цветов. Эти вопросы подробно обсуждались в разделе, разделе и разделе основной части книги. Здесь мы ограничимся простым примером.

В разделе описан способ простой установки палитры, хранящейся в файлах формата PCX. Рассмотрим аналогичные действия для вмр-файлов.

Напомним, что для записи кодов базовых цветов в регистры цвета видеокарты используется одна из функций прерывания int 10h. Существуют две ее разновидности — для записи кода в один регистр и для записи кодов в группу регистров. Нас будет интересовать последняя, поскольку она позволяет установить всю палитру за одно обращение к BIOS.

Подробное описание функций BIOS приведено в разделе. Нужная нам функция имеет код 12h. Перед ее вызовом надо подготовить палитру так, чтобы в каждой ее строке было записано по 3 байта, содержащих базовые цвета, расположенные в порядке rgb, причем коды цветов должны быть сокращены до шести разрядов (два старших разряда кода BIOS игнорирует).

Программная реализация преобразования и простой установки палитры показана в примере А.7, который является продолжением примера А.6.



А 7 Преобразование и установка палитры



Пример А.7. Преобразование и установка палитры

convert : mov ex, ax ex = количество строк в палитре
mov si, di si = di адрес начала палитры
push es сохраняем содержимое es
mov es, SwpSeg es = сегмент буфера обмена
push di сохраняем адрес начала палитры
push ex сохраняем количество строк
s Ip : mov al, fs: [si] al = код синего цвета
xchg al, fs: [si+2] перестановка кодов красного и синего
shr al, 02 сокращаем код красного до 6 разрядов
stosb es:di = al, di = di + 1
mov ax, fs: [si+1] ax = коды зеленого и синего цвета
shr ax, 02 сокращаем коды до 6 разрядов
and al, 3Fh очищаем в al 2 старших разряда
stosw es:di = ax, di = di + 2
add si, bx si = si + pbpl (pbpl = 3 или 4)
loop S_lp управление повторами цикла
setpal : pop ex восстанавливаем количество строк
pop dx восстанавливаем адрес палитры
xor bx, bx номер первого DAC-регистра
mov ax, 1012h ах = код запрашиваемой функции
int lOh BIOS устанавливает палитру
pop es восстанавливаем содержимое es
ret возврат из подпрограммы

Напомним, что после выполнения примера А.6 в регистрах находятся следующие величины: ах — количество строк в палитре, bх — размер строки в байтах, di — адрес начала палитры. Выполнение примера А.7 начинается с формирования содержимого регистров, используемых при преобразовании палитры. Для этого в сх копируется содержимое ах, а в si — содержимое di. Исходное содержимое es сохраняется и в него записывается код сегмента буфера обмена. Адрес и количество строк будут нужны при установке палитры, поэтому содержимое регистров сх и di сохраняется в стеке.

Цикл преобразования имеет метку s_ip. В нем производятся перестановка красного и синего и сокращение кодов базовых цветов до 6-ти разрядов. Первая команда цикла считывает код синего базового цвета в регистр ai, a вторая команда переставляет содержимое регистра ai и байта со смещением 2. В результате в ai оказывается код красного цвета, а в байте со смещением 2 — код синего цвета. Содержимое регистра ai сдвигается на 2 разряда вправо (сокращается до 6-ти разрядов) и записывается в буфер обмена. Зеленый и синий цвета обрабатываются совместно. Их коды считываются в регистр ах, его содержимое сдвигается на 2 разряда вправо, и очищаются два старших разряда ai. Результат записывается в буфер обмена. Описанные действия повторяются для каждой строки палитры.

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

После цикла преобразования, начиная с команды, имеющей метку setpai, выполняется установка палитры. Размер палитры вйталкивается из стека в регистр сх, а адрес ее начала в dx. Для записи, начиная с нулевого DAc-регистра, bx очищается. В регистр ах помещается код запроса и происходит обращение к BIOS для установки палитры. После возврата из BIOS из стека восстанавливается исходное значение es и подпрограмма BitMap завершается, поскольку она выполнила все предусмотренные действия.



А 8 является несколько



Пример А.8 является несколько измененным вариантом примера 7.19. При выполнении подготовительных действий добавлено формирование содержимого регистров bx, сх и si, а в цикл построения таблицы включена команда add si, bx для пропуска пустого байта, если заголовок соответствует формату Windows.

Если ваша задача устанавливает один из режимов Hi-color, то возьмите за основу пример 7.18 и внесите в него аналогичные изменения.

Заключение.
Основные действия, выполняемые при обработке заголовка файла, описаны в примерах А.2—А.4 и А.6. Если манипуляции с палитрой будет выполнять специализированная подпрограмма, то после выполнения примера А.6 надо завершить выполнение подпрограммы BitMap. Если же BitMap устанавливает палитру или формирует таблицу цветов, а это желательно, то она завершается после выполнения соответствующих действий. В таком случае при выходе из BitMap использованы все данные, находящиеся в буфере обмена, и его содержимое больше не нужно.



А 8 Построение таблицы цветов для режимов True Color



Пример А.8. Построение таблицы цветов для режимов True Color

TabCol: mov si, di si = адрес начала палитры
push es сохраняем содержимое es
les di, dword ptr GenOffs; es:di = адрес таблицы цветов
sub bx, 03 уменьшаем содержимое bx на 3
mov ex, ax ex = количество строк в палитре
modcol: movs word ptr [di], fs [si]; копируем 2 младших байта
lods byte ptr fs:[si] читаем в регистр al третий байт
xor ah, ah очищаем старший байт регистра ах
stosw записываем 2 старших байта
add si, bx корректируем адрес для чтения
loop modcol управление повторами цикла
pop es восстановление содержимого es
ret возврат из подпрограммы BitMap



А 9 Построение рисунка формата ВМР сверху вниз



Пример А.9. Построение рисунка формата ВМР сверху вниз

Smlbmp : pusha сохранение "всех" регистров
PushReg <fs,gs,Cur wi n>; сохранение fs, gs, Cur win
mov ax, horsize ax = horsize
sub ax, iwidth ax = horsize — iwidth
mul bytppnt ax = (horsize — iwidth) * bytppnt, dx = 0
mov dx , ax dx = ax, для коррекции адреса строки
mov ex , si ex = si, размер образа рисунка
mov SwpOffs, 0 начало считываемых данных
call readf чтение образа рисунка
jnc ok -> чтение без ошибок
; Здесь должны выполнят! эСЯ действия в случае ошибки при чтении
ok: mov fs, SwpSeg fs = сегмент буфера обмена
mov gs, GenSeg ! ! gs = сегмент таблицы цветов
mov ex, iheight сх = количество строк в рисунке
invout : push ex сохраняем счетчик повторов
mov ex, iwidth сх = размер строки рисунка
sub si, fwidth адрес начала новой строки
call drawline ! ! или call bx — построение строки
pop ex восстанавливаем счетчик повторов
add si, rmndr учитываем "лишние байты"
sub si, fwidth адрес начала построенной строки
add di, dx коррекция адреса видеопамяти
jne @F -> адрес в пределах сегмента
call NxtWin установка следующего окна
@@: loop invout ; управление повторами цикла
PopReg <Cur win,gs,fs> ; восстановление Cur win, gs, fs
popa ; восстановление "всех" регистров
call setwin ; восстановление исходного окна
ret ; возврат из подпрограммы

Выполнение примера А.9 начинается с сохранения в стеке содержимого регистров общего назначения, сегментных регистров fs, gs и переменной cur_win. Следующие четыре команды вычисляют константу для коррекции адресов строк видеопамяти по способу, описанному в примере 7.13.

Для чтения образа файла в регистр сх помещается его размер, очищается переменная Swpoffs и происходит обращение к подпрограмме readf. Если чтение прошло без ошибок, то команда jnc ok обойдет строку, состоящую только из комментария. Что делать при ошибках чтения, решать вам.

В подпрограммах построения строк (drawiine) регистр fs используется при чтении кодов точек из оперативной памяти, а регистр gs — при работе с таблицей цветов. Поэтому перед началом основного цикла в них записываются коды соответствующих сегментов.
Цикл построения рисунка имеет метку invout. Работа с адресами в нем организована так, что при каждом повторе регистр di содержит адрес очередной строки видеопамяти, а регистр si — адрес последней обработанной строки образа рисунка (при первом входе это размер рисунка в байтах).

Перед вызовом подпрограммы drawiine содержимое si уменьшается на размер строки в файле (fwidth) и указывает начало очередной строки. При выполнении подпрограммы drawiine регистр si увеличится на iwidth. Поэтому после возврата из drawiine значение si увеличивается на rmndr (количество лишних байтов) и уменьшается на fwidth. В результате регистр si будет содержать адрес начала последней обработанной строки.

Адрес начала следующей строки видеопамяти, как обычно, увеличивается на константу переадресации, которая хранится в регистре dx. Если при сложении (add di, dx) происходит переполнение, то устанавливается следующее окно видеопамяти.
Команда loop invout повторяет выполнение цикла нужное количество раз, после чего восстанавливаются сохраненные в стеке величины, исходное окно видеопамяти и происходит возврат на вызывающий модуль.

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

Замечание 1
Замечание 1

Напомним, что имя подпрограммы построения строки не обязательно указывать в явном виде. Команду call drawline можно изменить на call bx, а перед обращением формировать в регистре bх адрес подпрограммы построения строки (см. раздел в данном приложении).



А 10 Построение рисунка формата BMP снизу вверх



Пример А.10. Построение рисунка формата BMP снизу вверх

BigBmp: pusha сохранение "всех" регистров
PushReg <gs,fs,Cur w in>; сохранение gs, fs и Cur win
mov ax, iheight ax = iheight, количество строк в рисунке
mov remline, ax remline = ах, количество строк в рисунке
dec ax учет нумерации строк с нуля
mul bperline dx:ax = (iheight — l)*bperline
add di, ax di = адрес последней строки рисунка
adc dx, 00 учитываем возможный перенос
mov ax, GrUnit ах = GrUnit (единица измерения окон)
mul dl вычисляем добавку к номеру окна
add Cur win, ax номер окна для последней строки
call Setwin установка вычисленного окна
mov ax, -1 ах = 65535
xor dx, dx очистка старшей части делимого
div fwidth ах = 65535 / fwidth (частное от деления)
mov part, ax part = число строк в порции для чтения
mul fwidth ах = ах* fwidth
mov numbyte, ax размер порции считываемой в байтах
mov ax, iwidth ах = iwidth
add ax, horsize ах = iwidth + horsize
mul bytppnt ах = (iwidth + horsize) *bytppnt, dx = 0
mov dx, ax сохраняем для коррекции адресов
mov fs, SwpSeg fs = сегмент буфера обмена
mov SwpOffs, 0 очистка смещения в сегменте
mov gs, GenSeg ! ! gs = сегмент таблицы цветов
NewPart: mov ex, numbyte сх = количество считываемых байтов
call Readf чтение порции в буфер обмена
jnc sr -> чтение прошло без ошибок
; Здесь должны выполни" ься действия при ошибке чтения
sr: mov ex, part сх = кол-во строк в полной порции
cmp remline, ex считана полная порция данных ?
jae @F -> да, обходим следующую команду
mov ex, remline нет, сх = оставшееся число строк
@@: sub remline, ex уменьшаем значение счетчика строк
xor si, si si = начало буфера обмена
drwout: push зх сохраняем значение счетчика строк
mov ex, iwidth ex = размер строки (в точках)
call drawline !! или call bx — построение строки
pop ex восстанавливаем счетчик строк
add si, rmndr корректируем адрес в буфере обмена
sub di, dx di = адрес начала предыдущей строки
jnc @F -> адрес в пределах текущего окна
call PrevWin установка предыдущего окна
@@: loop drwout управление циклом построения строк
cmp remline, 0 остались не обработанные строки ?
jne NewPart -> да, на чтение следующей порции
PopReg <Cur_win,fs,gs>; восстановление Cur_win, fs и gs
рора восстановление "всех" регистров
call Setwin восстановление исходного окна
ret возврат из подпрограммы

Выполнение подпрограммы примера А.10. начинается с подготовительных действий, смысл и назначения которых описаны выше. Основной цикл имеет метку NewPart. Если вы сравните его с одноименным циклом примера 7.25, то убедитесь в их полном совпадении, поэтому мы не будем повторять описание выполняемых действий.

Таким образом, основной цикл построения вмр-файлов произвольного размера не зависит от того, как подготовлен образ исходного рисунка, — с использованием или без использования палитры цветов. От этого зависят только подпрограммы drawline, вызываемые для построения строк рисунков.



А 11 Начало построения рисунка формата BMP



Пример А.11. Начало построения рисунка формата BMP

BmpShow: lea bx, mode_8 ; bx = адрес подпрограммы mode_8
cmp bitcnt, 08 ; bitcnt = 8 ?
je @F ; -> да, переход на локальную метку
lea bx, mode_4 bx = адрес подпрограммы mode_4
cmp bitcnt, 04 bitcnt = 4 ?
je @F -> да, переход на локальную метку
lea bx, mode__2 bx = адрес подпрограммы mode_2
@@: mov ax, fwidth ax = fwidth, размер строки в файле
mul iheight dx:ax = fwidth*iheight
or dx, dx образ рисунка помещается в сегменте ?
jne @F -> нет, файл большого размера
mov si, ax si = размер образа рисунка
jmp Smlbmp переход на Smlbmp
@@: jmp BigBmp переход на BigBmp

Для того чтобы выполняемые в примере А. 11 действия имели смысл, в текстах примеров А.9 и А.10 надо заменить call drawiine на call bx, как указано в комментарии. Первые 7 команд примера А. 11 формируют в bх адрес подпрограммы обработки строки (примеры А.12—А.14), в зависимости от размера кода точки образа рисунка. После этого вычисляется размер образа рисунка, и если он помещается в одном сегменте (dx=o), то выбирается подпрограмма Smlbmp, в противном случае BigBmp.

Таким образом, для построения рисунка формата BMP задача должна обращаться к процедуре BmpShow, как к подпрограмме. Перед ее вызовом в регистре di указывается адрес левого верхнего угла рисунка в видеопамяти и устанавливается окно, которому принадлежит этот адрес. Регистр es должен содержать код видеосегмента (хранящийся в vbuff).



А 12 Вывод строки формата 8 бит на точку (256 цветов)



Пример А.12. Вывод строки формата 8 бит на точку (256 цветов)

mode_8: lods byte ptr f s:[si] ; al = код очередной точки
call outpnt ; обращение к подпрограмме записи
loop mode_8 ; управление повторами цикла
ret ; возврат из подпрограммы
В примере А. 13 приведен текст подпрограммы, выполняющей распаковку точек 16-цветного рисунка в процессе построения строки.



А 13 Вывод строки формата 4 бита на точку (16 цветов)



Пример А.13. Вывод строки формата 4 бита на точку (16 цветов)

mode 4 : lods byte ptr fs: [si] al = код очередных 2-х точек
push ax сохраняем содержимое ах
shr al, 04 выделяем код старшей тетрады
call outpnt обращение к подпрограмме записи
pop ax. восстанавливаем содержимое ах
dec ex сх = сх — 1, счетчик точек в строке
je @F -> все точки выведены
and al, OFh выделяем код младшей тетрады
call outpnt обращение к подпрограмме записи
loop mode 4 управление повторами цикла
§@: ret возврат из подпрограммы

Сравните текст примера А.13 с текстом подпрограммы drwiin4 (см. пример 3.17). В данном случае он упростился за счет использования вспомогательной подпрограммы outpnt.

Замечание
Напомним, что дополнительная коррекция и проверка значения счетчика повторов цикла (сх) нужна потому, что при нечетном количестве точек в рисунке последний байт содержит код только одной точки (младшую тетраду выводить на экран нельзя).

В примере А. 14 приведен текст подпрограммы, выполняющей распаковку 2-цветных рисунков в процессе построения строки.



А 14 Вывод строки формата 1 бита на точку (2 цвета)



Пример А.14. Вывод строки формата 1 бита на точку (2 цвета)

mode 2 : lods byte ptr fs: [si] al = код очередных восьми точек
mov ah, 80h ah = константа для выделения разряда
md 21: push ax сохраняем содержимое ах
and al, ah выделяем текущий разряд
je md_22 если нуль, то обходим одну команду
mov al, 01 иначе записываем в al единицу
md 22: call outpnt обращение к подпрограмме записи
pop ax восстанавливаем содержимое ах
dec ex сх = сх — 1, счетчик точек в строке
je md_23 -> все точки выведены
shr ah, 01 изменяем константу выделения
jne md_21 -> если обработаны не все точки
jmp short mode 2 -> если обработано 8 точек
md 23: ret возврат из подпрограммы

Сравните текст примера А. 14 с текстом подпрограммы drwiinl (см. пример 3.18).

Замечание 1
Замечание 1

Напомним, что дополнительная коррекция и проверка значения счетчика повторов цикла (сх) нужна потому, что в зависимости от количества точек в строке последний байт может быть заполнен частично.

Подпрограммы записи кодов точек существенно зависят от видеорежима. Если задача установила один из видеорежимов PPG, то в самом простом случае выполняются действия, показанные в примере А. 15.



А 15 Простой вывод точки в режимах PPG



Пример А.15. Простой вывод точки в режимах PPG

outpnt: stosb ; запись кода точки в видеопамять
or di, di ; адрес ь пределах сегмента?
jne @F ; -> да
call NxtWin ; нет, установка следующего окна
@@: ret ; возврат из подпрограммы



А 15 рассчитан на те случаи



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

При работе в режимах PPG палитра может быть установлена с изменением исходного расположения базовых цветов. В разделе мы подробно обсуждали, в каких случаях и почему используется такая установка палитры. При любом изменении расположения цветов, описанных в палитре, требуется изменение кодов точек при их записи в видеопамять. Один из вариантов перекодировки был показан в примере 4.8.

При работе в режимах direct color перекодировка производится в тех случаях, когда используется таблица цветов. Вариант подготовки такой таблицы показан в примере А.8. Размер строки таблицы, а следовательно, и действия, выполняемые при перекодировке, зависят от установленного видеорежима. При режимах Hi-Color строка таблицы занимает 2 байта, а при режимах True color — 4 байта. В примере А. 16 приведена подпрограмма, выполняющая перед записью в видеопамять перекодировку точек по таблице цветов.



А 16 Перекодировка точек в режимах direct color



Пример А.16. Перекодировка точек в режимах direct color

outpnt : push eax ; сохранение содержимого еах
and eax, OFFh ; очистка старших разрядов еах shl ax, wrdppnt ; учет размера строки таблицы
add e.x, GenOffs ; ax = смещение начала таблицы
mov ax, qs:[eax] ; !! или mov eax, gs:[eax] для True Color
stosw ; !! или stosd для режимов True Color
pop eax ; восстановление содержимого eax
ret ; возврат из подпрограммы

Текст примера А. 16 является простым повторением текста примера 7.22. Дополнительные подробности вы найдете в разделе.

Заключение.
Мы завершили описание работы с файлами формата BMP. В данном приложении отсутствует описание построения полноцветных рисунков. Это объясняется тем, что оно приведено в разделе основной части книги вместе с текстом соответствующей подпрограммы (см. пример 7.25).



А З Формирование исходных



Пример А.З. Формирование исходных значений основных переменных

Part 2 : mov ax, fs: [si+12h] ax = biwidth, формат Windows и OS/2
mov bx, fs: [si+16h] bx = biHeight, формат Windows
mov ex, fs: [si+ ICh] ex = biBitCnt, формат Windows
cmp byte ptr fs: [si+01 ]h) , 28h; заголовок формата Windows ?
je @F -> да, переходим на запись значений
mov bx, fs: [si+14h] bx = biHeight, формат OS/2
mov ex, fs: [si+18h] сх = biBitCnt, формат OS/2
Э @ : mov iwidth, ax iwidth = biwidth
mov f width, ax fwidth = biwidth
mov iheight, bx iheight = biHeight
mov bitcnt, cl bitcnt = biBitCnt
cmp cl, 18h cl = 18h ?
jne Part_3 -> нет, переход на продолжение
ret возврат из подпрограммы

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

Здесь уместно напомнить, что в стандарте PCX значение переменной fwidth фанится в заголовке файла и вычислять его не надо (см. раздел).

Наиболее простой способ определения значения fwidth следующий. В соответствии со стандартом в полях заголовка хранятся размер файла и адрес начала образа рисунка. Размер образа рисунка в байтах равен разности указанных величин. При делении этой разности на количество строк получается размер строки в байтах.

К сожалению, этот способ на практике не применим. Проверка достаточно большого количества файлов показала, что поле bfsize (его смешение 02) в котором должен находиться размер файла в байтах, может содержать ошибочное значение или нуль. Размер файла можно определить программно, но для этого придется обращаться к DOS со специальными запросами.

Следующие 4 шага. Мы опишем более простой способ, основанный на том что адрес начала строки в файле должен быть кратен четырем. Поэтому если количество точек в строке рисунка не кратно четырем, то при записи в файл после каждой строки добавляется необходимое количество байтов, содержимое которых не определено. При построении образа рисунка эти байты не используются, их просто пропускают. Таким образом, одна из причин различия значений переменных iwidth и fwidth связана с возможным наличием дополнительных байтов в строке файла. Кроме того, вследствие упаковки точек 2- и 16-цветных рисунков, строка в файле будет в 2 или в 8 раз короче строки рисунка (без учета дополнительных байтов).

Для получения нужного результата выполняются следующие 4 шага.

Шаг 4. Предполагаем, что fwidth = iwidth. Округляем это значение до ближайшего целого числа, кратного четырем. Очищаем константу сдвига. Если bitcnt = 8, то переходим к шагу 7, иначе следующий шаг.

Шаг 5. Полученное на предыдущем шаге значение округляем до ближайшего целого, кратного 8. Присваиваем константе сдвига значение 1. Если bitcnt = 4, то переходим к шагу 7, иначе следующий шаг.

Шаг 6. Если мы дошли до этого шага, то bitcnt = 1, т. е. рисунок двухцветный (но не обязательно черно-белый). Округляем полученное на предыдущем шаге значение fwidth до ближайшего целого, кратного 32. Константе сдвига присваиваем значение 3.

Шаг 7. Округленное значение сдвигаем вправо на константу, значение которой выбиралось на предыдущих шагах, и присваиваем его переменной fwidth. Вычисляем количество дополнительных байтов в строке файла (значение переменной rmndr). Нужные величины сформированы.

Программная реализация описанных шагов показана в примере А.4, который является продолжением примеров А.2 и А.З.



Сжатие образа рисунка



Сжатие образа рисунка

Образы рисунков, содержащих более 2-х цветов, могут быть подвергнуты сжатию по способу RLE (Run-Length-Encoding). Прежде всего, отметим, что сжатие возможно только в формате для Windows, в формате для OS/2 оно просто не предусмотрено.

В формате для Windows (см. табл. А. 1) имеется поле BiCompr, в котором указано состояние образа рисунка. Если в этом поле указан о, то образ рисунка хранится в естественном виде. Bicompr=1 означает, что рисунок, содержащий 256 цветов, сжат по способу RLE. BiCompr=2 означает, что рисунок, содержащий 16 цветов, предварительно упакован по 2 точки в байт, а затем сжат по cпособу RLE.

Алгоритм декомпрессии файла, сжатого по способу RLE, следующий:

1. Если значение текущего (первого) байта отлично от нуля, то оно указывает, сколько раз надо повторить в выходном массиве код, находящийся в следующем байте. В противном случае проверяется код следующего байта. 2. Если он больше двух (от 3 до 255), то соответствующее количество последующих байтов просто копируется в выходной массив, т. к. они не упакованы. 3. Значения второго байта 0, 1 и 2 являются признаками конца строки (0), конца рисунка (1) и изменения текущих координат (2). В последнем случае в двух следующих байтах указаны приращения значений координат х и Y, которые надо прибавить к их текущим значениям.

Таким образом, в основе упаковки по способу RLE лежит замена группы подряд расположенных одинаковых кодов двумя байтами, в первом указыва-ются количество повторов, а во втором — повторяемый код. Сама по себе эта идея не принадлежит разработчикам стандарта BMP, они только выбирали конкретную реализацию. Аналогичный способ используется и в стандарте сх, но его реализация несколько проще. Мы обсуждали ее в разделе и в разделе основной части книги.

Замечание 1
Замечание 1

Автор исследовал достаточно много файлов формата BMP, но не обнаружил ни одного сжатого рисунка. Напомним также, что в формате для OS/2 сжатие просто исключено. Остается только гадать, зачем надо было описывать способ сжатия и не использовать его на практике? Ведь аналогичный способ сжатия применяется при подготовке файлов формата PCX. Мы не будем рассматривать программирование распаковки именно по причине отсутствия упакованных рисунков.




А 1 Заголовок дарфайла для Windows



Таблица А. 1. Заголовок дар-файла для Windows

Смещение поля Размер в байтах Имя поля Назначение поля
00 (ООН) 2 bfType Метка "ВМ"
02 (02h) 4 bfSize Размер файла в байтах
06 (06h) 2 Reserved Резервное поле
08 (08h) 2 Reserved Резервное поле
10(OAh) 4 BfOffBits Смещение области данных
14(OEh) 4 BiSize Размер области информации
18(12h) 4 BiWidth Ширина рисунка в точках
22(16h) 4 BiHeight Высота рисунка в точках
26(1Ah) 2 Biplanes Количество плоскостей (всегда 1)
28(1Ch) 2 BiBitCnt Количество бит на точку
30(1 Eh) 4 BiCompr Тип сжатия данных
34 (22h) 4 BiSizelmage Размер данных в байтах
38 (26h) 4 biXPels/M Количество точек на метр по оси
42 (2Ah) 4 biYPels/M Количество точек на метр по оси
46 (2Eh) 4 BiClrUsed Количество используемых цветов
50 (32h) 4 BiClrlmprt Количество цветов в палитре


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

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

Большинство рисунков получается путем оцифровки рукотворных картинок, фотографий, слайдов, кино или видеокадров. При выводе таких рисунков на печать важно знать размер оригинала. Необходимые для этого данные находятся в полях biXPels/M И biYPels/M, ГД6 Pels означает Picture Elements (элемент рисунка или просто точка). Если содержащиеся в них величины умножить на 25,4 и разделить на 1000, то получится разрешающая способность устройства, с помощью которого была оцифрована картинка. Например, при сканировании с разрешением 300 точек на дюйм по обеим координатам на один метр приходится приблизительно 300-1000/25,4 =11811 точек. Этому числу соответствует код 2Е23h, который и будет храниться в описываемых полях. Учитывая возможные ошибки вычислений, реальная величина может незначительно отличаться от вычисленной.



Информационная часть заголовка сократилась до



Таблица А.2. Заголовок вмр-файла для OS/2

Смещение поля Размер в байтах Имя поля Назначение поля
00 (OOh) 2 bfType Метка "ВМ"
02 (02h) 4 BfSize Размер файла в байтах
06 (06h) 2 Reserved Резервное поле
08 (08h) 2 Reserved Резервное поле
10(OAh) 4 BfOffBits Смещение области данных
14(OEh) 4 BcSize Размер области информации
18(12h) 2 BcWidth Ширина рисунка в точках
20(14h) 2 BcHeight высота рисунка в точках
22(16h) 2 BcPlanes Количество плоскостей (всегда 1 )
24(18h) 2 BcBitCnt Количество бит на точку
Информационная часть заголовка сократилась до 12 байтов, в то время как в стандарте Windows она занимала 40 байтов. Мы подчеркиваем этот факт потому, что размер информационной части заголовка является единственным критерием для определения типа вмр-файла в программе.

После заголовка располагается палитра, а затем образ самого рисунка, т. е. в целом структуры обоих типов вмр-файлов (для Windows и для OS/2) идентичны, что упрощает задачу программиста. Остается только гадать, почему программа bitmap.exe, входящая в состав Norton Commander (NC), не обрабатывает вмр-файлы для OS/2.


Таблица цветов используется для



Таблица цветов используется для преобразования кодов точек при построении рисунка.

Способы построения таблицы цветов описаны в разделe (для режимов Hi-color) и разделe (для режимов True color). Здесь мы ограничимся примерами ДЛЯ реЖИМОВ True Color.

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

Если заголовок файла соответствует формату Windows, то исходная палитра просто копируется в буфер общего назначения. А если заголовок файла соответствует формату OS/2, то при копировании палитры к базовым цветам каждой строки надо добавить пустой байт.

Программная реализация построения таблицы цветов показана в примере А.8, который является продолжением примера А.6. Предполагается, что в регистрах находятся следующие данные: ах — количество строк в палитре, bx — размер строки в байтах, di — адрес начала палитры.



Универсальная процедура построения рисунка



Универсальная процедура построения рисунка









В данном разделе описана универсальная



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

Текст процедуры приведен в примере А. 11, программы описаны ниже.



Возможные отклонения от стандарта



Возможные отклонения от стандарта









Ввод спецификации и открытие файла



Ввод спецификации и открытие файла









Оболочка Presentation Manager операционной системы



Заголовок файла для OS/2

Оболочка Presentation Manager операционной системы OS/2 использует другой формат заголовка вмр-файла. Разработчики OS/2 учли избыточность стандарта BMP для Windows и сократили заголовок до минимально необходимых размеров. Его структура показана в табл. А.2. Для упрощения ссылок в ней сохранены те же обозначения полей, что и в табл. А.1, но в соответствии со стандартом OS/2 в именах полей, начиная с адреса 14 (OEh), префикс bi изменяется на bc.


Заголовок файла для Windows



Заголовок файла для Windows









Здесь описан способ построения



Здесь описан способ построения рисунка, при котором строки его образа выбираются в обратном порядке (начиная с последней строки), а на экран выводятся в естественном порядке сверху вниз.