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


         

Цвет в коде точки

Видеорежимы с указанием цвета непосредственно в коде точки (direct color) были введены в версии стандарта VBE 1.2, опубликованной в октябре 1991 года. К этому времени цветные сканеры уже преодолели барьер в 256 цветов, и возникла необходимость стандартизации способов работы с цветом. Кроме того, за время, прошедшее после публикации первых версий стандарта VBE, элементная база существенно улучшилась и позволяла выпускать видеокарты с нужными техническими характеристиками.

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

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


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

Кодирование цвета

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

Размер кода точки и расположение в нем базовых цветов зависят от видеорежима. Стандарт VESA предусматривает возможность определения указанных величин при выполнении задачи. В главе 2 мы договорились, что характеристики установленного видеорежима находятся в массиве info, а их перечень приведен в табл. 1.2. В массиве info количество разрядов в коде точки хранится в байте 19h, а расположение базовых цветов для режимов direct color — в байтах iFh-26h. Корректно составленная задача должна использовать эти величины для настройки на конкретный видеорежим.


Среднее количество цветов

Режимы среднего цветового разрешения в англоязычной литературе принято называть Hi-Color. При их установке возможны два способа кодирования цвета, различающиеся размерами кода точки. Например, в режиме иоь код точки занимает 15 разрядов, в которых можно указать 32 768 (или 32К) различных комбинаций (цветов), а в режиме nih код точки занимает 16 разрядов, в которых можно указать 65 536 (или 64К) разных комбинаций. Разрешение в обоих режимах одинаковое (640x480 точек), различается только расположение базовых цветов.

Кодирование цвета. В режимах m-coior код точки занимает одно слово, расположение базовых цветов в его разрядах показано в табл. 7.1.

Таблица 7.1. Размещение базовых цветов в слове

Режим 32К цветов
Красный цвет
Зеленый цвет
Синий цвет
F E D C B A 9 8 7 6 5 4 3 2 1 0
Режим 64К цветов

         
 

Максимальное цветовое разрешение

Максимальное цветовое разрешение обеспечивают видеорежимы VESA, которые в англоязычной литературе принято называть True color или 24-bit Color, последнее название указывает размер кода цвета, а не кода точки. В этих режимах базовые цвета имеет 256 градаций, а общая палитра содержит 256x256x256 = 16 777 216 (пли 16М) цветов.

Кодирование цвета

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

Таблица 7.2. Расположение базовых цветов в 32-разрядном слове

БайтЗ
Разряды 24—31

Байт 2
Разряды 16—23

Байт 1
Разряды 8— 15

Байт 0
Разряды 0—7

1F

...

18

17

...

10

F

...

8

7

...

0

Резервный байт

Красный цвет

Зеленый цвет

Синий цвет

Байты оперативной и видеопамяти располагаются в порядке увеличения их номеров (адресов). Именно в такой последовательности (слева направо) они выводятся на экран при просмотре содержимого памяти с помощью различных редакторов. Адрес двойного слова совпадает с адресом его нулевого байта, поэтому первым в памяти хранится код синего цвета, вторым — зеленого, третьим — красного, а четвертый байт пустой (резервный).

Код в памяти и в регистре

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

При сравнении содержимого памяти и регистров у неискушенного человека может сложиться впечатление, что базовые цвета переставлены. На самом деле это не так, просто одни и те же данные представлены двумя разными способами. Сказанное иллюстрирует табл. 7.3.

Таблица 7.3. Расположение кодов цвета в памяти и в регистре

Название цвета

Байты памяти

Байты памяти

0

1

2

3

3

2
1
0

Черный

00

00

00

00

00

00
00
00

Синий

FF

00

00

00

00

00
00
FF

Зеленый

00

FF

00

00

00

bD
FF
00

Красный

00

00

FF

00

00

FF
00
00

Белый

FF

FF

FF
00
00
FF
FF
FF

Как уже говорилось, после установки видеорежима его основные характеристики можно прочитать в массиве info. В нем, начиная со смешения IF. расположены четыре пары байтов, содержащие размер кода и адрес его младшего бита для красного, зеленого, синего цветов и резервного пространства. Обычно в них находятся следующие коды: 08, 10h, 08, 08, 08, 00, 08, 18h, что соответствует табл. 7.2.

После установки режимов True Color задача обязательно должна проверять байт массива info со смещением I9h, содержащий размер точки в битах. Если этот байт содержит код 20h, то видеокарта поддерживает 32-разрядный код точки. Не все видеокарты устанавливают код такого размера, одно из исключений описано в следующем разделе.

Возможность работы в режимах True Color зависит от объема памяти, расположенной на видеокарте. При объеме видеопамяти 1 Мбайт эти режимы не поддерживаются. При объеме видеопамяти 2 Мбайт памяти доступны видеорежимы H2h и ush, имеющие разрешения 640x480 и 800x600 точек. При объеме видеопамяти 4 Мбайт к ним добавляется режим ush с разрешением 1024x768 точек. Напоминаем, что перечень режимов приведен в табл. 1.1.

Сравнение с режимом Hi-Color

В режимах True color код базового цвета увеличился на три разряда, соответственно количество градаций базовых цветов увеличилось в 8 раз, а общее количество цветовых оттенков — в 256 раз. Поэтому передача цвета в режимах True Color существенно улучшается по сравнению с режимами Hi-color. Однако последние в два раза сокращают необходимый объем видеопамяти и во столько же раз или больше ускоряют манипуляции с графическими объектами. Поэтому во многих случаях, например при работе графических акселераторов, основным является режим Hi-Color. Тем более, что способность человеческого глаза различать 256 градаций базовых цветов весьма сомнительна.

 

Глава 24-разрядный код точки

Стандартом VESA не оговорено обязательное наличие резервного байта в коде точки. Поэтому видеокарты, у которых он отсутствует, а код точки занимает всего 24 разряда, формально соответствуют требованиям стандарта VBE 1.2.

Такой размер кода точки был обнаружен автором при исследовании единственной видеокарты МАСН64 фирмы ATI Technologies, выпущенной 20 октября 1997 г. Как уже говорилось в главе 1, по данным на сентябрь 1998 года фирма ATI вошла в первую пятерку производителей графических чипов, на ее долю приходится 27% этой продукции. Поэтому весьма вероятно, что видеокарты, поддерживающие 24-разрядный код в режимах True color, будет выпускать не только фирма ATI.

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

Недостатки трехбайтового кода

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

1. У всех без исключения команд микропроцессоров Intel размер операнда кратен степени двойки, поэтому обработать три байта одной командой нельзя. В таком случае при обмене данными с видеопамятью приходится обрабатывать сначала слово, а затем байт или наоборот, что замедляет процесс обмена. Однако это не самое неприятное. 2. Размер сегмента оперативной или видеопамяти так же кратен степени двойки.

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

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

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

Координаты и адреса точек

Для вывода точки заданного цвета в нужное место экрана надо связать координаты этого места с адресом видеопамяти, по которому должен быть записан код точки. Поэтому мы вновь возвращаемся к вопросам, рассмотренным в разделе, но с учетом особенностей режимов direct color.

Новые переменные

После чтения массива info задаче доступны две величины, имеющие отношение к разрешающей способности режима.

1. Одна из них расположена в слове со смещением 12h, она указывает ширину экрана, выраженную в точках. В главе 2 предлагалось хранить копию этого слова в переменной Horsize, которая затем неоднократно использовалась в примерах. 2. Другая величина расположена в слове со смещением ion, она указывает, сколько байтов видеопамяти отображается при выводе строки на экран. Иначе говоря, это ширина строки, умноженная на размер кода точки, выраженный в байтах. В документации VESA она называется bytes per scan line.

При работе в режимах PPG обе величины совпадают, поэтому мы использовали только первую из них. Теперь нам может пригодиться и вторая величина, поэтому после чтения массива info ее значение надо присвоить переменной bperiine, она была описана в примере 2.11, но не применялась.

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

Если копию этой переменной сдвинуть еще на 1 разряд вправо, то получится еще одна переменная, содержащая количество слов в коде точки. Имена и описания новых переменных следующие:

bperiine dw 2560 ; размер строки отображаемой на экране в байтах
bytppnt dw 0004 ; размер кода точки, выраженный в байтах
wrdppnt dw 0002 ; размер кода точки, выраженный в словах

В этом описании переменных указаны значения, которые получаются при установке режима H2h — True color, 640x480 точек.

Преобразование координат в адрес выполняется перед началом работы с большинством графических объектов. Примеры таких преобразований при работе в режимах PPG приводились неоднократно. Здесь нас будет интересовать универсальный вариант преобразования, который можно использовать при работе во всех видеорежимах VESA.

При описании подпрограммы примера 6.13 (перемещение курсора) было рекомендовано для учета размера кода точки, после вычисления адреса, сдвинуть результат на 1 или 2 разряда влево, что равносильно умножению на 2 или на 4. Это самый простой, но не универсальный способ, поскольку при составлении программы надо знать величину сдвига, которая зависит от видеорежима. Кроме того, с помощью сдвигов невозможно выполнить умножение на 3, нужны дополнительные команды сложения. Поэтому, в общем случае, целесообразно выполнять умножение, а не сдвиг.
Формулу для вычисления адреса по заданным значениям координат х и у можно записать в следующем виде:

Address = (Y*horsize t- X) *bytppnt

При замене умножения на bytppnt сдвигами действия выполнялись в той последовательности, в какой они указаны в формуле — сначала умножение, затем сложение и, наконец, сдвиг. Если же сдвиг заменить умножением, то последовательность действий придется изменить.

Результат заключенных в скобки действий расположен в двух регистрах, dx содержит его старшую часть, а ах — младшую. Для умножения двойного слова (или содержимого двух регистров) на значение переменной bytppnt потребуется много вспомогательных действий. Чтобы упростить вычисления в приведенной выше формуле, надо раскрыть скобки и учесть, что bperiine = horsize*bytppnt, в результате получится следующее выражение:

Address = Y*bperline + X*bytppnt

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

В примере 7.3 приведен текст подпрограммы, выполняющей вычисления по этой формуле. Перед ее вызовом в регистре сх указывается номер столбца (координата х), а в регистре dx — номер строки (координата у). Вычисленный адрес помещается в регистры dx:ax, т. е. в ах находится значение окна, а в dx — адрес (смещение) в этом окне.

Пример 7.3. Универсальная подпрограмма вычисления видеоадреса

Daladdr: mov ax, bperline ax = размер строки в байтах
mul dx dx:ax = Y*bperline
push dx сохраняем старшую часть результата
xchg ax, ex обмен содержимого регистров
mul bytppnt ах = X*bytppnt, dx = 0
add ax, ex вычисляем младшую часть адреса
mov dx, ax и сохраняем ее в регистре dx
pop ax ах = старшая часть Y*bperline
adc ax, 00 учитываем возможность переноса
mul byte ptr GrUnit ах = al * GrUnit
add ax, Base win ! ! если используется базовое окно
ret выход из подпрограммы

Текст примера 7.3 не нуждается в подробных пояснениях, обращаем ваше внимание только на следующие особенности. Содержимое регистра dx (старшую часть произведения y*bperiine) надо сохранить в стеке потому, что оно будет испорчено при втором умножении. После второго умножения и вычисления младшей части адреса старшая часть выталкивается из стека в регистр ах. К ней прибавляется единица переноса, которая могла возникнуть, если при выполнении команды add ax, сх произошло переполнение и был установлен С-разряд регистра флагов (признак Carry). Команды пересылки и выталкивания из стека не изменяют состояние С-разряда. Поэтому если он был установлен, то adc ax, 00 прибавит единицу к содержимому регистра ах.

Можно изменить текст примера 7.3 так, чтобы вычисленный адрес возвращался в регистре di, значение окна присваивалось переменной cur_win и выполнялась установка окна (call setwin). В результате получится вариант подпрограммы Caiiwin, описанной в примере 3.4, применимый в любых видеорежимах VESA.

Другой вариант Caladdr

В примере 7.4 показан вариант подпрограммы caiaddr, в котором вместо умножения x*bytppnt содержимое регистра сх сдвигается на к разрядов влево. В зависимости от видеорежима, в команде сдвига букву к надо заменить цифрами 1 или 2, т. е. эта подпрограмма не универсальна.

Пример 7.4. Пересчет координат в адрес с использованием сдвига

Caladdr: mov ax, bperline ax = размер строки в байтах
raul dx dx:ax = Y*bperline
shl ex, k k=l для Hi-Color; k=2 для True Color
add ax, ex вычисляем младшую часть адреса
adc dx, 00 учитываем возможность переноса
xchg ax, dx обмен содержимого регистров
mul byte ptr GrUnit ax = al * GrUnit
add ax, Base_win !! если используется базовое окно
ret выход из подпрограммы

В примере 7.4 сдвигается не результат умножения, а только значение координаты х (содержимое регистра сх). Это возможно потому, что значение координаты Y (содержимое регистра dx) умножается не на Horsize, а на bperline = horsize*bytppnt.

Особенность операций сдвигов заключается в том, что величина сдвига может либо находиться в регистре el (младший байт регистра сх), либо указываться непосредственно в команде, других вариантов нет. Поэтому для автоматического выбора величины сдвига в примере 7.4 вместо двух подряд расположенных команд shl ex, k и add ax, сх надо записать следующие:

mov bx, wrdppnt ; bx = величина сдвига
xchg bx, ex ; обмен содержимого регистров
shl bx, cl ; сдвиг значения координаты X
add ax, bx ; вычисляем младшую часть адреса

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

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

Координаты и адреса смежных точек

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

Если базовая точка не лежит на одной из четырех границ экрана, то ее окружает восемь смежных точек. В табл. 7.4 показаны приращения значений координат и адресов смежных точек. Базовая точка имеет координаты х, Y, а приращение ее адреса равно нулю. В правой части таблицы буква k соответствует переменной bytppnt, а буква w — переменной bperline.

Таблица 7.4. Приращения координат и адресов смежных точек

Приращение смешанных координат

Приращение их адресов

X-1.Y-1

X, Y-1

X+1.Y-1

k-w

W

k-w

X-1.Y

X, Y

X+1.Y

k

0

k

X-1.Y+1

X, Y+1

X+1.Y+1

w-k

w

w+k

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

Адрес следующей строки

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

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

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

offsline = bperiine — widthrect*bytppnt = (horsize — widthrect)*bytppnt.

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

При работе в режимах PPG bytppnt=1 и формула превращается в разность (horsize - widthrect), которая и вычислялась в приведенных ранее примерах. В разделe и разделe при описании подпрограмм построения и перемещения изображения курсора мы советовали в режимах direct color сдвигать указанную разность на 1 или 2 разряда влево. Такой прием прост, но не универсален. Универсальные способы вычисления значения offsline для режимов direct color описаны ниже в

Линии, строки и прямоугольные области

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

Способ пересылки зависит от размера кода точки и не зависит от расположения в нем базовых цветов. В режимах direct color код точки может занимать 2, 3 или 4 байта, а команды пересылки и строковые операции работают только со словами (2 байта) или с двойными словами (4 байта). Тем не менее при определенных условиях можно составить подпрограммы, выполнение которых не зависит от размера кода точек. Мы опишем эти условия и приведем примеры универсальных подпрограмм для рисования линий и построения строк рисунков.


Подпрограммы для рисования линий

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

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

Базовые варианты подпрограмм. В примере 7.5 приведены два варианта подпрограмм рисования линии, различающиеся способом пересылки. Перед их вызовом код цвета точки помещается в регистр ах или еах, а размер линии (количество точек) в сх.

Исходный адрес видеопамяти указывается в регистре di, и устанавливается окно видеопамяти, которому принадлежит этот ад-5сс. Как обычно, регистр es должен содержать код видеосегмента (значение теременной vbuff).

Пример 7.5. Цикл рисования горизонтальной линии в режимах Hi-Color

; Вариант 1, используется команда пересылки
lorline: mov es: [di], ax !! для True Color — mov es[di], eax
add di, bytppnt переадресация операнда
jne @F переход, если не нуль
call NxtWin установка следующего окна
}Q: loop horline управление повторами цикла
ret возврат из подпрограммы
; Вариант 2, используется строковая операция.
lorline: stosw !! для True Color — stosd
or di, di начало нового сегмента ?
jne @F -> нет
call NxtWin установка следующего окна
i@: loop horline управление повторами цикла
ret возврат из подпрограммы

Текст примера 7.5 отличается от текста примера 3.6 незначительными изменениями. В первом варианте при переадресации используется не 1, а значение переменной bytppnt, которое равно 2 или 4.

Для использования подпрограмм примера 7.5 в режимах True Color надо первые команды в обоих вариантах заменить командами, указанными в сомментариях. В этих режимах перед вызовом подпрограмм код цвета точек юмещается в регистр еах, поскольку он занимает 32 разряда.

Давайте уточним, почему приведены два варианта циклов, если они содержат одинаковое количество команд. Команда пересылки удобна в тех случаяx, когда переадресация не может выполняться сразу после записи или чтения кода точки (см. пример 6.5), или когда шаг переадресации не совпадает : размером кода точки, например при рисовании вертикальных линий. Если оказанные условия не существенны, то второй вариант цикла 7.5 предпочтительнее. После компиляции он окажется короче первого на 3 байта, и будет выполняться несколько быстрее. Но главное, при определенных условиях юзможно применение микропрограммного цикла, существенно ускоряющего процесс рисования. Об этом мы поговорим особо.

Ускорение цикла рисования

В примере 7.5 после каждой переадресации вы-юлняется проверка принадлежности нового значения адреса текущему сегменту и, в случае необходимости, устанавливается следующее окно видео-тамяти. Вероятность того, что новое значение адреса выйдет за границу :егмента, достаточно мала. Например, при установке видеорежимов 110h или 111h (Hi-color, 640x480) рабочее пространство видеопамяти занимает 9 неполных окон. Если создаваемое изображение заполняет все это пространство, то только в восьми случаях из 307 200 возникнет необходимость изменения окна, а в остальных случаях проверка адресов не требуется. Сказанное не означает, что она вообще не нужна. Для сокращения количества дополнительных действий надо изменить способ проверки.

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

Замечание
Рисование линии по частям в режимах PPG уже описано в разделе, а соответствующая подпрограмма при: ведена в примере 3.8.

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

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

Входные параметры подпрограммы Twopart полностью соответствуют параметрам подпрограммы horiine (пример 7.5) и расположены в тех же регистрах.

Пример 7.6. Рисование линии по част-ям в режимах Hi-Color

push dx coxp аняем содержимое регистра dx
mov dx, di копируем адрес в регистр dx
shl ex, 01 ! ! для True Color - shl ex, 02
add dx, ex сумма исходного адреса и размера линии
jc @F -> прямая расположена в двух окнах
xor dx, dx очис тка регистра dx
@@: sub ex, dx количество точек в текущем окне
shr сх, 01 ! ! для True Color - shr ex, 02
call baseip рисуем всю линию или ее первую часть
or di, di адрес в пределах текущего окна ?
jne hrl_exit -> да, линия нарисована полностью
call NxtWin установка следующего окна
add ex, dx количество не нарисованных точек
je hrl_exit линия нарисована полностью
shr ex, 01 !! для True Color - shr ex, 02
call baselp рисуем остаток линии
hrl exit: pop dx восстановление содержимого dx
ret возврат из подпрограммы
; Подпрограмма, выполняющая основные действия
baselp: mov es: [di],ax !! для True Color — mov es: [di], eax .
add di, bytppnt переадресация операнда
loop baselp управление повторами цикла
ret возврат из подпрограммы

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

При первом вызове baselp может быть нарисована вся линия или только ее первая часть. Это важно знать для выполнения дальнейших действий. Они начинаются с проверки значения адреса, находящегося в регистре di.

Возможен случай, когда при первом рисовании достигнута граница окна. В таком случае в регистре di находится 0, но нулевой адрес принадлежит не текущему, а следующему окну видеопамяти. Поэтому если регистр di очищен, то обязательно надо сменить окно видеопамяти. Если же содержимое di отлично от нуля, то нарисована вся линия.

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

Замечание
Практическая ценность примера 7.6 заключается в том, что он иллюстрирует способ обработки строки графического объекта по частям. Основные действия локализованы в подпрограмме baselp. Ее можно изменить так, чтобы вместо записи в видеопамять содержимого регистра ах выполнялись другие действия, например инверсия кодов точек, пересылка кодов из видеопамяти в оперативную или наоборот и т. д.

Ускоренное рисование линии. В подпрограмме baselp, текст которой приведен в примере 7.6, работу с видеопамятью выполняет одна команда, поэтому сразу после нее возможна переадресация операнда. Если при этом шаг переадресации совпадает с размером операнда, то вместо команды пересылки можно использовать строковую операцию. В таком случае тело цикла пересылки сокращается до одной команды rep stosw, которую надо вставить вместо call baselp. Это и сделано в примере 7.7.

Пример 7.7. Ускоренное рисование линии в режимах Hi-Color I

horline: push dx сохраняем содержимое регистра dx
mov dx, di копируем адрес в регистр dx
shl ex, 01 ! ! для True Color - shl ex, 02
add dx, ex сумма исходного адреса и размера линии
jc hrl 1 -> прямая расположена в двух окнах
xor dx, dx очистка регистра dx
hrl_l : sub ex, dx количество точек в текущем окне
shr ex, 01 ! ! для True Color — shr ex, 02
rep stosw ! ! для True Color — stosd
or di, di адрес в пределах текущего окна ?
jne hrl exit -> да, линия нарисована полностью
call NxtWin установка следующего окна
mov ex, dx количество не нарисованных точек
shr ex, 01 ! ! для True Color - shr ex, 02
rep stosw ! ! для True Color — stosd
hrl exit: pop dx восстановление содержимого dx
ret возврат из подпрограммы

Обратите внимание на то, что во второй части примера 7.7 проверяется только содержимое регистра di и не проверяется оставшееся количество точек. Это допустимо потому, что если регистр сх очищен, то цикл rep stosw не будет выполняться и предварительная проверка содержимого сх не обязательна.

В комментариях к примерам 7.6 и 7.7 указано, как надо изменить переменные команды для использования подпрограмм в режимах True Color, в таком случае код цвета линии помещается в регистр еах.

Условное ассемблирование

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

Признак (условие для выбора) описывается как обычная константа и располагается в начале текста программы перед описанием первого сегмента.

Предположим, что ему присвоено имя variant, а значения равны 1 для режимов Hi-color и 2 для режимов True color. Описание выглядит так:

variant = 1 ; Описание признака "variant" для режимов Hi-Color

В текст подпрограммы, приведенной в примере 7.8, вместо переменной команды вставлен условный блок, в котором описаны варианты выбора.

Пример 7.8. Выбор варианта команды по заданному признаку

IF variant EQ 1 ; проверка условия выбора
mov es:[di], ax ; основной вариант команды
ELSE ; признак альтернативного варианта
mov es:[di], eax ; альтернативный вариант команды
ENDIF ; конец условного блока

При выполнении примера 7.8 Макроассемблер выберет вариант команды пересылки в зависимости от значения признака variant. Если variant=2, то будет выбрана команда, записанная после ELSE.

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

Значения признака variant выбраны не случайно. При таких значениях в примерах 7.6 и 7.7 в операциях сдвига можно заменить 1 на имя variant. Тогда в примере 7.6 останется только одна переменная команда, а в примере 7.7 — две. Соответственно, в текст примера 7.6 понадобится включить один условный блок (описанный в примере 7.8), а в текст примера 7.7 — два для выбора вариантов команд rep stosw или rep stosd.

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

Трехбайтовый код точки

Такой код не укладывается в общую схему по двум причинам. Во-первых, размер операндов команд не может быть равен трем байтам. Во-вторых, существуют особые точки, код которых расположен в двух смежных сегментах. Поэтому нужны специальные подпрограммы, при составлении которых учитываются особенности трехбайтовых кодов. Две такие подпрограммы, выполняющие запись и чтение кода точки, приведены в примерах 7.1 и 7.2 (см.

Подпрограммы для построения строк

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

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

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

Для всех вариантов подпрограммы построения строки мы сохраним то расположение входных параметров в регистрах, которое было принято в разделе. Адрес оперативной памяти (источника) указывается в паре регистров fs:si, а адрес видеопамяти (приемника) — в регистре di. Предварительно устанавливается окно видеопамяти, которому принадлежит адрес первой точки (указанный в di). Регистр es должен содержать код сегмента видеобуфера, хранящийся в переменной vbuff.

Исходный вариант подпрограммы

Для дальнейших рассуждений нам нужен простой вариант подпрограммы, иллюстрирующий последовательность действий при построении строки. Он приведен в примере 7.10.

Пример 7.10. Цикл построения строки в режиме Hi-Color

drawline: mov ax, fs: [si] ; ! ! для True Color — mov eax, fs[si]
mov es:[di], ax ; !! для True Color — mov es:[di], eax
add si, bytppnt переадресация операнда источника
add di, bytppnt переадресация операнда приемника
jne @F переход, если не нуль
call NxtWin установка следующего окна
@@: loop drawline управление повторами цикла
ret возврат из подпрограммы

В комментариях к тексту примера 7.10 показано, как надо изменить две первые команды для того, чтобы подпрограмма drawline могла использоваться при работе в режимах True color.

Вариант со строковой операцией

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

При простом копировании строк размер операнда совпадает с кодом точки (если он не трехбайтовый) и возможна коррекция адреса сразу после записи точки. Поэтому в примере 7.10 команды пересылки имеет смысл заменить строковой операцией. Одна строковая операция заменяет четыре первые команды — две пересылки и две переадресации операндов. Измененный цикл построения строки приведен в примере 7.11. Его можно использовать в тех случаях, когда код точки занимает 2 или 4 байта.

Пример 7.11. Улучшенный цикл построения строки в режиме Hi-Color

drawline: movs word ptr [di], f s: [si] ; !! movs dword ptr [di], f s: [si]
or di, di ; начало нового сегмента ?
jne @F ; -> нет
call NxtWin ; установка следующего окна
I @@: loop drawline ; управление повторами цикла
ret ; возврат из подпрограммы

Первая команда примера 7.И переменная, способ ее записи для пересылки 32-разрядных кодов (режим True color) показан в комментарии.

Для использования всех преимуществ строковой операции из цикла надо исключить проверку значений адресов, т. е. пересылать строку по частям так, как это делалось в примере 7.7. В зависимости от видеорежима, основные действия в нем выполняли команды rep stosw (режим Hi-Color) или rep stosd (режим True color). При подстановке в текст примера 7. И их надо изменить так, как показано ниже.

rep stosw заменяется командой rep movs word ptr [di], fs:[si]
rep stosd заменяется командой rep movs dword ptr [di], fs:[si]

Мы не будем приводить измененный вариант примера 7.7, а перейдем к описанию универсального способа пересылки.

Универсальная подпрограмма пересылки

Давайте вернемся к постановке вопроса. Задана строка, содержащая N точек, код каждой из них занимает м байтов. Эту строку надо скопировать из одного места памяти в другое. Мы специально употребили выражение "место памяти", поскольку пересылка из оперативной в видеопамять является частным случаем.

При такой формулировке задачи напрашивается очевидный способ ее решения. Надо вычислить количество байтов в строке (L = N * м) и переслать L байтов из одного места памяти в другое. Обратите внимание, в результате умножения мы избавились от размера кода точки и при составлении подпрограммы учитывается только количество пересылаемых байтов.

Частные случаи решения такой задачи обсуждались уже несколько раз. Наиболее подробно был рассмотрен один из них при описании способов ускорения рисования линии в режимах PPG (примеры 3.8—3.10). Остается собрать указанные примеры в одну подпрограмму, включив в нее вычисление количества байтов в строке. Это и сделано в примере 7.12.

Пример 7.12. Универсальный (цикл пересылки) способ построения строк

drawl ine: push dx сохранение содержимого регистра dx
xchg ax, ex обмен содержимого регистров (ах = N)
mul bytppnt dxrax = L =-N * М
xchg CX j 3X обмен содержимого регистров (сх = L)
pop dx восстановление содержимого регистра dx
drawalt : push dx сохранение содержимого регистра dx
mov dx, di копирование адреса в регистр dx
add dx, ex dx = исходный адрес + L
jc @F -> прямая расположена в двух окнах
xor dx, dx остаток в dx равен нулю
@@: sub ex, dx количество байтов в текущем окне
call moveto строим первую часть строки
mov ex, dx сх = оставшееся количество байтов
pop dx восстановление содержимого регистра dx
or di, di адрес в пределах текущего окна ?
jne d exit -> да, строка построена полностью
call NxtWin установка следующего окна
moveto: shr ex, 01 преобразуем байты в слова
jnc @F -> четное число байтов
movs byte ptr [di] , fs:[si] ; пересылка одного байта
@@: shr ex, 01 преобразуем слова в двойные слова
jnc @F -> четное число слов
movs word ptr [di], fs:[si] ; пересылка одного слова
@@: je d exit ; -> пересылать больше нечего
rep movs dword ptr [di], fs:[si] ; основной цикл пересылки
d exit: ret ; возврат из подпрограммы

Выполнение примера 7.12 начинается с вычисления количества байтов в строке. При умножении используются регистры dx и ах, поэтому содержимое dx сохраняется в стеке, а содержимое ах — за счет двухкратного использования команды xchg. Произведение находится в регистрах dx:ax. Мы будем считать, что оно меньше чем 65 536, т. е. dx содержит 0. Команда обмена xchg помешает результат в сх, одновременно восстанавливая исходное состояние ах, а из стека выталкивается исходное содержимое регистра dx.

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

Команда с меткой drawait сохраняет в стеке содержимое регистра dx, затем в него копируется адрес видеопамяти, который увеличивается на размер строки в байтах. Если при сложении произойдет переполнение (установка С-разряда), то команда jc @F исключает очистку dx. В противном случае строка помещается в текущем окне и регистр dx очищается. Затем вычисляется количество выводимых точек, и подпрограмма moveto строит первую часть строки.

После построения первой части строки в регистр сх копируется содержимое dx (остаток строки). Регистр dx освободился и надо восстановить его исходное состояние. Для выбора дальнейших действий проверяется текущий адрес в регистре di, если он отличен от нуля, то построение строки завершено и выполняется команда ret. В противном случае устанавливается следующее окно видеопамяти, и подпрограмма moveto строит остаток линии. После ее выполнения завершится работа основной подпрограммы, т. к. в верхушке стека находится адрес возврата на вызывающий модуль.

В подпрограмме moveto команда rep movs dword ptr [di], f s: [si] является основной, она пересылает группу 32-разрядных слов. Однако количество байтов в строке не обязательно кратно четырем. Поэтому нужна предварительная проверка и пересылка от одного до трех "лишних" байтов, так чтобы остаток был кратен четырем.

Команда, имеющая метку moveto, сдвигает содержимое регистра сх на разряд вправо. Если оно было нечетным, то пересылается первый байт строки, в противном случае jnc @F исключает эту пересылку.

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

В результате выполнения двух сдвигов и пересылки "лишних" байтов в строке остается целое число 32-разрядных слов, количество которых находится в регистре ex. Если оно равно нулю, то произойдет переход на команду ret, в противном случае выполняется микропрограммный цикл копирования 32-разрядных слов. После этого выполняется команда ret.

Вторая точка входа drawait введена для тех случаев, когда известен размер строки в байтах. Приведем несколько примеров таких случаев. При работе в режимах PPG количество байтов совпадает с количеством точек. Если размер строки равен ширине экрана, то ее размер в байтах указывает переменная bperiine и вычислять его нет смысла. При построении рисунка, содержащего большое количество строк, целесообразно один раз вычислить размер строки в байтах, а не повторять одни и те же вычисления при построении каждой строки. Наконец, возможны также случаи, когда количество байтов вычисляется нестандартным способом, например, зависит от определенных условий.

Обсуждение результатов

Описанная подпрограмма не только не зависит от размера кода точек, но и затрачивает минимально возможное время на построение строки, что особенно важно при работе в режимах direct color. Поэтому мы советуем использовать именно ее в тех случаях, когда возможна простая пересылка строк графических объектов.

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

Если при работе со строкой выполняется не простое копирование, а более сложные действия, то для сокращения количества проверок адресов видеопамяти можно использовать работу с двумя частями строки. Алгоритм работы с двумя частями приведен в примере 7.6 (подпрограмма Twopart). Для выполнения конкретных действий надо составить подпрограмму baselp, которая в примере 7.6 вызывается для обработки каждой из двух частей строки. Например, такая подпрограмма может считывать код очередной точки, как-то обрабатывать его и возвращать результат на старое место. Вопрос о целесообразности работы с двумя частями строки решается с учетом конкретных особенностей алгоритма работы с графическим объектом.


Работа с прямоугольными областями

В данном разделе нас будет интересовать многофункциональная подпрограмма, способная выполнять различные манипуляции с графическими объектами прямоугольной формы. Ее составление возможно при условии, что требуемые действия выполняют специализированные вспомогательные подпрограммы.

Переадресация строк

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

offsline = bperline — widthrect*bytppnt = (horsize — widthrect)*bytppnt.

Как видно из этой формулы, значение offsline зависит от видеорежима (переменные Horsize и bytppnt) и от ширины объекта (переменная widthrect), поэтому его приходится вычислять в каждом конкретном случае.

При работе в видеорежимах PPG команды для вычисления значения offsline (пересылка в и вычитание) мы включали в тексты примеров.

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

Варианты подпрограммы calloffs. В примере 7.13 показаны два варианта подпрограмм, вычисляющие значение offsline с использованием команд умножения или сдвигов. Входным параметром является значение переменной widthrect, которое указывается в регистре dx. Этот регистр выбран потому, что во всех примерах он использовался для указания ширины прямоугольной области. Для совместимости с ранее приведенными примерами результат вычислений находится в регистре bх.

Пример 7.13. Варианты подпрограмм для вычисления offsline

; Вариант 1 — вычисление off-sline с использованием сдвигов
calloffs: push ex сохранение содержимого сх
mov ex, wrdppnt ex = величина сдвига
mov bx, horsize bx = ширина экрана в точках
sub bx, dx bx = horsize — widthrect
shl bx, cl bx = (horsize - widthreet) * bytppnt
pop ex восстановление содержимого сх
ret возврат из подпрограммы
; Вариант 2 — вычисле ние offsline с использованием умножения
calloffs: push dx сохранение содержимого dx
xchg ax, bx обмен содержимого регистров ах, bx
mov ax, horsize ах = ширина экрана в точках
sub ax, dx ах = horsize — widthrect
mul bytppnt ах = (horsize — widthrect)* bytppnt
xchg ax, bx ; обмен содержимого регистров ах, bх pop dx ; восстановление содержимого dx ret ; возврат из подпрограммы

Первый вариант подпрограммы примера 7.13 короче на одну команду и выполняется немного быстрее второго, но его можно использовать только в тех случаях, когда код точки занимает 1, 2 или 4 байта.

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

Пересылка в видеопамять

При работе с графикой достаточно часто приходится сохранять и восстанавливать содержимое видеопамяти. Это делается, например, при каждом перемещении курсора.

В примере 7.14 приведен текст подпрограммы, выполняющей копирование содержимого оперативной памяти в видеопамять. Входными параметрами подпрограммы являются размер прямоугольной области и адреса операндов источника и приемника. Ширина прямоугольной области помещается в регистр dx, а высота в регистр сх. Адрес оперативной памяти (источника) указывается в регистрах fs:si. Адрес видеопамяти (приемника) помещается в регистр di и устанавливается окно видеопамяти, которому принадлежит этот адрес. В регистре es должен находиться код видеосегмента (A000h).

Пример 7.14. Подпрограмма пересылки из оперативной в видеопамять

Rstreg: PushReg <bx, ex, di, si, Cur_win> ; сохранение в стеке
call calloffs ; вычисление константы offsline
mvsr: push сх сохранение значения счетчика строк
mov ex, dx задание количества точек в строке
call drawline копируем очередную строку
add di, bx адрес начала следующей строки
jnc @F -> адрес в пределах окна
call Nxtwin установка следующего окна
@@: pop сх восстановление счетчика строк
loop mvsr управление повторами цикла
PopReg <Cur_win, si, di, ex, bx> ; восстановление из стека
call setwin восстановление исходного окна
ret возврат из подпрограммы

Текст примера не требует особых пояснений — подобные циклы мы описывали неоднократно, например, в разделе (подпрограмма draw). Поговорим о том, что явно не следует из текста.

Зависимость от установленного видеорежима в данном примере скрыта в подпрограммах calloffs и drawline. Если вы выберете второй вариант подпрограммы caiioffs примера 7.13 и подпрограмму drawiine, текст которой описан в примере 7.12, то подпрограмма Rstreg будет выполняться в любом видеорежиме, независимо от размера кода точки.

Размер прямоугольной области, выраженный в байтах, не должен превышать размера стандартного сегмента памяти, т. е. 65 536 байтов. Это ограничение связано с тем, что пересылаемые данные находятся в оперативной памяти, которая сегментирована так же, как и видеопамять, а в примере 7.14 отсутствует контроль значения адресов оперативной памяти.

Способы контроля значений адресов оперативной и видеопамяти ничем не отличаются друг от друга, но существенно различаются способы переключения сегментов, которые зависят еще и от типа оперативной памяти. Они подробно описаны в приложении Б данной книги. Там же приведен пример подпрограммы, выполняющей сохранение или восстановление содержимого всего пространства видеопамяти отображаемого на экране (см. примеры Б.7 и Б.8).

Пересылка из видеопамяти

Для сохранения исходного содержимого видеопамяти производится его копирование (пересылка) в оперативную память. Нас интересуют универсальные процедуры пересылки, основанные на применении строковых операций. Однако у строковых операций фиксированы эегистры, содержащие адреса операндов источника и приемника. Поэтому при пересылке в обратном направлении в es:di должен находиться адрес шеративной памяти, а в fs-.si — адрес видеопамяти. Для удобства лучше сохранить единообразный способ указания адресов в регистрах и изменять его в подпрограмме на время пересылки.

Заметим, что и после перестановки адресов использовать подпрограмму drawiine из примера 7.12 нельзя. При ее составлении предполагалось, что адрес видеопамяти находится в регистре di, а он оказался в регистре si. Поэтому надо сделать копию примера 7.12, присвоить ей новое имя, например saveiine, и заменить в двух командах копии имя регистра di именем регистра si. В результате получится универсальная подпрограмма, выполняющая сохранение строки видеопамяти в оперативной памяти.

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

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

Пример 7.15. Подпрограмма пересылки из видеопамяти в оперативную

Savereg: PushReg <bx, ex, di, si, es, Cur_win> ; сохранение в стеке
mov bx, fs ; копируем код сегмента из fs в bx
mov es, bx ; копируем код сегмента из bx в es
mov fs, Vbuff ; fs = сегмент видеобуфера
ЮЗак П78
xchg di, si перестановка адресов di и si
call calloffs вычисление константы offsline
svrg: push ex сохранение значения счетчика строк
mov ex, dx задание количества точек в строке
call saveline копируем очередную строку
add si, bx адрес начала следующей строки
jnc @F -> адрес в пределах окна
call Nxtwin установка следующего окна
@@: pop ex восстановление счетчика строк
loop svrg управление повторами цикла
push es помещаем код сегмента из es в стек
pop fs и выталкиваем его из стека в fs
PopReg <Cur_win, es, si, di, ex, bx> ; восстановление из стека
call setwin восстановление исходного окна
ret возврат из подпрограммы

Напомним, что команда xchg не работает с сегментными регистрами, а у команды mov только один операнд может быть именем сегментного регистра. Поэтому для пересылки содержимого одного сегментного регистра в другой приходится использовать либо регистр-посредник, либо стек. Оба этих способа показаны в примере 7.15.

Основной цикл пересылки примера 7.15 имеет имя svrg, он отличается от аналогичного цикла примера 7.14 (mvsr) только одной командой. При вычислении адреса следующей строки константа коррекции прибавляется к содержимому регистра si, а не di, как это делалось в примере 7.14.

Заливка прямоугольной области

Изменим текст примера 7.14 так, чтобы его можно было использовать для окрашивания прямоугольной области заданным цветом. В этом случае при выполнении цикла должна вызываться подпрограмма horiine, а не drawline. Регистр si не используется, поэтому его ИМЯ Исключается ИЗ СПИСКОВ PushReg И PopReg.

Измененный текст подпрограммы показан в примере 7.16. При ее вызове код цвета указывается в регистрах ах или еах (в зависимости от видеорежима). Ширина прямоугольной области задается в регистре dx, а высота — в сх. Адрес видеопамяти для левого верхнего угла должен находиться в регистре di, регистры fs:si не используются. Предполагается, что es содержит код видеосегмента и установлено исходное окно видеопамяти.

Пример 7.16. Окрашивание прямоугольной области заданным цветом

Fillreg: PushReg <bx, ex, di, Cur_win> ; сохранение в стеке
call calloffs ; вычисление константы offsline
fillrg: push ex ; сохранение значения счетчика строк
mov ex, dx ; задание количества точек в строке
call horline рисуем очередную строку
add di, bx адрес начала следующей строки
jnc @F -> адрес в пределах окна
call Nxtwin установка следующего окна
@@: pop ex восстановление счетчика строк
loop fillrg управление повторами цикла
PopReg <Cur_win, di, ex, bx> ; восстановление из стека
jmp setwin установка окна и выход

В отличие от примеров 7.14 и 7.15, в данном случае размер закрашиваемой области экрана не ограничен, лишь бы хватило памяти, установленной на видеокарте. Например, для окрашивания всей рабочей поверхности экрана в нужный цвет, перед вызовом подпрограммы код цвета помещается в регистре bx (или еах), в сх копируется переменная versize, а в dx — Horsize, регистр di очищается и устанавливается нулевое окно видеопамяти.

Текст примера 7.16 не зависит от видеорежима, но в нем вызывается подпрограмма horline, в тексте которой есть переменные команды, зависящие от видеорежима (см.

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

Полноцветные и подготовленные с применением палитры рисунки имеют разное назначение. В режимах direct color графические задачи должны "уметь" работать с любыми рисунками, независимо от способа их подготовки.

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

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

В большинстве стандартов используется формат rgb, впервые он был применен в стандарте PCX. В формате rgb строка состоит из трех байтов, в которых хранятся коды красного (r), зеленого (g) и синего (b) базовых цветов, расположенные в указанной последовательности.

Единственный в своем роде, но широко распространенный стандарт BMP для Windows предусматривает хранение палитры в формате bgro. В этом случае строка палитры состоит из четырех байтов. В трех первых хранятся коды синего (b), зеленого (g) и красного (r) базовых цветов, расположенные в указанной последовательности, последний байт резервный, обычно он очищен.

В модифицированном стандарте BMP для OS/2 из строки палитры исключен резервный байт, а в трех оставшихся байтах базовые цвета расположены в формате bgr.

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


Преобразование палитры в форматы Hi-Color

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

Способы чтения палитры файлов стандартов BMP и PCX описаны, соответственно, в приложении А и в разделе. В обоих случаях мы советовали размешать прочитанную палитру в буфере обмена, сегмент которого хранится в переменной swpseg, расположенной в разделе данных задачи.

Независимо от стандарта, для определения размера и местонахождения палитры в файле надо, прежде всего, прочитать и проанализировать его заголовок. При чтении заголовок помещается в буфер обмена, поэтому к моменту начала обработки палитры в регистре fs находится код сегмента (копия переменной Swpseg), а в si — адрес начала палитры в памяти.

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

После чтения палитры вызываются описываемые ниже подпрограммы. Они формируют таблицу (одномерный массив), содержащую коды цветов, формат которых соответствует установленному видеорежиму. Для хранения таблицы надо выделить пространство оперативной памяти. Учитывая, что количество цветов в исходной палитре не больше 256-ти, в режимах Hi-Color размер указанного пространства памяти составляет 256 слов (512 байтов).

Расположение таблицы цветов

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

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

GenOffs dw 0 ; адрес (смещение) в буфере общего назначения
GenSeg dw 0 ; сегмент, содержащий буфер общего назначения

Способы выделения пространства для размещения буфера описаны в приложении Б данной книги. После его выделения становится известным значение сегмента, которое задача должна сохранить в GenSeg. Исходное значение Genoffs равно нулю, а текущее значение зависит от того, какая часть буфера используется в данный момент времени.
Все приведенные ниже подпрограммы используют следующие входные параметры:

адрес начала преобразуемой палитры указан в регистрах fs:si; размер палитры (в виде количества строк) в регистре сх; адрес начала формируемой таблицы цветов указывают переменные Genseg и GenOffs; результат преобразований помещается в таблицу цветов.

Теперь о преобразованиях. Формируемый код цвета зависит от установленного видеорежима Hi-Color. Существует две разновидности этих режимов. В одном случае код цвета занимает 15, а в другом 16 разрядов, расположение базовых цветов для обоих случаев показано в табл. 7.1. Мы рассмотрим два варианта подпрограмм, формирующих 15-разрядный код цвета, и обсудим, как сформировать 16-разрядный код цвета.

Палитра формата rgb

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

Пример 7.17. Преобразование палитры rgb в 15-разрядный код

cnvpal: PushReg <ax,bx,cx,di,si,es>; сохранение содержимого регистров
les di, dword ptr GenOffs; es:di = адрес таблицы цветов
modcol: mov al, fs:[si] ; читаем код красного цвета в al
shr al, 03 ; сокращаем его до 5-ти разрядов
mov bh, fs:[si+1] ; читаем код зеленого цвета в bh
shld ax, fax, 05 ; !! или shld ax, bx, 06
mov bh, fs: [si-t-2] читаем код синего цвета в bh
shld ax, bx, 05 сдвигаем и дополняем код в ах
add si, 03 адрес следующей строки палитры
stosw записываем новый код цвета
loop modcol управление повторами цикла
PopReg <es,si,di,cx,bx,ax> ; восстановление регистров
ret возврат из подпрограммы

В примере 7.17 основную роль играет команда shld, напомним ее особенности. При выполнении команды содержимое первых двух операндов, в данном случае это регистры ах и bx, сдвигается как одно 32-разрядное слово, но записывается только старшая часть результата в первый операнд, а содержимое второго операнда не изменяется. Таким образом, в примере 7.17 команда shld сдвигает содержимое регистра ах на пять разрядов влево и записывает в освободившееся место пять старших разрядов регистра bх.

В зависимости от установленного видеорежима код зеленого цвета может занимать 5 или 6 разрядов. В комментарии показано, как изменится одна из команд shld в случае формирования 16-разрядного кода цвета.

Устаревший формат палитры PCX. В разделе 4.4 говорилось о том, что существует устаревший формат 256-цветной палитры, поддерживаемый версией стандарта PCX, разработанной фирмой Genius. Если вам надо работать с файлом, соответствующим этому стандарту, то проще всего преобразовать его в основной стандарт фирмы ZSoft. Это можно сделать, например, с помощью графического редактора Photofinish фирмы ZSoft.

Палитры формата bgr и bgr0

В этом случае базовые цвета в строке палитры и в формируемом коде расположены в противоположном порядке, коды красного и синего цветов переставлены местами. Есть два способа учесть эту особенность. Байты палитры можно считывать по порядку, а перед добавлением очередного цвета сдвигать формируемый код вправо, а не влево, как это делалось раньше. Другой способ заключается в том, чтобы считывать байты строки палитры в обратном порядке, а формируемый код по-прежнему сдвигать влево. В примере 7.18 реализован второй способ формирования кода цвета.

Пример 7.18. Преобразование палитры bgr в 15-разрядный код

cnvpal: PushReg < ax,b x, ex, di , si,es>; сохранение содержимого регистров
les di, dwo :d ptr ( 5enOffs; es:di = адрес таблицы цветов
modcol : mov al, fs: [si+2] читаем код красного цвета в al
shr al, 03 сокращаем его до 5-ти разрядов
mov bh, fs: [si+1] читаем код зеленого цвета в bh
shld ax, bx, 05 ! ! или shld ax, bx, 06
mov bh, fs: [si] читаем код синего цвета в bh
shld ax, bx, 05 сдвигаем и дополняем код в ах
add si, 03 ! ! для формата "bgrO" — add si, 04
stosw записываем новый код цвета
loop modcol управление повторами цикла
PopReg <es,si ,di, ex, зх,ах> ; восстановление регистров
ret возврат из подпрограммы

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

В примере 7.18 появилась вторая переменная команда, выполняющая переадресацию строк палитры. При работе с форматом bgr значение адреса увеличивается на 3, а с форматом bgr0 — на 4. На практике нет никакой необходимости работать с переменной командой, ее второй операнд просто не должен зависеть от размера строки палитры.
Палитры форматов bgr и bgr0 используются в двух разных версиях стандарта BMP.

Заголовки файлов обрабатываются по единой схеме, не зависящей от версии (см. приложение А данной книги). При обработке заголовка обязательно определяется размер строки палитры т. к. он нужен при всех манипуляциях с палитрой. Поэтому вторым операндом команды add si, оз должно быть не значение константы, а имя регистра или переменной, в котором (в которой) хранится размер строки палитры.

Универсальный вариант подпрограммы

В обоих приведенных примерах от установленного режима Hi-Color зависит третий операнд одной из команд shid. От переменной команды можно избавиться следующим способом.

При выполнении задачей подготовительных действий обязательно считывается массив info, содержащий наиболее важные характеристики установленного видеорежима. В частности, в байте этого массива со смещением 2in указан размер кода зеленого цвета. В режимах Hi-color там находится число 5 или 6. Это число надо сохранить в специально выделенном байте, присвоив ему удобное для вас имя, например gcoisize, и использовать в подпрограммах примеров 7.17 и 7.18 при обработке зеленого цвета.
Причем величина сдвига может быть либо явно указана в записи команды, либо помещена в регистр el, который применяется в качестве первого операнда команды. В примерах 7.17 и 7.18 регистр сх используется в качестве счетчика, поэтому понадобятся две дополнительные команды для сохранения и восстановления его содержимого. Таким образом", одна переменная команда shid может быть заменена следующей группой команд:

push сх ; сохраняем счетчик повторов цикла
mov cl, gcoisize ; cl = величина сдвига (5 или 6)
shid ах, bx, cl ; сдвиг на величину, указанную в cl
pop сх ; восстанавливаем счетчик повтором цикла

Учитывая, что цикл преобразований повторяется не более 256-ти раз, дополнительные потери времени будут не столь велики, зато подпрограмма окажется применимой во всех видеорежимах Hi-Color.


Преобразование палитры в форматы True Color

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

Мы предполагаем, что вы внимательно прочитали, по крайней мере, начало предыдущего раздела и знаете, как задаются входные параметры перед вызовом подпрограмм, где расположены исходная палитра и формируемая таблица цветов. Отметим только, что при работе в видеорежимах True color размер таблицы цветов увеличивается до 1024 байтов (1 Кбайт), поскольку формируемый код цвета занимает 32 разряда (двойное слово).

Палитра формата bgr0 не требует никаких преобразований, поскольку расположение базовых цветов и резервного байта полностью соответствует режимам True color (см. табл. 7.2 и 7.3). В этом случае палитру из файла надо просто прочитать в буфер общего назначения, а не в буфер обмена, как мы это обычно делали.

Замечание
Напомним, что чтение из файла выполняет функция 3Fh прерывания int 2lh. Перед ее вызовом адрес для размещения прочитанных данных указывается в регистрах ds:dx. Мы использовали для чтения подпрограмму readf, описанную в примере 3.23, которая загружала в эти регистры сегмент и смещение буфера обмена. Для чтения в любое указанное место памяти она не предназначена, поэтому вам придется составить аналогичный вариант подпрограммы, выполняющей чтение из файла в буфер общего назначения.

Палитра формата bgr отличается от формата bgr0 отсутствием в ее строках пустого (резервного) байта. Поэтому формирование кода цвета сводится к копированию базовых цветов из палитры и добавлению пустого байта. Текст подпрограммы приведен в примере 7.19.

В цикле формирования кода примера 7.19 из палитры в таблицу цветов сначала копируется первое слово, содержащее коды синего и зеленого цветов. Затем в байт al считывается из палитры код красного цвета, а байт ah очищается. В результате в регистре ах оказывается старшая часть 32-разрядного кода цвета. Следующая команда stosw записывает содержимое ах в таблицу цветов. Код очередного цвета сформирован и записан, а поскольку для чтения и записи в цикле использованы строковые операции, то дополнительные команды переадресации не требуются. Последняя команда loop управляет повторами цикла.

Палитра формата rgb

В этом случае в строках палитры не только отсутствует пустой (резервный) байт, но и базовые цвета расположены в обратном порядке, по сравнению с форматом bgr. Поэтому, в отличие от примера 7.19, их простое копирование не возможно — требуется изменение расположения цветов. В примере 7.17 для расположения базовых цветов в нужной последовательности мы использовали сдвиг формируемого кода перед добавлением очередного цвета. Этот прием применим и в данном случае, как выполняется подобное преобразование, показано в примере 7.20.

Пример 7.19. Преобразование палитры bgr в 32-разрядный код

cnvpal: PushReg <ax,ex,di,si,es> ; сохранение содержимого регистров
les di, dword ptr GenOffs ; es:di = адрес таблицы цветов
modcol: movs word ptr [di], fs:[si]; копируем 2 младших байта
lods byte ptr fs:[si] ; читаем в регистр al третий байт
xor ah, ah ; очищаем старший байт регистра ах
stosw ; записываем 2 старших байта
loop modcol ; управление повторами цикла
PopReg <es,si,di,cx,ax> ; восстановление регистров
ret ; возврат из подпрограммы

Пример 7.20. Преобразование палитры rgb в 32-разрядный код

cnvpal: PushReg <ax,ex,di,si,es> ; сохранение содержимого регистров
les di, dword ptr GenOffs; es:di = адрес таблицы цветов
modcol: xor ah, ah очистка регистра ah
lods byte ptr f s:[si] чтение кода красного цвета
shl eax, 16 сдвиг содержимого еах влево на слово
lods word ptr fs:[si] чтение зеленого и синего цвета
xchg ah, al перестановка синего и зеленого цвета
stosd запись строки в таблицу цветов
loop modcol управление повторами цикла
PopReg <es,di,cx,ax> восстановление регистров
ret возврат из подпрограммы

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

Она сдвигается на 16 разрядов и оказывается в старшей половине регистра еах, а в младшую считываются коды зеленого и синего цветов. Для того чтобы содержимое регистра еах соответствовало табл. 7.2, эти цвета надо поменять местами, что и делает команда xchg. Сформированный код записывает в таблицу цветов команда stosd. Для чтения и записи в цикле использованы строковые операции, поэтому команды переадресации не нужны. Сразу после stosd выполняется команда loop, управляющая повторами цикла.


Построение рисунков с использованием палитры

Ранее в разделе были подробно описаны способы построения не сжатых рисунков, в образах которых точки расположены в естественном порядке. При этом мы различали рисунки ограниченного и произвольного размера. Соответствующие варианты подпрограмм приведены в примерах 3.21 и 3.22, они предназначены для выполнения в режимах PPG. В данном разделе описаны такие варианты этих примеров, которые можно использовать в любых видеорежимах.

Построение строки рисунка

Для построения строки рисунка в примерах 3.21 и 3.22 вызывалась подпрограмма drawiine. Большинство описанных ранее ее вариантов выполняет копирование кодов точек образа рисунка в видеопамять. Исключением являются примеры 2.17 и 2.18, в которых при построении строки выполняется распаковка 16- и 2-цветных рисунков. В данном случае нам нужен вариант подпрограммы, выполняющий перед записью в видеопамять перекодирование точек образа рисунка по таблице цветов.

Текст подпрограммы приведен в примере 7.21. Перед ее вызовом в регистрах указываются следующие данные: сх — размер строки, di — адрес ее первой точки в видеопамяти, fs:si — адрес начала рисунка в оперативной памяти, gs — сегмент оперативной памяти, содержащий таблицу цветов. Адрес начала таблицы в этом сегменте подпрограмма выбирает сама из переменной GenOffs. Как обычно, предварительно надо установить окно видеопамяти, к которому относится указанный в di адрес, а в регистре es должен находиться код видеосегмента.

Пример 7.21. Построение строки с перекодированием по таблице цветов

drawline: push eax сохранение содержимого еах
drwlin: lods byte ptr fs:[di] ; al = код точки образа рисунка
and eax, OFFh очистка старших разрядов еах
shl ax, wrdppnt учет размера строки таблицы
add ax, GenOffs ax = смещение начала таблицы
mov ах, дз: [еах] ! ! или mov eax, gs: [eax] для True Color
stosw !! или stosd для True Color
or di, di достигнута граница окна ?
jne @F -> нет, обход следующей команды
cal I NxtWin установка следующего окна
@@: loop drwlin управление повторами цикла
pop eax восстановление содержимого еах
ret возврат из подпрограммы

В цикле примера 7.21 регистр еах используется для указания адреса при чтении кода цвета точки. Адрес формируется в младшем слове регистра, а его старшее слово должно быть очищено. Поэтому после чтения в al кода образа точки старшие разряды регистра еах очищаются с помощью операции "конъюнкция". В зависимости от установленного видеорежима, третья команда сдвигает содержимое ах на 1 или 2 разряда влево. Остается прибавить к ах смещение таблицы в сегменте gs, и адрес требуемого кода будет сформирован. Код считывается в регистр ах (или в еах) и оттуда записывается в видеопамять с помощью строковой операции stosw или stosd. Далее,
как обычно, проверяется адрес видеопамяти и в случае необходимости устанавливается следующее окно. Повторами цикла управляет команда loop.

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

Упрощение подпрограммы

В примере 7.22 приведен текст упрощенного варианта подпрограммы, выполняющей перекодировку и запись одной точки, код которой указан в регистре ai.

Пример 7.22. Перекодировка по таблице и запись точки в видеопамять

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

Построение небольшого рисунка

Подпрограмма построения рисунка, образ которого помещается в одном сегменте, приведена в примере 7.23.

Перед ее вызовом устанавливается окно видеопамяти, содержащее левый верхний угол рисунка, а адрес этого угла помещается в регистр di. Адрес начала образа рисунка в оперативной памяти записывается в регистры fs:si. Его ширина и высота указываются, соответственно, в dx и сх. Адрес начала таблицы подпрограмма выбирает из переменных GenSeg и GenOffs. Как обычно, должно быть установлено окно видеопамяти, к которому относится указанный в di адрес, а регистр es должен содержать код видеосегмента.

Пример 7.23. Построение рисунка из файла небольшого размера

drawing: PushReg <di,si,bx,ex,gs,Cur_win>; сохранение в стеке
call calloffs вычисление константы offsline
mov gs, GenSeg gs = сегмент таблицы цветов
drwout: push ex сохраняем счетчик повторов
mov . ex, dx задаем размер строки рисунка
call drawline построение очередной строки
pop ex восстанавливаем счетчик повторов
add di, bx коррекция адреса видеопамяти
jnc @F -> адрес в пределах сегмента
call NxtWin установка следующего окна
@@: loop drwout ; управление повторами цикла
PopReg <Cur win,gs,cx,bx,si,di>; восстановление из стека
call setwin ; восстановление исходного окна
ret ; возврат из подпрограммы

Если вы сравните тексты примеров 3.21 и 7.23, то обнаружите, что они различаются только второй и третьей командами, а в списках параметров макровызовов PushReg и PopReg в примере 7.23 добавился регистр gs. Ну и, конечно же, имя drawiine относится к двум разным подпрограммам.

Построение большого рисунка

Если образ рисунка не помещается в одном сегменте, то его приходится считывать из файла и выводить на экран по частям. Поэтому перед началом цикла построения вычисляется размер порции считываемых данных в байтах и количество содержащихся в ней строк. Способ построения рисунка по частям показан в примере 3.22. В него надо внести некоторые изменения, для учета особенностей режимов direct color.

Измененный текст приведен в примере 7.24. Напомним, что в этом случае при вызове явно указывается только адрес начала рисунка в видеопамяти (в регистре di). Размеры рисунка подпрограмма выбирает из переменных iheight и iwidth. Как обычно, должно быть установлено исходное окно видеопамяти, а в регистре es указан код видеосегмента (обычно А000h).

Пример 7.24. Построение рисунка из файла произвольного размера

BigDraw: pusha сохраняем стандартные регистры
PushReg <fs, gs, CL г win> ; сохраняем fs, gs и Cur win
mov fs, SwpSeg fs = сегмент буфера обмена
mov gs, GenSeg gs = сегмент таблицы цветов
mov SwpOffs, 0 нулевой адрес в буфере обмена
xor dx, dx старшая часть делимого dx=0
mov ax , - 1 младшая часть делимого ах=65535
div iwidth ах = 65535 / iwidth
mov part, ax число строк в порции для чтения
mul iwidth количество байтов в порции для чтения
mov nurnbyte , ex сохраняем его в numbyte
mov ax, iheight копируем количество строк в рисунке
mov remline, ax в счетчик не выведенных строк
mov dx, iwidth dx = iwidth, ширина рисунка
call calloffs вычисляем константу переадресации
NewPart: mov ex, numbyte размер порции для чтения
call readf чтение очередной порции данных
jnc sucread -> чтение без ошибок
; Здесь должны выполь яться действия в случае ошибки чтения
sucread : mov ex, part ex = стандартное количество строк
cmp remiine, ex осталось меньше строк ?
jae lbl_l -> нет, обходим команду пересылки
mov ex, remiine сх = оставшееся число строк
1Ы 1: sub remiine, ex уменьшаем оставшееся число строк
xor si, si адрес начала в буфере обмена
drwout : push ex сохраняем счетчик повторов
mov ex , dx задаем размер строки рисунка
call drawline построение очередной строки
pop ex восстанавливаем счетчик повторов
add di, bx адрес начала следующей строки
jnc @F -> адрес в пределах сегмента
call NxtWin установка следующего окна
@@: loop drwout управление повторами цикла
cmp remiine, 0 все строки выведены ?
jne NewPart -> нет, продолжаем построение
PopReg <Cur win, js, fs> ; восстановление Cur win, gs и fs
popa восстановление всех регистров
call setwin восстановление исходного окна
ret возврат из подпрограммы

По сравнению с оригиналом (см. пример 3.22), текст примера 7.24 изменился в той части, где выполняются подготовительные действия. Добавилось сохранение в стеке содержимого регистра gs и запись в него кода сегмента, содержащего таблицу цветов, а для вычисления константы переадресации строк видеопамяти вызывается подпрограмма calioffs. В заключительной части примера добавилось восстановление из стека исходного содержимого gs.

Цикл построения рисунка не изменился, но имя drawline соответствует другой подпрограмме построения строки, поскольку нужна перекодировка точек по таблице цветов.

Учет лишних байтов

В примерах 7.23 и 7.24, так же, как и в примерах 3.21 и 3.22, предполагается, что строки образа рисунка расположены в файле подряд друг за другом. На практике это не всегда так. По разным причинам к каждой строке может быть добавлено некоторое количество байтов, содержимое которых не определено и их нежелательно записывать в видеопамять.

В указанных примерах вывод "лишних" байтов исключен, поскольку обрабатывается ровно столько точек, сколько их содержится в строке. Однако в них отсутствует пропуск "лишних" байтов в строке образа рисунка. Если таковые имеются, то построенное изображение будет искажено.

Способ определения наличия дополнительных байтов зависит от особенностей формата (или стандарта), которому соответствует файл, содержащий образ рисунка. Общих правил не существует. Как это делается при работе с файлами формата BMP, подробно описано в приложении А.


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

Полноцветные (full-color) или художественные точечные компьютерные рисунки не используют палитру. Цвет каждой точки указывается в ее коде, который занимает три байта, содержащих восьмиразрядные коды базовых цветов. Обычно такие рисунки получаются путем оцифровки, например, сканирования, цветных фотографий, слайдов, картинок, видеокадров и т. п. Иногда при подготовке значков или пиктограмм различного назначения они создаются вручную с помощью графических редакторов.

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

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


Рисунки, подготовленные в стандарте BMP

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

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

Строки образа рисунка хранятся в файле в обратном порядке, т. е. первой записана последняя строка, а последней — первая. Код каждой точки занимает три байта, содержащих базовые цвета в формате bgr. Адрес начала строки в файле должен быть кратен 4, а т. к. размер строк (в байтах) кратен 3, то после обработки каждой из них может понадобиться пропустить от О до 3 байтов в файле. Специального признака, указывающего наличие дополнительных байтов в строке, не существует, поэтому надо вычислить размер строки в файле и количество "лишних" байтов. Размер строки в файле равен утроенному количеству точек в строке, округленному до значения кратного четырем. А разность между округленным и не округленным значениями равна количеству дополнительных байтов.

Способ построения рисунка

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

Коррекция адресов строк

Для определения адреса начала следующей строки мы прибавляли к текущему адресу видеопамяти константу коррекции, которая вычисляет подпрограмма caiioffs (см. пример 7.13) по формуле (horsize - widthrect) *bytppnt. В данном случае нас интересует адрес предыдущей строки, поэтому формулу для вычисления константы коррекции надо записать в виде (horsize + widthrect} *bytppnt и вычитать вычисленное значение из текущего адреса видеопамяти.

Объясним, почему так, а не иначе. В процессе построения строки исходный адрес видеопамяти увеличивается на widthrect*bytppnt байтов. Если текущий адрес видеопамяти уменьшить на эту величину, то получится адрес начала построенной строки. А если адрес начала текущей строки уменьшить на величину bperiine (horsize*bytppnt), то получится адрес начала предыдущей строки (см. табл. 7.4).

Есть два способа вычисления нужной нам величины. Можно составить вариант подпрограммы caiioffs, в котором вычитание заменено сложением. А можно ничего не изменять в caiioffs, но перед ее вызовом указывать в регистре dx отрицательное значение переменной widthrect (ширина рисунка). Мы используем второй способ.

Адрес начала последней строки

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

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

Размер порции данных

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

При чтении из файла размер порции данных задается в байтах, а для управления повторами цикла построения рисунка используется количество считанных строк. В примере 3.22 количество строк вычислялось путем деления числа 65 535 на размер строки, а размер порции — путем умножения количества строк на 65 535. В данном случае размер строки в файле может не совпадать с размером строки в рисунке и перед делением его надо вычислить.

Новые переменные. Для хранения размеров рисунка и величин, вычисляемых в процессе подготовительных действий, в примере 3.22 использовались 5 переменных, описанных в разделе данных задачи. В этом случае к ним добавляется еще три, поэтому в разделе данных задачи должны быть описаны следующие переменные:

iwidth dw 0 количество точек в строке (ширина) рисунка
fwidth dw 0 количество байтов в строке файла
iheight dw 0 количество строк в рисунке (высота рисунка)
rmndr dw 0 добавка к адресу для пропуска "лишних" байтов
part dw 0 количество строк в считываемой порции данных
numbyte dw 0 количество байтов в считываемой порции данных
remline dw 0 количество строк, которые еще не обработаны

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

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

Текст подпрограммы приведен в примере 7.25. Перед ее вызовом должно быть установлено окно видеопамяти, в котором расположено начало рисунка. Его адрес указывается в регистре di, a es должен содержать код видеосегмента. Предполагается, что задача предварительно открыла файл для чтения и обработала его заголовок так, как это рекомендовано в приложении А, поэтому известны значения переменных iwidth и iheight и файл установлен на начало образа рисунка.

Напомним
Перед построением любого рисунка, тем более большого размера, надо удалить с экрана изображение курсора (call Hidepnt), а после построения рисунка восстановить его на экране (call Showpnt).

Пример 7.25. Построение полноцветного рисунка формата BMP

BigBmp : pusha сохранение "всех" регистров
PushReg <fs,Cur win> сохранение fs и Cur win
mov fs, SwpSeg fs = сегмент буфера обмена
mov SwpOffs, 0 очистка смещения в сегменте
mov dx, iwidth dx = количество точек в строке
neg dx dx = — iwidth
call calloffs bx = (horsize + iwidth) *bytppnt
mov ax, 03 ax = 3
mul iwidth ax = 3*iwidth, утроенный размер строки
mov dx , ax сохраняем его в dx
add ax, 03 с помощью. двух команд округляем
and al, OFCh размер строки до значения, кратного 4
mov f width, ax fwidth = размер строки в файле
sub ax, dx вычисляем добавку к адресу
mov rmndr, ax и сохраняем ее в rmndr
mov ax, -1 ах = 65535
mov numbyte, ax numbyte = 65535
xor dx, dx очистка старшей части делимого
div fwidth ах = 65535 / fwidth (частное от деления)
mov part, ax part = число строк в порции для чтения
sub numbyte, dx numbyte = numbyte — ах
mov ax, iheight ах = количество строк в рисунке
mov remline, ax remline — счетчик числа строк
dec ax ах = номер последней строки
mul bperline ах = (iheight — l)*bperline
add di, ax di = адрес последней строки рисунка
adc dx, 00 учитываем возможный перенос
mov ax, GrUnit ах = GrUnit (единица измерения окон)
mul dl вычисляем добавку к номеру окна
add Cur win, ax номер окна для последней строки
call Setwin установка окна
NewPart mov ex, numbyte сх = количество считываемых байтов
call Readf чтение порции в буфер обмена
jnc sucread -> чтение прошло без ошибок
/ Здесь должны выполнять ся действия в 'случае ошибки при чтении
sue re ad mov ex, part сх = кол-во строк в полной порции
cmp remline, ex считана полная порция данных ?
jae @F -> да, обходим следующую команду
mov ex, remline нет, сх = оставшееся число строк
@@: sub remline, ex уменьшаем значение счетчика строк
xor si, si si = начало буфера обмена
drwout : push ex сохраняем\ значение счетчика строк
mov ex, iwidth сх = размер строки- (в точках)
call drawline построение очередной строки
pop ex восстанавливаем счетчик строк
add s i , rmndr корректируем адрес в буфере обмена
sub di, bx di = адрес начала предыдущей строки
jnc @F -> адрес в пределах текущего окна
call PrevWin установка предыдущего окна
@@: loop drwout управление циклом построения строк
cir.p remline, 0 остались не обработанные строки ?
jne NewPart -> да, на чтение следующей порции
PopReg <Cur win, f s> восстановление Cur win и fs
popa восстановление "всех" регистров
call Setwin восстановление исходного окна
ret возврат из подпрограммы

Выполнение примера 7.25 начинается с сохранения в стеке содержимого "всех" регистров. Команда pusha не сохраняет сегментные регистры, поэтому содержимое fs и переменной cur_win сохраняет макровызов pushReg.

Для чтения данных вызывается подпрограмма Readf, текст которой приведен в примере 3.23. Она размещает данные в буфере обмена, начиная с адреса, указанного в swpoffs. Для максимального использования пространства буфера эта переменная предварительно очищается, а в регистр fs записывается сегмент буфера обмена из SwpSeg.

После этого выполняются подготовительные действия, смысл и назначение которых описаны выше. Три первые команды вычисляют константу для коррекции адресов строк, ее значение помещается в регистр bx и используется в основном цикле. Следующие восемь команд формируют значения переменных fwidth и rmndr. Затем шесть команд вычисляют значения переменных part и numbyte. Последние десять команд формируют адрес начала последней строки рисунка в видеопамяти и устанавливают окно, которому он принадлежит.

Основной цикл имеет метку NewPart. Он практически совпадает с одноименным циклом примера 7.24. Отличие заключается в следующем. При переадресации строк видеопамяти вычисляется адрес начала предыдущей строки, поэтому содержимое регистра bx вычитается из содержимого регистра di, а в случае переполнения результата вычитания устанавливается предыдущее окно видеопамяти. Кроме того, для исключения "лишних" точек адрес буфера обмена, хранящийся в регистре si, увеличивается на величину rmndr. Ну и, конечно же, имя drawiine соответствует разным подпрограммам.

Подпрограммы для построения строк

В отличие от основного текста примера 7.25, тексты подпрограмм построения строк существенно зависят от установленного задачей видеорежима. Это связано с необходимостью преобразования исходного формата bgr в формат Hi-color или True Color. Варианты подпрограмм для обоих режимов показаны в примере 7.26.

Пример 7.26. Варианты подпрограммы построения строки

; Вариант 1 для работы в режимах True Color
drawline: movs word ptr [di], f s: [si]; копируем коды синего и зеленого
lods byte ptr fs:[si] ; al = код красного цвета
xor ah, ah очистка резервного байта
stosw записываем старшее слово кода
or di, di адрес в пределах сегмента ?
jnz @F -> да, обход команды
call NxtWin установка следующего окна
@@: loop drawline управление повторами цикла
ret возврат из подпрограммы
; Вариант 2 для работы в 15-разрядных режимах Hi-Color
drawline: mov al, fs:[si+2] читаем код красного цвета в
al shr al, 03 сокращаем его до 5-ти разрядов
mov bh, fs:[si+1] читаем код зеленого цвета в
bh shld ax, bx, 05 добавляем в ах код зеленого цвета
mov bh, f s:[si] читаем код синего цвета в
bh shld ax, bx, 05 сдвигаем и дополняем код в
ах add si, 03 добавляем в ах код синего цвета
stosw записываем код в видеопамять
or di, di адрес в пределах сегмента ?
jnz @F -> да, обход команды
call NxtWin установка следующего окна
@@: loop drawline управление повторами цикла
ret возврат из подпрограммы

В

Рисунки, подготовленные в стандарте PCX

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

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

Новое поле заголовка.

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

В байте со смещением 41h указано количество цветов (битовых плоскостей), на которое разложена каждая строка образа рисунка. Если оно равно 3, то мы имеем дело с полноцветным рисунком. Никакого другого признака, указывающего на то, что рисунок полноцветный, не существует.

Напоминаем
В байте со смещением 2 хранится "ключ кодирования", значение которого может быть равно 0 или 1. В первом случае образ рисунка не упакован, а во втором упакован по способу RLE, который описан в разделе.

Способ распаковки строки. Перед упаковкой строка рисунка разлагается на три подстроки, размер каждой из которых равен размеру строки. Одна из них содержит коды красного базового цвета, другая — зеленого и третья — синего. Другими словами, производится разложение строки по компонентам базовых цветов. Затем каждая подстрока упаковывается по способу RLE, и результат записывается в файл. Если исходный рисунок состоял из к строк, содержащих трехбайтовые коды точек, то упакованный рисунок содержит зк групп однобайтовых кодов цвета. Зачем это делается, описано в следующем разделе, а здесь нас интересует способ распаковки.
Итак, в файле формата PCX, содержащем упакованный полноцветный рисунок, начиная с адреса 80h, записаны группы упакованных однобайтовых кодов базовых цветов в такой последовательности:

коды красного базового цвета первой строки рисунка
коды зеленого базового цвета первой строки рисунка
коды синего базового цвета первой сроки рисунка
коды красного базового цвета второй строки рисунка
коды зеленого базового цвета второй строки рисунка
и т. д. вплоть до коды синего базового цвета последней строки рисунка

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

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

Для того чтобы не усложнять подпрограммы построения строки, надо изменить порядок записи результатов распаковки так, чтобы базовые цвета одной точки оказались в трех подряд расположенных байтах оперативной памяти. Другими словами, подпрограмма распаковки должна восстанавливать формат rgb или bgr, по усмотрению программиста. Мы выберем формат bgr для того, чтобы при построении строки можно было использовать подпрограммы примера 7.26.

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

Текст подпрограммы распаковки строки рисунка с преобразованием в формат bgr показан в примере 7.27. Распакованная строка записывается в свободную часть буфера общего назначения, поэтому перед вызовом подпрограммы в регистр gs копируется содержимое переменной Genseg. Адрес в буфере общего назначения подпрограмма выбирает из переменной GenOffs. Для чтения байтов упакованной строки вызывается вспомогательная подпрограмма Nxt_sym (см. пример 3.25, раздел 3.3.3), которая помещает в регистр ai код очередного байта из буфера обмена и следит за тем, чтобы этот буфер не был пустым.

Пример 7.27. Распаковка строки и преобразование в формат bgr

Unpack: PushReg <ax, dx, ex, di> сохранение используемых регистров
mov di, GenOffs адрес начала строки в GenSeg
add di, 02 начинаем с записи красных цветов
mov ex, 03 количество цветов
Unpckl: PushReg <di,cx> сохранение содержимого di и сх
mov dx, f width логический размер строки
Unpck2 : call nxt sym возвращает в al — текущий код
rnov ex, 01 предполагаем одиночный символ
cmp al, OCOh одиночный символ ?
jbe Unpck3 => да, на запись символа
mov ex, ax пересылка ах в счетчик
and ex, 3Fh выделяем число повторов
call Nxt sym читаем повторяемый код
Unpck3 : sub dx, ex кол-во байтов до конца строки
Unpck4 : mov gs: [di] , al запись кода цвета
add di, 03 коррекция адреса
loop Unpck4 управление повторами записи

Подпрограмма примера 7.27 состоит из двух вложенных циклов, внешний имеет метку Unpckl, а внутренний unpck2.

Внутренний цикл отличается от аналогичного цикла unpioop примера 3.24 тем, что запись результатов распаковки выполняется в цикле, имеющем метку unpck4 и состоящем из трех команд. Он усложнен потому, что результаты распаковки записываются в память не подряд друг за другом, а с шагом в 3 байта.
Внешний цикл управляет трехкратным повторением внутреннего. Кроме того, он формирует в регистре di адрес для записи результатов так, чтобы строка соответствовала формату bgr.

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

Перед построением рисунка надо прочитать заголовок файла, проверить его соответствие стандарту PCX и наличие в нем полноцветного упакованного рисунка. Затем из полей заголовка выбираются значения переменных iheight, iwidth и fwidth. Если заголовок прочитан полностью (soh байтов), то файл установлен на начало образа рисунка.

Текст подпрограммы приведен в примере 7.28. Перед ее вызовом устанавливается окно видеопамяти, в котором расположено начало строящегося рисунка. Адрес начала рисунка указывается в регистре di, а регистр es должен содержать код видеосегмента (доешь). Кроме того, надо удалить с экрана изображение курсора (call Hidepnt) и восстановить его на экране (call Showpnt) после построения рисунка.

Пример 7.28. Построение упакованного рисунка формата PCX

or dx, dx обработана вся строка ?
jnz Unpck2 => нет, продолжение обработки
PopReg <cx,di> восстановление содержимого di и сх
dec di для записи зеленых или синих цветов
loop Unpckl управление внешним циклом
PopReg <di, ex, dx, ax> восстановление регистров
ret возврат из подпрограммы
I PackDrw : pusha сохранение "всех" регистров
PushReg <f s , gs , Cur win> ; сохранение fs, gs и Cur win
mov gs, GenSeg gs = сегмент общего назначения
mov f s , SwpSeg fs = сегмент буфера обмена
xor si, si адрес начала буфера обмена
mov SwpOffs, si адрес начала буфера обмена
mov incount, si incount = 0 — буфер обмена пуст
mov dx, iwidth dx = количество точек в строке рисунка
1 mov ex, iheight сх = количество строк в рисунке
call calloffs bx -- константа для коррекции адресов
make: push ex ; сохраняем счетчик повторов цикла
call Unpack распаковка очередной строки
PushReg <fs,si> сохраняем содержимое fs и si
Ifs si, dword ptr GenOffs; fs:si = адрес распакованной строки
raov ex, dx сх = количество точек в строке рисунка
call drawline построение очередной строки
add di , bx адрес начала следующей строки
jnc @F ; -> адрес в пределах текущего сегмента
call Nxtwin установка следующего окна
@@: PopReg <s i , f s , cx> восстанавливаем содержимое si, fs и сх
loop make управление повторами цикла
PopReg <Cur win,gs, fs> ; восстановление Cur win, gs и fs
popa восстановление "всех" регистров
call Setwin восстановление исходного окна
ret возврат из подпрограммы

Выполнение примера 7.28 начинается с сохранения в стеке содержимого всех регистров, а также сегментных регистров fs, gs и переменной cur_win. Затем в gs и fs записываются коды сегментов буферов GenSeg и SwpSeg и очищаются регистр si, переменные swpoffs и incount. В регистры dx и сх копируются размеры рисунка, и вызывается подпрограмма caiioffs для вычисления константы переадресации строк.
Основной цикл примера 7.28 имеет метку make. Для построения каждой строки в нем последовательно вызываются подпрограммы unpack и drawline. Перед вызовом drawline сохраняется исходное содержимое пары регистров fs:si, а в них помещается адрес распакованной строки. После возвращения из drawline, как обычно, корректируется адрес видеопамяти, восстанавливается содержимое регистров si, fs, сх и команда loop make управляет повторами цикла.

Перед возвратом на вызывающий модуль восстанавливаются все сохраненные в стеке величины и исходное окно видеопамяти. Завершает подпрограмму команда ret.
В зависимости от видеорежима в примере 7.28 используется один из вариантов подпрограмм drawline, описанных в примере 7.26.

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

Подпрограмма PackDrw позволяет строить рисунки, подготовленные с применением палитры цветов. Для распаковки таких рисунков в ней вызывается подпро!рамма unpack, описанная в примере 3.26, а вариант подпрограммы drawline выбирается в зависимости от установленного видеорежима.

При работе в режимах PPG для построения распакованной строки вызывается одна из подпрограмм drawline, описанных в разделе.

Если же задача работает в одном из режимов direct color, то перед построением рисунка хранящаяся в файле палитра преобразуется в таблицу цветов. В зависимости от видеорежима для этого выбирается одна из подпрограмм 7.17—7.20. Строку рисунка с перекодированием точек по таблице цветов строит подпрограмма примера 7.21.


Способы сжатия полноцветных рисунков

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

Сжатие по способу RLE

В стандартах PCX и BMP предусмотрены простые алгоритмы упаковки и распаковки данных, которые называются Run Length Encoding или RLE. Реализация способа RLE в этих стандартах различается в деталях, но суть остается неизменной. Она заключается в том, что группа повторяющихся кодов заменяется двумя байтами, первый из которых содержит количество повторов, а второй — повторяемый код. Чем больше размер группы одинаковых кодов, тем выше степень сжатия.

Способ RLE основан на анализе содержимого соседних байтов. У рисунков, подготовленных с применением палитры, в них находятся коды смежных точек. А у полноцветных рисунков в каждой тройке подряд расположенных байтов хранятся базовые цвета точек, и проверять их совпадение не имеет смысла. Коды базовых цветов совпадают только у точек, окрашенных в оттенки серого цвета. Из 16М возможных комбинаций на оттенки серого цвета приходится всего 256!

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

Эффективность способа RLE зависит от структуры сжимаемого рисунка. Она тем выше, чем чаще в нем встречаются подряд расположенные одноцветные точки. Оценить ее в общем случае невозможно, поэтому мы ограничимся примерами трех рисунков, характеристики которых приведены в табл. 7.5. Первый рисунок является значком, предназначенным для оформления рабочей области экрана, в нем использованы только оттенки серого цвета. Второй получен в результате сканирования цветной фотографии женского лица, в этом рисунке количество цветов не соответствует качеству изображения, на котором много пятен инородного происхождения. Третий рисунок взят из коллекции Corel PrintHouse, он подготовлен на высоком профессионачьном уровне. Это снимок берега моря, уходящего за горизонт, на переднем плане волны прибоя и несколько лодок. У этого рисунка количество цветов соответствует качеству изображения.

Таблица 7.5. Результат сжатия трех полноцветных рисунков

Содержание рисунка

Размер рисунка

Количество цветов

Размер файла в байтах

BMP PCX JPEG
Значок 99x40 251 12054 12656 1565
Фотография 184x246 22 158 135846 90302 7460

Ландшафт

736x497

41 682

1 097 430 1 079 973 138964

Табл. 7.5 иллюстрирует недостатки способа сжатия RLE и очевидное преимущество стандарта JPEG. Приведенные в ней данные получены в результате преобразования файлов из формата JPG в форматы BMP (не упакованный файл) и PCX (упаковка по способу RLE).

Из табл. 7.5 видно, что только в одном случае файл формата PCX оказался короче файла формата BMP примерно на 35%. Это произошло потому, что на фотографии женское лицо окружает довольно большое пространство одноцветного фона. В двух остальных случаях сжатие по способу RLE не дает ощутимых результатов. Причина кроется не только в недостатках способа сжатия, но и в самих рисунках.

Применение палитры ограничивало разнообразие кодов точек до 256, поэтому в рисунках неизбежно повторялись одноцветные точки и их комбинации. В этих условиях алгоритм LZW, реализованный в стандарте GIF (см. раздел), обеспечивал вполне удовлетворительные результаты.

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

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

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

Цветовое пространство YUV

Трехмерное пространство света, у которого яркость и цвет являются независимыми компонентами, обычно обозначается YUV или УСЬСГ, в англоязычной технической литературе его называют "luminance/chrominance color space". Точная расшифровка аббревиатуры YUV автору неизвестна. Входящие в нее буквы обозначают следующие величины: Y — светимость (luminance), и — синий цвет, v — красный цвет. Во втором варианте записи сь является сокращением слов chrominance-blue, а сr — chrominance-red. Обратите внимание, в данном случае при описании компонент английские слова "яркость" и "цвет" не употребляются, поскольку мы имеем дело с некими абстрактными или обобщенными величинами.

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

Таблица 7.6. Уравнения для преобразования пространства цветов

RGB->YUV YUV->RGB
Y = 0.299R + 0,5870 + 0,114В R = Y+ 1,134V

U = 0,434В - 0.146R - 0,2880

G = Y- 0,578V -0,396U

V = 0.617R - 0,5170 - 0,100В

В = Y + 2.045U

Простой визуальный анализ показывает, что наибольший вклад в значение компоненты Y вносит зеленый цвет, к которому наиболее чувствителен глаз человека. В значение компоненты и основной вклад вносит синий цвет, а компоненты v — красный. Точкам серого цвета, у которых R=G=B=k соответствуют Y=k,U=V=0. Таким образом, компонента Y соответствует яркости точек серого цвета.

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

Замечание
В группу прерывания int lOh входит функция, имеющая код lOlBh. Она преобразует цвета R,G,B в оттенки серого по формуле Y = 0,299R + 0,587G + 0,114В.

В описании стандарта VESA перечислено 8 кодов моделей видеопамяти. Как уже говорилось в главе 1 (см. раздел ), на практике используются только 4 из них. К числу названных, но не используемых относится и модель YUV. Причем отсутствуют даже намеки на ее возможные характеристики. Это объясняется тем, что в простых видеокартах она не применяется. Зато современные акселераторы, как правило, выполняют преобразования, описанные в табл. 7.6. Они применяются, например, в тех случаях, когда при построении трехмерных графических объектов светимость точек зависит от расстояния до источника света, а цвет при этом не изменяется.

При использовании способа JPEG преобразование RGB -> YUV предшествует сжатию графического изображения, а обратное преобразование выполняется после его распаковки. Покажем, что это дает на двух примерах.

Предположим, что рисунок содержит N точек и занимает в памяти зы байтов. Если в нем использованы только различные оттенки серого цвета, в пространстве YUV останется только одна компонента Y, а и и v будут содержать нули. То есть еще до сжатия занимаемое изображением пространство сокращается в три раза.

Другой пример, можно усреднить цвета четырех смежных точек, расположенных в квадрате размером 2x2 точки, оставив без изменения их светимость. При этом размеры компонент и и v сократятся в 4 раза, а пространство, занимаемое всем рисунком, сократится в 2 раза. В зависимости от степени различия усредняемых цветов, воспроизведенный рисунок будет несколько отличаться от оригинала. Если оригинал не содержал контрастных точек, то это различие окажется незаметным для человеческого глаза.

Таким образом, пространство цвета YUV позволяет решать те задачи, решение которых при работе с пространством RGB было невозможно.

Общая характеристика способа JPEG

JPEG — это аббревиатура от Joint Photographic Experts Group (объединенная группа экспертов по фотографии) и одновременно обозначение способа сжатия полноцветных рисунков. Для сжатия динамических изображений эта же группа разработала другой не менее распространенный стандарт, который называется MPEG.

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

Количество цветов, использованных в рисунке, сокращается при сжатии и восстанавливается при распаковке. Однако если сравнить результат распаковки и оригинал, то окажется, что коды большинства точек различаются. С формальной точки зрения после распаковки получается совершенно другое изображение. Поэтому имеет смысл говорить только о визуальном совпадении оригинала с результатом распаковки.
Об эффективности способа JPEG можно судить по результатам, приведенным в табл. 7.5. Качество воспроизведенного рисунка можно оценить только визуально, точных количественных критериев его оценки не существует. В технической документации принято говорить о сжатии с минимальными (minimum), малыми (low), средними (medium) и большими (high) потерями.

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

Таблица 7.7. Качество рисунка и размер файла

Величина потерь качества изображения

Размер файла в байтах

Исходный рисунок

2476101

Минимальные потери

244 998

Малые потери

73654

Средние потери

56196

Большие потери

43506

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

Способы сжатия в JPEG

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

Дополнительный способ заключается в простом усреднении значений компонент и и У. которое было описано выше. При этом можно выбрать усреднение цвета одной, 2- или 4-х смежных точек, значения компоненты v остаются без изменений. Выбор одной точки означает отказ от усреднения.

Для основного способа можно выбирать только степень потерь, о чем говорилось выше. Он заключается в том, что последовательно выбираются области рисунка размером 8x8 точек. Для каждой группы из 64 точек выполняется преобразование Фурье. Его назначение в том, чтобы отфильтровать (исключить) высокочастотную компоненту из исходных данных. Результаты преобразования квантуются и преобразуются в целые числа, которые кодируются по таблицам Хуффмана (D. A. Huffman) и записываются в выходной массив. После обработки всего изображения выходной массив упаковывается в файл одним из стандартных способов.

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

Основные потери качества изображения происходят при квантовании результатов преобразования Фурье. Чем больше коэффициент квантования, тем больше точек будет отброшено. Точные количественные оценки, как уже говорилось, невозможны.
Сжатие полноцветных рисунков с минимальными потерями качества изображения представляет большой практический интерес. В настоящее время разработано несколько модификаций описанной схемы вычислений, направленных на сокращение потерь при сжатии. Например, в редакции JPEG, выпущенной в конце 1995 года, рекомендуется квантование результатов Фурье анализа с переменным шагом, задаваемым в таблицах. Это позволяет сохранять более подробную информацию о наиболее важных частях сжимаемого рисунка.

Файлы формата JPG

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

Среди пользователей более популярен стандарт JFIF, разработанный фирмой C-Cubc Microsystems. Это один из основных стандартов для передачи графических данных в сети Internet. Файлы, соответствующие этому стандарту, имеют тип (расширение) JPG.
JFIF расшифровывается как JPEG File Interchange Format (формат для обмена файлами JPEG). В настоящее время наибольшее распространение получила версия JFIF 1.2, опубликованная в сентябре 1992 года. К сожалению, автору не известен перевод описания стандарта на русский язык. В случае необходимости вам придется использовать описание на английском языке, которое можно легко найти в сети Internet.

 

Наложение рисунков и спецэффекты

Видеорежимы direct color позволяют решать задачи, постановка которых при работе в режимах PPG была просто невозможна. К ним относится, например, получение различного рода спецэффектов, за счет манипуляций с цветами отдельных точек изображения. В данном разделе описаны способы получения некоторых спецэффектов, широко применяемых в трехмерной графике (ЗD-графика).

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

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

Спрайты (Sprites)

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

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

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

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

Фильтрация цвета (Chroma Keying)

Преобразование черного цвета в прозрачный является частным случаем фильтрации цвета, которая широко применяется в ЗD-графике. В новой рекламе технологии ммх, появившейся в связи с выпуском процессора Pentium 3, приводится пример построения изображения с фильтрацией зеленого цвета.

В отличие от построения спрайта при фильтрации решается более общая задача — исключение однородного фона независимо от его цвета. В обоих случаях исключаемый цвет не должен использоваться в, основной части рисунка, в противном случае в ней появятся точки с цветом точек подложки, на которую накладывается рисунок.
Для того чтобы цвет с заданным кодом оказался прозрачным, его надо просто не записывать в видеопамять. Это и делает приведенная в примере 7.29 подпрограмма, выполняющая построение строки формата bgr с фильтрацией цвета. Она отфильтровывает цвет, код которого указан в переменной bkgrcod. Эта 32-разрядная переменная должна быть описана в разделе данных задачи. Рабочий видеорежим True Color.

Перед вызовом входные параметры задаются так же, как для всех описанных подпрограмм построения строки (drawline). В регистрах fs:si указывается адрес начала строки в оперативной памяти, в di — адрес ее начала в видеопамяти, в сх — количество точек в строке. Как обычно, должно быть установлено окно видеопамяти, которому принадлежит адрес точки начала строки, а в регистре es должен находиться код видеосегмента.

Пример 7.29. Фильтрация цвета строки формата bgr, режим True Color

sprline: lods word ptr fs: [si] ; ax = коды синего и зеленого цвета
push ax ; сохраняем ах в стеке
lods byte ptr fs:[si] ; al = код красного цвета
xor ah, ah ; очищаем резервный байт
shl eax, 16 ; сдвиг ах в старшую половину еах
pop ax ; восстанавливаем ах из стека
cmp eax, bkgrcod это фильтруемый цвет ?
je Ысо! -> да, обходим запись нового кода
mov es:[di], eax нет, записываем код в видеопамять
blcol: add di, bytppnt корректируем адрес видеопамяти
jnz @F -> адрес в пределах окна
call NxtWin установка следующего окна
@@: loop splint управление повторами цикла
ret возврат из подпрограммы

В 1-м варианте примера 7.26 была приведена подпрограмма, выполняющая простое построение строки формата bgr. В отличие от нее, в данном случае код очередной точки строки не копируется в видеопамять, а помещается в регистр еах. Затем он сравнивается с кодом, указанным в переменной bkgrcod, и в случае совпадения не записывается в видеопамять, поэтому цвет соответствующей точки экрана не изменяется.

Описанную подпрограмму можно использовать при построении рисунков, хранящихся в форматах BMP (не упакованный) и PCX (упакованный). Для этого в примерах 7.25 и 7.28 достаточно просто заменить call drawiine на call spriine. Замена возможна потому, что в обоих случаях используется одна и та же подпрограмма drawiine.

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

Смешение цветов (alpha blending)

Наиболее общей формой наложения двух изображений является смещение их цветов с разными весовыми коэффициентами. На практике обычно используется так называемое "альфа-смешение", при котором вычисления выполняются по следующей формуле:

Z[i,j] = X[i,j] * alpha + Y[i,j] * (1 - alpha)

Здесь x и Y — смешиваемые изображения, a z — результат смешения. Формула применяется к каждому базовому цвету каждой точки смешиваемых изображений, поэтому i изменяется от 1 до N (N — количество точек в рисунке), a j — от 1 до 3 (по количеству базовых цветов). Допустимые значения alpha находятся в пределах от 0 до 1.

Очевидно, ЧТО при alpha = 0 Z[i,j] = Y[i,j], а при alpha = 1 Z[i,j] : = x[i, j], т. е. при граничных значениях alpha в результате смешения получается одно из двух изображений. В остальных случаях результат смешения будет зависеть как от значения alpha, так и от конкретных цветов точек. Вспомните табл. 7.6, при пропорциональном изменении значений базовых цветов точки одновременно изменяются ее яркость и цвет.

Предположим, что коды двух смешиваемых точек в формате rgb равны FF, 0, 0 и 0, FF, 0. т. е. одна из них окрашена в ярко-красный цвет, а другая — в ярко-зеленый. В зависимости от значения alpha цвет результата смешения будет изменяться от красного до зеленого, а его яркость сначала будет уменьшаться до 50%, а затем начнет возрастать до максимального значения. При aipha=0,5 получится чисто желтый цвет, но его яркость составит 50% от максимальной.

Особенности программной реализации

Прежде всего, давайте раскроем скобки в приведенной выше формуле и сгруппируем величины, умножаемые на alpha. В результате получится следующий вариант формулы:

Z[i,j] = (X[i,j3,- Y[i,j]} * alpha + Y[i,j]

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

Теперь о значениях alpha. Величины x[i,j], Y[i, j] и z[i, j] являются кодами базовых цветов точек изображений, поэтому их значения заключены в пределах от 0 до 255 (или от о до OFFh). Умножать такие величины на дроби неудобно, поэтому при программной реализации значения alpha задаются в виде целых чисел, изменяющихся в пределах от 0 до 255, а результат умножения делится на 256. Фактически деление на 256 не требуется, достаточно просто использовать старший байт произведения. При таком представлении граничными значениями aipha будут 0 и 255/256 = 0,9961, но с учетом реальной точности вычислений отличие верхнего предела от 1 не имеет существенного значения.

Давайте упростим задачу и рассмотрим частный случай, когда z = у и изображение z находится на экране, а коды его точек, соответственно, в видеопамяти. Это позволит нам рассмотреть простой вариант подпрограммы, которая вместо смешения изображений, хранящихся в двух файлах, выполняет наложение изображения, хранящегося в файле, на изображение, находящееся на экране. Текст подпрограммы, выполняющей наложение строки формата bgr на строку, расположенную в видеопамяти, приведен в примере 7.30, предполагается, что установлен видеорежим True Color.

Входные параметры задаются так же, как для всех описанных подпрограмм построения строки (drawline):

в регистрах fs.-si указывается адрес начала строки в оперативной памяти; в регистре di — адрес ее начата в видеопамяти; в регистре сх — количество точек в строке.

Как обычно, должно быть установлено окно видеопамяти, а регистр es должен содержать код видеосегмента.

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

Пример 7.30. Альфа-наложение строки формата Ьдг, режим True Color

alphamix: PushReg <bx,dx>; сохранение регистров bx, dx
mixpnt: push ex ; сохранение счетчика повторов
mov c.x, 03 ; для обработки трех базовых цветов
mixcol: mov Ы, es:[di] ; Ы = код базового цвета Y[i,j]
iods byte ptr fs:[si] ; al = код базового цвета X[i,j]
xor ah, ah преобразуем байт в слово
xor bh, bh преобразуем байт в слово
sub ax, bx X[i,j] = X[i,j]-Y[i,j]
imul alpha ax = (X [i, j ] -Y[i, j ]) * alpha; dx = 0
xchg al, ah al = ax/256
add al, Ы Z[i,j] = (X[i,j]-Y[i,j])*alpha/256+Y[i,j]
stosb запись Z[i,j] в видеопамять
loop mixcol управление повторами цикла
inc di пропуск резервного байта
jne @F -> адрес в пределах текущего окна
call NxtWin установка нового окна
@@: pop ex восстановление счетчика повторов
loop mixpnt управление повторами цикла
PopReg <dx,bx> восстановление регистров dx, bx
ret возврат из подпрограммы

Подпрограмму alphamix можно использовать при построении рисунков, хранящихся в форматах BMP (не упакованный) и PCX (упакованный). Для этого в примерах 7.25 и 7.28 достаточно просто заменить call drawiine на call alphamix. Замена возможна потому, что в обоих случаях используется одна и та же подпрограмма drawiine.

Основные действия в примере 7.30 выполняются во внутреннем цикле, имеющем метку mixcol, рассмотрим их более подробно. Две первые команды считывают в регистры ы и al коды смешиваемых базовых цветов. Перед вычитанием значения байты преобразуются в слова. Специальная команда CBW (convert byte to word) в данном случае не применима, т. к. она интерпретирует коды со значениями больше чем 127, как отрицательные числа. В примере 7.30 просто очищаются старшие байты регистров ах и bx, после чего выполняется вычитание (ах = ах - bx). Результат вычитания может быть как положительным, так и отрицательным числом, поэтому для его
умножения на значение переменной alpha используется специальная команда imui. В отличие от команды mul она интерпретирует операнды как числа со знаком.

Произведение находится в регистрах dx:ax, но поскольку модули значений операндов не превышают 255, dx будет просто очищен, а в ах будет находиться младшая часть произведения. Вместо деления результата на 256 выполняется перестановка байтов регистра ах (с таким же успехом его содержимое можно было сдвинуть на 8 разрядов вправо). Остается сложить содержимое регистров ai и ы и записать результат в видеопамять. Последняя команда loop mixcoi трижды повторяет выполнение цикла.
После выхода из внутреннего цикла адрес видеопамяти увеличивается на 1 для пропуска резервного байта в коде точки. Как обычно, проверяется принадлежность адреса установленному окну видеопамяти и, в случае необходимости, устанавливается следующее окно. Затем в регистр сх из стека выталкивается содержимое счетчика повторов, и команда loop mixpnt управляет повторами внешнего цикла.

Очевидно, что наложение рисунка будет выполняться дольше, чем его простое построение. Более точное представление о степени замедления дают результаты следующего эксперимента. Один и тот же рисунок формата BMP, размером 640x480 точек просто строился, или накладывался на изображение, уже имеющееся на экране. В обоих случаях основная подпрограмма соответствовала примеру 7.25. Компьютер был укомплектован процессором Pentium с тактовой частотой 100 МГц. Время, затрачиваемое на наложение, увеличилось, по сравнению со временем построения, примерно на 1,1 сек. Это свидетельствует о том, что альфа-наложение можно выполнять с помощью обычных команд, но лучше использовать операции ммх или функции акселератора.

Альфа-смешение — это один из популярных приемов используемых в 3D-графике для получения различных эффектов. Рассмотрим некоторые из них.

Наплыв изображения (Image Dissolve)

Если результат выводить на экран и многократно повторять смешение, увеличивая при каждом повторе значение alpha, то на фоне постепенно исчезающего исходного изображения будет все более четко проступать новое изображение. Или наоборот, если значение alpha постепенно уменьшается от 1 до 0, то одно из двух изображений будет исчезать (растворяться в другом изображении). Такие трюки довольно часто используются в оформительских целях не только в компьютерных приложениях, но и на телевидении.

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

Прежде всего, подпрограмма примера 7.30 должна выполнять смешение, а не наложение строк. Для этого первая команда цикла mixcoi (mov bl, es: [di]) заменяется на mov bl, gs: [si]. Однако такая замена возможна при условии, что смешиваемые изображения имеют одинаковый формат. В противном случае при выборке кодов точек нельзя будет использовать один и тот же индексный регистр gs.

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

Рисунок, подлежащий наложению, надо прочитать в сегмент оперативной памяти, указанный в регистре fs. Для создания эффекта наплыва организуется цикл последовательного смешения расположенных в памяти изображений с увеличением значения alpha от 0 до 255 с выбранным шагом. Для удаления построенного изображения можно использовать тот же цикл, предварительно обменяв содержимое сегментных регистров gs и fs.

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

Прозрачная поверхность (Transparent Surface)

В этом случае создается зрительный эффект, при котором наблюдаемый объект как бы отгорожен от наблюдателя бесцветной прозрачной перегородкой, например стеклом. Для получения эффекта производится наложение однородного фона белого цвета на наблюдаемый объект. При этом степень прозрачности перегородки тем больше, чем меньше значение alpha, если alpha = 1, то перегородка превратится в прямоугольник белого цвета.

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

Пример 7.31. Наложение прозрачной строки заданного цвета

Trnspl: PushReg <bx, dx> сохранение регистров bx, dx
Tline: push ex сохранение счетчика повторов
mov ex, 03 для обработки 3-х базовых цветов
lea si, Trcol si = адрес переменной Trcol
Tpnt: lodsb al = налагаемый базовый цвет (C[jl)
mov Ы, es:[di] bl = базовый цвет изображения (Y[i,j]'
xor ah, ah преобразуем байт в слово
xor bh, bh преобразуем байт в слово
sub ах, bx ax = C[j] - Y[i,j]
imul alpha ax = ax * alpha; dx = 0
xchg al, ah al = ax/256
add al, bl al = al + Y[i,j]
stosb запись результата в видеопамять
loop Tpnt трехкратное повторение цикла
inc di пропуск резервного байта
jne @F -> адрес в пределах текущего окна
call NxtWin установка следующего окна
@@: pop ex восстановление счетчика повторов
loop Tline управление циклом преобразования строки
PopReg <dx, bx> восстановление регистров dx, bx
ret возврат из подпрограммы

Все отличия подпрограммы примера 7.31 от примера 7.30 связаны с тем, что код прозрачного цвета выбирается не из файла, а из переменной Trcoi. При каждом повторении внешнего цикла адрес этой переменной помещается в регистр si для использования во внутреннем цикле. Trcoi должна быть описана в разделе данных задачи, ее размер 3 байта, в которых находятся компоненты налагаемого цвета (коды базовых цветов). Для совместимости с видеорежимом True Color их надо расположить в порядке bgr.

Следует заметить, что при работе только с белым цветом переменная Trcci не нужна, поскольку в этом случае коды трех базовых цветов совпадают и равны OFFh. Мы ввели переменную Trcoi специально для возможности задания любого прозрачного цвета.

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

Формулу для выполнения альфа-наложения тумана однородного цвета можно записать в следующем виде:

X[i,j] = Fc[j] * alpha[i] + X[i,j] * (1 - alpha[i])

Здесь FC — цвет тумана, ах— цвет изображения, затягиваемого туманом. Запись Fctj] означает, что каждая точка тумана окрашена в один и тот же цвет, это позволяет избавиться от файла, содержащего изображение тумана. В отличие от ранее приведенных формул, в данном случае у коэффициента alpha появился индекс 1, т. е. значений alpha должно быть столько, сколько точек в основном рисунке (который затуманивается). О том, как подготовить массив значений alpha, мы поговорим после.описания программной реализации формулы.

Если у значений alpha убрать индекс i, то получится формула для наложения прозрачного цвета, которая была реализована в подпрограмме примера 7.31. Следовательно, надо несколько изменить внешний цикл этой подпрограммы, добавив в него выборку значения alpha для каждой точки изображения, что и сделано в примере 7.32. Предполагается, что затягиваемое туманом изображение находится на экране.
Перед вызовом подпрограммы в регистре di указывается адрес начала строки рисунка в видеопамяти, а в fs:si — адрес начала строки коэффициентов alpha в буфере обмена. В сх задается количество точек в строке.

Пример 7.32. Наложение строки тумана заданного цвета

Foglin: PushReg <bx, dx> ; сохранение регистров bx, dx
f og_l : push ex ; сохранение счетчика повторов
lods byte ptr f s : [si] ; al = alpha [i]
mov byte ptr falpha, al ; falpha = al запоминаем alpha [i]
push si сохраняем содержимое si
lea si, fogcol si = адрес переменной fogcol
mov ex, 03 для обработки трех базовых цветов
f og_2 : lodsb al = базовый цвет тумана (F[j])
mov Ы, es: [di] Ы = базовый цвет точки (Y[i,j]>
xor ah, ah преобразуем байт в слово
xor bh, bh преобразуем байт в слово
sub ax, bx ах = F[j] — Y[i,j]
imul falpha ах = ах * alpha [i]; dx = 0
xchg al, ah al = ax/256
add al, Ы al = al + Y[i,j]
stosb сохраняем результат в видеопамяти
loop fog 2 трехкратное повторение цикла
inc di пропуск резервного байта
jne @F -> адрес в пределах текущего окна
call NxtWin установка следующего окна
@@: Pop si восстановление адреса в si
pop ex восстановление счетчика повторов
loop fog 1 управление повторами цикла
PopReg <dx, bx> восстановление регистров dx, bx
ret возврат из подпрограммы

Значения alpha изменяются от 0 до 255, для сокращения размера файла их лучше хранить в виде байтов, но при умножении коэффициент alpha должен иметь размер слова. Поэтому в примере 7.32 введена специальная переменная falpha, имеющая размер слова. Ее надо описать в разделе данных задачи, присвоив нулевое значение.
Во внешнем цикле примера 7.32, по сравнению с примером 7.31, появились три новые команды. Первая из них считывает очередное значение alpha [i] в регистр al (и увеличивает содержимое si на 1). Вторая копирует содержимое ai в младший байт переменной faipha, а третья сохраняет в стеке адрес, находящийся в регистре si. Таким образом, в результате выполнения этих трех команд получено очередное значение alpha и освобожден регистр si. Теперь в него можно записать адрес переменной, содержащей цвет тумана, для использования во внутреннем цикле, это же делалось и в примере 7.31.

После выхода из внутреннего цикла восстанавливается находившийся в si адрес, и при повторе внешнего цикла будет выбрано следующее значение alpha из буфера обмена.
Для использования описанной подпрограммы надо подготовить файл, содержащий значения коэффициентов alpha. От их расположения в файле зависит способ построения рисунка, поэтому первыми должны располагаться коэффициенты для его верхней, а последними — для нижней строки. В таком случае построение будет производиться в направлении слева направо и сверху вниз, т. е. по наиболее простой схеме. Способ чтения файла в буфер обмена зависит от количества значений alpha. Например, для затягивания туманом всего изображения при разрешении 640x480 точек файл будет содержать 307 200 байтов и его придется считывать по частям.

Следовательно, перед построением риcунка надо вычислить размер считываемой порции данных (количество строк и байтов в порции). Мы это делали неоднократно.
Вычисление значений alpha. Вам понадобится специальная программа, формирующая файл, содержащий значения alpha для каждой точки затуманиваемого изображения. Значения alpha зависят от расстояния (r[i]) между текущей точкой (i) и точкой принятой за начало отсчета (точкой наблюдения). В линейном двухмерном дискретном пространстве это расстояние вычисляется как квадратный корень из суммы квадратов приращений значений координат:

r[i] = sqrt { (x[i] - х[0])**2 + (y[i] - у[0])**2}

Здесь ** обозначают степень, sqrt — квадратный корень, х[0] и у [0] — значения координат в точке наблюдения, x[i] и y[i] — текущие значения координат.

В общем случае значения alpha описываются некоторой функцией (F), зависящей от расстояния, т.е. alpha [i] = F(r[i]). Независимо от природы функции F значения alpha равны нулю в точке наблюдения и увеличиваются по мере удаления от точки наблюдения, не превышая при этом значение 255 (которое принято за 1).

Советуем вам начать с простого случая, когда F является нормированной чинейной функцией, у которой за норму принято значение г в максимально /деленной точке, т. е. alpha [i] = <r[i] / max) *255. В. результате получится }>айл, содержащий значения коэффициентов для тумана, плотность которого возрастает пропорционально расстоянию. Эксперименты с файлом позволят вам оценить степень соответствия линейной модели ожидаемым результатам.

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

alpha[i] = exp (-k*r[i]> или alpha[i] = exp {-(k*r[i])**2)

Замечание
Программировать вычисление значений alpha на языке ассемблера вовсе не обязательно. Можно использовать любой удобный для вас язык. Главное, чтобы файл имел нужную структуру и содержание.

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

Общий случай смешения

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

Предположим, что изображение сада уже находится на экране и на него накладывается изображение окна. В таком случае нужен массив значений alpha для каждой точки изображения окна. Не прозрачным точкам, например рамам, подоконнику и т. п., должны соответствовать значения alpha=i, для полностью прозрачных точек, соответствующих открытой части окна, alpha=o, частично прозрачные точки (стекло) предварительно окрашиваются в белый цвет, а значение alpha для них подбирается в пределах от 0,25 до 0,5. Оставим в стороне вопрос о том, как составить массив значений alpha, предположим, что он существует.

Работать с двумя файлами не очень удобно, поэтому на практике используются комбинированные файлы, в которых коды базовых цветов каждой точки дополнены значениями alpha (от 0 до 255). Код точки при этом занимает не 24, а 32 разряда. В специальной литературе вы можете встретить выражение альфа-канал (Alpha Channel), которое обозначает некий источник кодов точек, в которых базовые цвета дополнены значениями alpha. Некоторые модели акселераторов имеют специальный альфа-буфер, для размещения рисунков с 32-разрядными кодами точек. При работе с обычными видеокартами прикладные пакеты (DirectX и Open GL) используют такие комбинированные файлы.

Для наложения строки комбинированного файла надо внести небольшие дополнения в подпрограмму Aiphamix, описанную в примере 7.30. В начале внешнего цикла перед командой, имеющей метку mixed, надо вставить следующие две команды:

mov al, es:[si+3] ; а!=значение альфа для данной точки
mov byte ptr alpha, al ; сохраняем его в переменной alpha

Кроме того,, после обработки кода точки во внешнем цикле, например перед командой inc di, надо вставить команду inc si для пропуска байта, содержащего значение alpha.
Мы не будем обсуждать способ создания комбинированного файла, только отметим, что основная задала заключается в вычислении массива значений коэффициентов alpha. А добавить эти значения к кодам точек образа рисунка не составит особого труда.

Заключение.
Данный раздел завершает основную часть книги. В нем описаны далеко не все спецэффекты, применяемые в современной компьютерной графике. Выбраны только те из них, способы получения которых можно отнести к основам компьютерной графики. При реализации более сложных спецэффектов используются специфические объекты трехмерной графики — треугольники, текстуры и пр. Описание работы с такими объектами выходит за рамки данной книги. Как говорил небезызвестный Козьма Прутков: "Нельзя объять необъятное".