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


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

         

Видеорежимы packed pixel graphics

Видеорежимы стандарта VESA различаются по разрешающей способности и размерам палитры цветов, которые можно одновременно изобразить на экране. В данной, а также в трех последующих главах изложен материал, относящийся, В первую очередь, К режимам packed pixel graphics (упакованная точечная графика), которые в дальнейшем будут сокращенно обозначаться как PPG. При работе в этих режимах код точки занимает один байт и является номером строки палитры, содержащей описание цвета. В палитре может быть описано только 256 цветов. Работа с цветом во многом отличается от построения графических объектов, поэтому ее описание вынесено в отдельную (следующую) главу.

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

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

Стандарт VESA допускает использование палитры, содержащей 16 цветов (EGA graphics), но мы не будем рассматривать такие режимы. Они описаны в многочисленных руководствах, где рассмотрены особенности всех режимов, соответствующих стандарту IBM, и приведены примеры программ.

Работа с отдельными точками

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


Команды для манипуляции с точками

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

Начиная с микропроцессора Intel 386, они могут манипулировать байтами, словами и двойными словами. Соответственно, при работе в режимах PPG одной командой можно обработать одну, две или четыре подряд расположенные на экране точки. Пересылка операндов является одним из наиболее частых действий при работе с видеопамятью. Ее выполняют команда mov и строковые команды movs, stos и lods. Напомним их основные свойства и различия.

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

Таблица 3.1. Пересылка из памяти в память

Пересылка байта
Пересылка слова
Пересылка двух слов
mov al, fs:[di]
mov ax, fs:[di]
mov eax, fs:[di]
mov gs:[si], al
mov gs:[si], ax
mov gs:[si], eax

При записи операндов команды mov можно использовать имена всех сегментных регистров: cs, ds, es, fs, gs и ss. Если сегментный регистр не указан явно, то подразумевается, что это ds. В сегментном регистре должно находиться конкретное значение сегмента обычной, расширенной или видеопамяти. Смещение (относительный адрес) байта, слова или двойного слова в этом сегменте, чаще всего, указывается в индексном регистре, имя которого заключается в квадратные скобки (это признак адреса). В табл. 3.1 при чтении из памяти полный адрес операнда задается в регистрах fs:di, а при записи в память — в регистрах gs:si.

Строковые инструкции lods, movs и stos отличаются от команды пересылки (mov) следующими особенностями:

имя инструкции может содержать дополнительную пятую букву — ь, w или d, указывающую количество пересылаемых байтов (1,2 или 4); если имя инструкции содержит пять букв, то операнды не указываются, их местонахождение определено по умолчанию и зависит от инструкции; после выполнения соответствующей пересылки содержимое индексных регистров, содержащих адреса операндов, увеличивается или уменьшается; увеличение или уменьшение адресов операндов зависит от состояния специального признака направления пересылки (direction flag); только перед строковой инструкцией может быть указана специальная команда rep, вызывающая ее многократное повторение.

Назначение инструкций пересылки, имена которых состоят из пяти букв, показано в табл. 3.2. Местонахождение операндов у них фиксировано, и изменить его нельзя. Один из операндов может находиться в регистре-аккумуляторе, а другой или оба — в оперативной памяти. Последняя буква имени инструкции (b, w или d) указывает, какой из этих регистров является аккумулятором — al, ах или еах.

Адрес операнда, находящегося в оперативной памяти, задается в одной из двух пар регистров — ds:si или es:di. Содержимое этой пары должно быть определено в задаче до выполнения инструкций пересылки.

Таблица 3.2. Назначение строковых операций

Размер операнда в байтах
Чтение памяти в аккумулятор accum = ds:[si]
Запись аккумулятора в память es:[di] = accum
Пересылка из памяти в память es:[di] = ds:[si]
1
lodsb
Stosb
movsb
2
lodsw
Stosw
movsw
4
lodsd
Stosd
movsd

Смена сегмента источника

Очевидным недостатком строковых операций ЭДяются фиксированные сегменты операндов источника и приемника, -егмент приемника менять нельзя, а вот сегмент источника можно изменить. Для этого у имени операции отбрасывается последняя буква (ь, w ли а) и явно описывается сегмент операнда источника. Имя регистра, содержащего адрес (смещение) операнда, в этом сегменте изменить нельзя, у
источника это регистры si или esi. Вот пример корректной записи строковых операций:

lods byte ptr gs:[si] ; загрузка байта al = gs:[si]
movs dword ptr es: [di], fs : [si] ; копирование двойного слова
stos word ptr es:[di] ; эквивалентна stosw es:[di] = ax

Из этого примера видно, что в записи операнда источника можно указать любой сегментный ре-гистр (в данном случае gs или fs), но в записи операнда приемника может быть указан только регистр es. Если вы укажете у приемника другое имя сегментного регистра, то Макроассемблер просто использует имя es, не выдавая сообщение об ошибке.

При отсутствии пятой буквы в имени инструкции Макроассемблер не может определить размер (тип) операнда исходя из текста программы. Поэтому обязательно используется оператор ptr, перед которым указывается размер операнда — byte, word или dword. При отсутствии явного описания типа операнда Макроассемблер выдаст сообщение об ошибке.

Следует отметить, что смену сегмента операнда источника допускают все строковые операции, а не только перечисленные в табл. 3.2.

Направление пересылки. После выполнения строковой операции адрес, находящийся в индексном регистре (или в двух регистрах), увеличивается или уменьшается на размер операнда (на 1, 2 или 4). В первом случае принято говорить о пересылке в прямом направлении, а во втором — в обратном.

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

Состояние седьмого разряда регистра флагов изменяют две специальные команды eld и std. Первая (eld) очищает разряд, разрешая тем самым пересылку в прямом направлении. Вторая (std), наоборот, устанавливает разряд, разрешая пересылку в обратном направлении. Обе команды не имеют операндов. Обычное состояние флага направления — очищенное.

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

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

Для управления циклом по счетчику предназначена специальная команда юор- Она имеет два параметра, но явно в команде указывается только один из них — метка, на которую передается управление (обычно это метка начала цикла). Неявно loop использует счетчик повторов, которым является содержимое регистра сх. При каждом выполнении команды содержимое сх уменьшается на 1, и если результат отличен от нуля, то управление передаётся на указанную в команде метку. В противном случае будет выполняться команда, расположенная после loop.

В примере 3.1 показан простой цикл очистки полного сегмента памяти, т. е. 65 536 байтов. Код очищаемого сегмента должен находиться в регистре ев. Для ускорения при каждом повторе очищаются 4 байта.

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

хог еах , еах очистка регистра еах
хог di, di di = 0, адрес начала сегмента
mov сх, 16384 сх = 16384, счетчик повторов
Ips : mov es : [di] , еах запись 4-х байтов в сегмент
add di, 04 di = di + 4 , коррекция адреса
loop Ips управление повторами цикла

В примере 3.1 цикл очистки имеет имя ips и состоит из трех команд. Первая из них очищает 4 байта памяти, вторая увеличивает хранящийся в регистре di адрес на 4, а третья повторяет цикл 16 384 раза.

Важнo
Команда loop может передать управление на метку, которая отстоит от нее не далее чем на 128 байтов. Если это условие нарушено, то при компиляции Макроассемблер выдаст сообщение об ошибке.

Для многократного повторения одной строковой операции предназначена команда rep, которую называют префиксом повторения. Так же, как команда loop, она использует счетчик повторов, хранящийся в регистре сх. В примере 3.2 показано применение строковой операции для очистки сегмента.

Пример 3.2. Микропрограммный цикл очистки сегмента памяти i

еах, еах ; очистка регистра еах
di, di ; di = 0, адрес начала сегмента
сх, 16384 ; сх = 16384, счетчик повторов
stosd ; очистка сегмента, указанного в ES

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

Инструкции rep и loop различаются способом работы со счетчиком повторов. Rep сначала проверяет содержимое регистра сх и, если оно отлично от нуля, выполняет строковую операцию и потом уменьшает содержимое сх на 1. Loop сначала уменьшает содержимое сх на 1 и в зависимости от результата повторяет или прекращает выполнение цикла. Это различие проявляется, если при входе в цикл регистр сх очищен. В таком случае указанная после rep операция не выполнится ни разу, в то время как команда loop будет повторять выполнение цикла 65 536 раз!

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

 

Окна видеопамяти

При работе в видеорежимах SVGA изображение, находящееся на экране монитора содержит большое количество точек. Оно зависит от разрешающей способности установленного режима и равно произведению количества точек в строке на количество строк. Например, при разрешении 640x480 на экране находится 307200 точек, а при разрешении 1600x1200— 1 920000 точек. Объем видеопамяти, необходимый для хранения содержимого экрана, также зависит от размера кода точки. В режимах PPG код точки занимает 1 байт, поэтому объем видеопамяти совпадает с количеством точек, находящихся на экране, в режимах direct color он в 2 или 4 раза больше. Следовательно, для работы с графическими объектами в режимах SVGA нужен доступ к большому пространству видеопамяти.

Сегментирование памяти

Независимо от конкретного назначения команды IBM PC всегда имеют доступ к ограниченному пространству адресов, предельный размер которого зависит от нескольких факторов, в том числе и от режима работы микропроцессора. Начиная с модели Intel 80386, микропроцессоры могут работать в реальном, виртуальном и защищенном режимах. В реальном и виртуальном режимах все пространство памяти делится на сегменты, предельный размер которых составляет 65 536 байтов. Указанные в командах адреса операндов всегда относятся к конкретному сегменту, и ни при каких условиях не могут выходить за его пределы, — это вызывает ава-, рийную ситуацию. Значение сегмента хранится в одном из сегментных регистров, который либо явно указывается в имени операнда, либо используется по умолчанию.

Доступ к сегментам

При работе на IBM PC в реальном режиме для доступа к пространству адресов, расположенному за пределами сегмента, используются два разных способа, выбор которых зависит от типа памяти.

Первый способ заключается в том, что в сегментный регистр записывается абсолютный адрес начала нужного сегмента. Специфической особенностью семейства IBM PC является то, что при работе с сегментами общий объем адресуемого пространства не может превышать один мегабайт. Следовательно, возможно использование только 16 сегментов предельного размера (16x65536 = 1048576 = 1 Мбайт). Поэтому прямое указание адреса начала сегмента применяется только при работе с младшей частью оперативной памяти (первые 640 Кбайт). Как это делается, описано в приложении Б данной книги.

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

Доступ к видеопамяти. Рассмотрим, как организуется работа со всем пространством видеопамяти. Для обращения к нему используется специальный сегмент, который принято называть видеосегментом. При работе в графических режимах он обычно имеет код АОООЬ, но лучше взять его точное значение из массива info (см. главу 2). Код видеосегмента является просто признаком обращения к видеокарте, а не к какому-либо другому устройству, и не является частью адреса видеопамяти.

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

Во внутреннем регистре видеоконтроллера хранится число, которое принято называть номером окна (или банка) видеопамяти. У современных видеокарт размер окна фиксирован и составляет 65 536 байтов, поэтому, зная объем видеопамяти, можно вычислить количество существующих окон. Одному мегабайту видеопамяти соответствует 16 окон, двум — 32 и т. д.

При каждой смене видеорежима регистр, содержащий номер окна, очищается, т. е. устанавливается нулевое окно видеопамяти. В дальнейшем текущий юмер окна зависит только от действий, выполняемых в задаче, которая мо-кет устанавливать его любое допустимое значение. При переключении окон надо изменять содержимое внутреннего регистра видеоконтроллера, поэтому Для выполнения таких действий предусмотрена специальная процедура BIOS. к Уже говорилось в главе 2, ее вызов через прерывание int 10h не существо замедляет переключение окон, и стандарт VBE рекомендует прямое э°ращение, минуя прерывание int 10h.

В примере 2.8 приведены тексты трех подпрограмм для установки заданного (setwin), следующего (Nxtwin) и предыдущего (prevwin) окна. Они работают c переменной Cur win, имеющей размер слова и содержащей номер теку-лего окна. В примере 2.11 показано, как зарезервировать эту переменную з разделе данных программы. Если задача составлена корректно, то при ее зыполнении значение cur_win должно совпадать с номером окна, храня-лимся в видеоконтроллере.

Закрашивание рабочей области экрана

Для иллюстрации работы с окнами эассмотрим подпрограмму, которая последовательно заполняет заданным «содом отображаемую на экране часть видеопамяти, в результате чего весь жран окрашивается в заданный цвет. Ее текст приведен в примере 3.3. Пе-эед обращением к подпрограмме в байтах регистра еах надо указать код выбранного вами цвета. Предположим, что синему цвету соответствует код 01 (см. табл. 4.2). В таком случае для вызова подпрограммы используются следующие две команды:

nov еах, OlOlOlOlh ; запись кода 01 в байты регистра еах ;all fillscr
; выполнение подпрограммы fillscr : продолжение текста основной программы

В тексте основной (вызывающей) программы должны быть описаны макроопределения PushReg и popReg (пример 2.12) и подпрограммы для работы с жнами (примере 2.8). Кроме того, до обращения к fillscr надо установить эдин из видеорежимов PPG и определить значения переменных Horsize, /ersize и vbuff. Способы описания и определения этих и других, часто используемых переменных, обсуждались в главе 2.

Пример 3.3. Закрашивание всего экрана заданным цветом

fillscr: PushReg <es,di,dx, ex, Cur win,eax>; сохранение в стеке
mov Cur win, 0 очистка переменной Cur win
call SetWin установка нулевого окна
mov es, Vbuff es <= значение видеосегмента
xor di, di 0 — исходный адрес видеопамяти
mov ax, Versize ах <= количество строк (Versize)
mul Horsize dx:ax = Versize Horsize
mov ex, dx ex = количество полных окон
mov dx, ax dx = размер последнего окна
pop eax восстановление содержимого еах
; Цикл записи в полностью заполняемые окна
:l_lp: push ex сохранение счетчика сегментов
mov ex, 16384 количество повторов для rep
rep stosd запись в полное окно
call NxtWin установка следующего окна
pop ex восстановление счетчика сегментов
loop fl Ip управление повторами цикла
l рсяолсги р/лс;/
Запись в частично заполненное окно, если оно существует
mov ex, dx rep stosb
f 1 out: pop Cur_win call SetWin PopReg <cx,dx,di,es>
ret
ex <= оставшееся число байтов запись в неполное окно
; Действия, связанные с завершением работы подпрограммы
восстановление Cur win восстановление исходного окна восстановление регистров возврат из подпрограммы

Первые пять команд примера 3.3 выполняют вспомогательные действия, а именно, сохранение в стеке тех величин, которые будут испорчены при выполнении подпрограммы, установку нулевого окна видеопамяти, запись в регистр es кода видеосегмента и очистку регистра di. В основной части подпрограммы полностью и частично заполненные окна обрабатываются по-разному. Поэтому надо предварительно вычислить количество полных окон, содержащихся в рабочей области памяти, и количество байтов в частично заполненном окне, если оно есть. В примере 3.3 эти величины вычисляются путем умножения значений переменных versize и Horsize.
Один из сомножителей команды умножения должен находиться в регистре ах, а второй указывается в самой команде. Старшая часть результата умножения находится в регистре dx, а младшая — в ах. Таким образом, произведение равно 65 536 [dx] + [ах], квадратные скобки обозначают содержимое указанных в них регистров. Другими словами, после умножения в регистре dx находится количество полных окон, а в регистре ах — количество байтов в частично заполненном окне.

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

Окрашивание точек полностью заполненных окон происходит в цикле, имеющем метку fi_ip, он повторяется столько раз, сколько полных окон содержит рабочая область экрана. Заполнение окна выполняет микропрограммный цикл rep stosd, для его ускорения строковая операция (stosd) записывает в видеопамять сразу четыре байта. Поэтому предварительно в регистре сх задается 16 384 повтора этой операции. После закрашивания всего сегмента происходит обращение к подпрограмме Nxtwin для установки следующего окна. Затем из стека восстанавливается содержимое регистра сх и команда loop управляет повторами никла.

После выхода из цикла f i_ip в регистр сх копируется из dx оставшееся количество байтов и выполняется микропрограммный цикл rep stosb.

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

Точки и их адреса

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

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

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

Мы сознательно описали упрощенную схему отображения содержимого видеопамяти на экран монитора. Фактически она может быть более сложной. поскольку стандарт VESA позволяет перемещать начало отображаемой области (display start) и изменять логический размер строки. Об этом говорилось в главе 1 при описании функций VBE. На практике упрощенная схема применяется наиболее часто, поэтому примем ее за основу.

При описанном способе отображения адрес точки равен ее порядковому номеру, умноженному на размер кода в байтах (на 1, 2, 3 или 4). В режимах PPG код точки занимает один байт, поэтому ее адрес в видеопамяти совпадает с порядковым номером.
Связь адресов с координатами. Порядковый номер точки (N) вычисляется по значениям координат, задаваемых в виде номеров строки (row) и столбца (column), на пересечении которых она расположена. Номера строк и столбцов начинаются с нуля. Поэтому для вычисления порядкового номера точки надо номер строки умножить на количество точек в строке (Horsize) и к произведению прибавить номер столбца:

N=row * Horsize + column

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

При умножении с помощью команды mul результат автоматически делится на две нужные нам части (вспомните пояснения к

Построение геометрических фигур

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


Прямые линии

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

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

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

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

; Вариант 1, используется команда пересылки
horline: mov es : [di] , al запись кода точки в видеобуфер
inc di увеличение адреса на 1
jne @F переход, если не нуль
call NxtWin установка следующего окна
@@: loop horline управление повторами цикла
ret возврат из подпрограммы

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

Различие между подпрограммами примера 3.6 состоит в том, что в одном случае для записи кода точки в видеопамять использована обычная команда пересылки, а в другом — строковая, которая сама увеличивает содержимое регистра di на 1. Поэтому в первом варианте адрес надо увеличивать, что и делает команда inc di, а во втором варианте просто проверяется его новое значение, это делает команда or di, di.
Как уже говорилось, при коррекции значение адреса может выйти за грани-ЦУ сегмента. В таком случае надо установить следующее окно. По мере записи кодов точек в видеопамять содержимое регистра di возрастает вплоть До значения 65 535 (код OFFFFh). Если к этой величине прибавить 1, то регистр di окажется очищенным, это и использовано в примере 3.6 в качестве признака необходимости смены окна. Если содержимое регистра di отлично т нуля, то команда jne @F обходит вызов процедуры NxtWin, а если равно нУлю, то она выполняется и происходит установка следующего окна.

В примере 3.6 впервые использованы локальные метки, поэтому опишем правила работы с ними. Все локальные метки имеют имя @@, после которого, как обычно, ставится двоеточие. В командах переходов или ветвлений вместо имени локальной метки применяются операторы @F или @в. Оператор @F (переход вперед) указывается, если локальная метка расположена ниже по тексту. Оператор @в (переход назад) применяется, если локальная метка расположена выше по тексту. Обнаружив один из этих операторов, Макроассемблер ищет в нужном направлении ближайшую локальную метку. Количество локальных меток в программе не ограничено, но их применение не должно затруднять визуальный анализ текста.

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

Рисование линии справа налево

Рассмотрим, как можно нарисовать на экране горизонтальную прямую линию в направлении справа налево. Два варианта подпрограмм приведены в примере 3.7, их вызов отличается от вызова подпрограмм примера 3.6 только тем, что исходное окно видеопамяти и адрес в регистре di соответствуют крайней правой точке прямой.

Пример 3.7. Рисование горизонтальной линии справа налево

; Вариант 1, используется команда пересылки. invline:mov es:[di], al запись кода точки в видеобуфер sub di, 01 уменьшение адреса на 1
jnc @F переход, если нет переноса
call PrevWin установка предыдущего окна
@@: loop invline управление повторами цикла
возврат из подпрограммы

Вариант 2, используется строковая операция. установка флага направления запись кода точки в видеобуфер начало нового сегмента ? -> нет
установка предыдущего окна управление повторами цикла очистка флага направления возврат из подпрограммы
invline: std
invlp: stosb
cmp di, -1 jne @F call PrevWin
@@: loop invlp
eld ret

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

Контроль текущего адреса выполняется по-разному. В первом варианте для этого проверяется состояние С-разряда регистра флагов после операции вычитания. При вычитании единицы из нуля он будет установлен, что приведет к вызову подпрограммы Prevwin. Во втором варианте вычитание выполняет строковая операция, не вырабатывающая признаки, поэтому проверяется код результата и если он равен ' — 1", то вызывается подпрограмма FrevWin.

Важно
В первом варианте примера 3.7 вместо команды sub di, 01 нельзя использовать dec di, поскольку последняя не вырабатывает признак переноса.

Подпрограммы примера 3.6 достаточно просты, но это не самый быстрый способ рисования горизонтальной линии. Если не происходит смена окна, то при записи кода каждой точки выполняются четыре команды. В первом варианте одна из них (jne @F), а во втором две (or di, di и jne @F) производят проверку текущих значений адреса.
Вероятность того, что при рисовании горизонтальной линии значение адреса выйдет за границу сегмента не превышает 1%. Например, при работе в режиме ioih точки только 4 из 480 строк расположены в двух окнах. Следовательно, примерно в 99% случаев проверка текущего адреса в процессе рисования не нужна, и выполняющие ее команды можно исключить из тела цикла записи точек. Сказанное не означает, что проверка не нужна вообще, просто она должна выполняться перед циклом рисования, а не в самом цикле.

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

Если из подпрограмм примера 3.6 исключить проверку адресов и установку следующего окна, то цикл записи в первом варианте подпрограммы будет состоять из трех команд, а во втором — из Двух (stosb и loop). Пару команд stosb и loop можно заменить одной командой rep stosb, т. е. использовать микропрограммный цикл, выполняющийся быстрее программного.

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

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

horline: push dx сохранение содержимого регистра dx
mov dx, di копирование адреса в регистр dx
add dx, ex сумма текущего адреса и количества точек
jc @F -> прямая расположена в двух окнах
xor dx, dx очистка регистра dx
@@: sub ex, dx количество точек в текущем окне
rep stosb рисуем всю прямую или ее начало
or di, di адрес в пределах текущего окна ?
jne @F -> да, линия нарисована полностью
call NxtWin установка следующего окна
mov ex, dx количество не нарисованных точек
rep stosb рисуем остаток линии
@@: pop dx восстановление содержимого dx
ret возврат из подпрограммы

Линия может размещаться в текущем окне полностью или частично. Для проверки этого в примере 3.8 текущий адрес копируется в регистр dx и к нему прибавляется размер линии. Если при этом не произошло переполнение, то линия полностью помещается в текущем окне и регистр dx надо очистить. Если при сложении произошло переполнение, то команда jc @F исключает очистку регистра dx, поскольку в нем находится количество точек остатка, который будет нарисован после смены окна. Команда sub ex, dx вычитает остаток (или 0) из общего числа точек и таким способом определяет количество повторов первого микропрограммного цикла. Следующая команда rep stosb рисует часть линии, расположенную в исходном окне видеопамяти.

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

Уважаемые читатели, попробуйте ответить на вопрос — почему в примере 3.8 перед установкой окна проверяется текущий адрес видеопамяти (or di, di), а не размер остатка строки (содержимое регистра dx)?

Давайте посчитаем, чего мы добились. Если прямая содержит N точек и полностью помещается в текущем окне, то при ее построении по подпрограммам примера 3.6 будет выполнено 4N команд, а при построении по подпрограмме 3.8 всего 10 команд (не считая ret). Одной из них является команда rep stosb, которая записывает N байтов в видеопамять. От нее зависит время, затрачиваемое на рисование линии. Можно считать, что мы сократили это время, по крайней мере, в 4 раза по сравнению с примером 3.6 и это вполне оправдывает увеличение размера подпрограммы примера 3.8.

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

Для записи точек парами вместо команды rep stosb надо использовать команду rep stosw, предварительно уменьшив содержимое регистра сх в два раза путем сдвига на 1 разряд вправо. Если в регистре сх находится нечетное число, то при таком сдвиге младшая единица кода попадет в С-разряд регистра флагов (признак переполнения). Следовательно, после сдвига надо проверить состояние С-разряда и записать дополнительную точку, если он установлен. Таким образом, для сокращения цикла записи в два раза в примере 3.8 каждую команду rep stosb надо заменить следующей группой команд (см. пример 3.9).

Пример 3.9. Замена команды rep stosb на rep stosw

shr ex, 01 ; уменьшаем количество точек в два раза
jnc @F ; -> обход следующей команды (stosb)
stosb ; запись дополнительной точки
@@: rep stosw ; основной цикл записи по два байта

Для записи кодов четырех точек при каждом обращении к видеопамяти нужно использовать команду rep stosd, предварительно уменьшив содержимое регистра сх в четыре раза. В тех случаях, когда содержимое сх не кратно четырем, надо дополнительно рисовать 1, 2 или 3 точки. Для упрощения выполняемых действий содержимое сх изменяется в два приема. Сначала оно уменьшается в два раза, и если получен признак переноса, то рисуется одна дополнительная точка. Затем оно повторно уменьшается в два раза и если опять получен признак переноса, то рисуются две дополнительные точки. После этого можно использовать команду rep stosd. Способ выполнения этих действий показан в примере 3.10.

Пример 3.10. Замена команды rep stosb на rep stosd

shr сx, 01 уменьшаем количество точек в два раза
jnc @F -> обход следующей команды (stosb)
stosb запись дополнительной точки
@@: shr ex, 01 уменьшаем количество точек в два раза
jnc @F -> обход следующей команды (stosw)
stosw запись двух дополнительных точек
@@: rep stosd основной цикл записи по четыре байта

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

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

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

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

Рисование гладких линий

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

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

Пример 3.11. Подпрограмма для рисования гладких линий

anyline:

         

Прямоугольники

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

Полоса заданного цвета

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

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

Пример 3.12. Закрашивание прямоугольной полосы

stripe: PushReg <di,cx,Cur win> ; сохранение di, ex и Cur win
fillbar: push ex ; сохранение текущего значения сх
mov ex, horsize ; задание размера строки
call horline ; вывод очередной строки
pop ex ; восстановление счетчика строк
loop fillbar ; управление выводом строк
PopReg <Cur win,cx,di> ; восстановление Cur win, ex и di
call SetWin ; восстановление исходного окна
ret ; возврат из подпрограммы

Выполнение подпрограммы примера 3.12 начинается с сохранения в стеке содержимого регистров di, сх и переменной cur_win. Закрашивание производится в цикле, имеющем метку fillbar. Регистр сх используется в этом цикле в качестве счетчика. Кроме того, в нем передается размер строки для подпрограммы horline. Поэтому в начале цикла содержимое сх сохраняется в стеке и восстанавливается после возвращения из horline. Благодаря этому команда loop fillbar работает с той величиной, которая была указана в регистре сх при обращении к подпрограмме stripe.

После завершения цикла fillbar восстанавливаются сохраненные в стеке величины и исходное окно видеопамяти, для чего вызывается подпрограмма setwin. Последняя команда выполняет возврат из подпрограммы.

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

Вычисление адресов строк

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

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

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

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

Закрашивание прямоугольной области. В примере 3.13 приведена подпрограмма, закрашивающая заданным цветом прямоугольную область произвольного размера. Перед ее вызовом адрес левого верхнего угла прямоугольника помещается в регистр di и устанавливается окно видеопамяти, которому принадлежит этот адрес. Ширина прямоугольника (количество точек в строке) помещается в регистр dx, а высота (количество строк или точек по вертикати) — в регистр сх. Задание кода цвета точек зависит от того, какой вариант подпрограммы horiine вы будете использовать. Horiine может записывать в видеопамять байты, слова или двойные слова (см.

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

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

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

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

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

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

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


Варианты построения строк

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

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

Построение строки слева направо

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

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

Пример 3.15. Построение строки 256-цветного рисунка

drawline:

         
drwlinl: push bx сохраняем содержимое bp
mov bx, ex bp = ex (количество точек в строке)
Ipdrwll: lods byte ptr fs: [si] читаем в al код очередного байта
mov ah, al копируем коды из al в ah
mov ex, 08 количество повторов цикла распаковки
outSpnt: xor al, al очищаем регистр al
shl ah, 01 сдвигаем ah на разряд влево
adc al, 00 прибавляем переполнение к al
stosb записываем код очередной точки
or di, di начало нового сегмента ?
jne
@F
;
-> нет, обход команды call NxtWin
call
NxtWin
;
установка следующего окна
@@:
dec
bx
bx = bx - 1
je
drlret
если bx = 0, то строка построена
loop
outSpnt
управление внутренним циклом
jmp
short Ipdrwll
-> на обработку следующего байта
drlret :
pop
bx
восстановление содержимого bx
ret
выход из подпрограммы

Подпрограмма примера 3.18 представляет собой два вложенных цикла. Имя внешнего цикла ipdrwii, а внутреннего — outSpnt.

Внешний цикл считывает очередной байт образа строки, копирует его в регистр ah и задает количество повторов внутреннего цикла.

Во внутреннем цикле производится распаковка очередной группы точек и запись их кодов в видеопамять. Распаковку выполняют три первые команды внутреннего цикла. Первая из них очищает регистр ai, вторая сдвигает содержимое регистра ah на разряд влево. При сдвиге старший разряд регистра ah переносится в С-разряд регистра флагов, поэтому если он содержал единицу, то вырабатывается признак переполнения. Третья команда (adc ai, оо) прибавляет содержимое С-разряда к регистру ai. В результате, в зависимости от кода очередной точки, в регистре ai окажется 0 или 1. Полученный код точки команда stosb записывает в видеопамять. Затем проверяется текущий адрес видеопамяти и при необходимости устанавливается следующее окно видеопамяти.

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

Чтение строки из видеопамяти.

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

В примере 3.19 приведена подпрограмма, выполняющая копирование строки из видеопамяти в оперативную память. При ее вызове адреса задаются
так же, как во всех предыдущих примерах, а именно, пара регистров es:di содержит адрес видеопамяти, а пара fs:si-- адрес оперативной памяти. Предварительно устанавливается окно, в котором расположено начало копируемой строки, а ее размер указывается в регистре сх.

Пример 3.19. Копирование строки из видеопамяти в ОЗУ

readlin: mov al, es:[di] ; чтение очередного байта видеопамяти
mov fs:[si], al ; запись кода точки в ОЗУ
inc si ; увеличение адреса ОЗУ
inc di ; увеличение адреса видеопамяти
jne @F ; -> адрес в пределах окна
call Nxtwin ; переход к следующему окну
@@: loop readlin ; управление внутренним циклом
ret ; выход из подпрограммы

В примере 3.19 использованы обычные команды пересылки, поэтому очередной байт сначала считывается из видеопамяти в регистр al, а затем содержимое ai копируется в оперативную память. После этого содержимое регистров si и di увеличивается на 1 и проверяется значение нового адреса видеопамяти. Если он окажется равным нулю, то выполняется команда call Nxtwin, в результате чего устанавливается следующее окно видеопамяти. Команда loop readlin повторяет выполнение цикла до тех пор, пока не будут скопированы все байты строки.

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

Улучшение цикла копирования

У операции movs фиксировано назначение индексных регистров di и si и сегментного регистра es. Поэтому для применения строковой операции надо изменить расположение адресов источника и приемника. Пара регистров fs:si должна содержать адрес видеопамяти, а пара es:di — адрес оперативной памяти, но для удобства лучше сохранить единообразный способ расположения адресов и временно изменять его в самой подпрограмме пересылки.

° примере 3.20 показано, как можно переставлять адреса источника и приемника в теле подпрограммы на время выполнения цикла пересылки. При обращении к подпрограмме этого примера регистры es:di, как обычно, Должны содержать адрес видеопамяти, а регистры fs:si — адрес оперативной памяти.

Пример 3.20. Копирование строки из видеопамяти в оперативную память

; Перестановка входных параметров
readlin: push fs ; сохраняем содержимое fs
pop es ; выталкиваем его в es
mov fs, Vbuff ; fs = Vbuff (код видеосегмента)
xchg di, si ; перестановка содержимого di и si
; Цикл копирования строки из видеопамяти в оперативную память
readlp: movs byte ptr [di], fs:[si]; копирование очередного байта
or si, si адрес в пределах сегмента 9
jne @F -> да, обход команды call NxtWin
call NxtWin установка следующего окна
@@: loop readlp управление повторами цикла
Восстановление исх здного расположения входных параметров
push es сохраняем содержимое es
pop fs сохраняем содержимое fs
mov es, Vbuff выталкиваем содержимое fs в es
xchg di, si перестановка содержимого di и si
ret ; возврат из подпрограммы

В примере 3.20 перед выполнением цикла пересылки содержимое регистров fs копируется в регистры es через стек, в fs помещается код видеосегмента (содержимое переменной vbuff) и переставляется содержимое индексных регистров di и si. Так получаются нужные адреса источника и приемника.

Цикл пересылки имеет метку readlp, он отличается от аналогичного цикла примера 3.15 только тем, что вместо команды or di, di используется or si, si, поэтому мы не будем повторять его описание. После пересылки восстанавливается исходное расположение входных параметров в сегментных и индексных регистрах и происходит выход из подпрограммы.

Что дает улучшение цикла

В примере 3.20 цикл readlp содержит на две команды меньше, чем цикл подпрограммы примера 3.19, т. е. количество вспомогательных команд сократилось на третью часть. На первый взгляд, это немного, но появилась возможность дальнейшего ускорения процесса копирования за счет использования микропрограммного цикла пересылки. Для этого применяется способ, показанный в примере 3.16, и варианты его дополнительного ускорения, описанные в пояснениях к этому примеру.

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

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


Воспроизведение не сжатых рисунков

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

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

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

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

Пример 3.21. Работа с прямоугольной областью небольшого размера

draw:
PushReg
<di, si, cx,b>
;,Cur win>; сохранение исходных величин
mov
bx, horsize
; копируем в bx значение horsize
sub
bx, dx
; и вычитаем из него ширину рисунка
drwout:
push
ex
сохраняем счетчик повторов
mov
ex, dx
задаем размер строки рисунка
call
drawline
! ! или call bp, пояснения в тексте
pop
ex
восстанавливаем счетчик повторов
add
di , bx
адрес начала следующей строки
jnc
@F
-> адрес в пределах сегмента
call
NxtWin
установка следующего окна
@@:
loop
drwout
управление повторами цикла
PopReg
<Cur win,bx,
cx,si,di>; восстановление исходных величин
call
setwin
восстановление исходного окна
ret
возврат из подпрограммы

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

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

Цикл построения рисунка имеет имя drwout. Он начинается с сохранения в стеке значения счетчика повторов (регистра сх) и записи в него размера строки. Затем происходит вызов подпрограммы drawiine для вывода на экран очередной строки рисунка. После возврата из подпрограммы восстанавливается сохраненное в стеке значение счетчика повторов и вычисляется адрес начала следующей строки. Если при сложении будет получен признак переполнения, то произойдет обращение к подпрограмме Nxtwin для установки следующего окна видеопамяти. Последняя команда цикла loop повторяет его выполнение до тех пор, пока не будут построены все строки рисунка.

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

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

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

Лучше всего его задавать в регистре Ьр, в виде адреса нужной подпрограммы, а в тексте примера 3.21 вместо команды call drawiine записать call bp, как это указано в комментарии. Если вспомогательные подпрограммы
включены в текст задачи, то для формирования адреса используется команда lea, например:

lea bp, drawline ; для рисования 256-цветных рисунков
lea bp, drwlin4 ; для рисования 16-цветных рисунков
lea bp, drwlinl ; для рисования двухцветных рисунков
lea bp, horline ; для закрашивания прямоугольной области

Таким образом, мы получили универсальную процедуру, позволяющую:

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

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

Особенности работы с большими рисунками

Большие рисунки не помешаются в одном сегменте оперативной памяти, и их приходится считывать и выводить на экран по частям. В этом случае при построении основное время затрачивается не на рисование строк, а на чтение данных из файла в оперативную память. Очевидно, что чем больше размер порции данных, считываемых за одно обращение к файлу, тем меньше повторных обращений к процедуре чтения и тем быстрее будет построен рисунок. Стандартные средства DOS, например функция 3Fh прерывания int 2ih, позволяют прочитать за одно обращение к диску от 1 до 65 535 байт. Однако считывать каждый раз по 65 535 байтов не рационально, и вот почему.

Размер считываемой порции

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

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

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

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


Воспроизведение сжатых рисунков

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

Заключительные замечания

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

Установка палитры

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

Изменение состояния курсора

Трудно представить графическую задачу, при выполнении которой не используется курсор — специальный рисунок, указывающий на экране текущее положение манипулятора "мышь". Обычно курсор перемещается так, что находящееся на экране изображение не портится. Если же вновь добавленный рисунок закроет изображение курсора, то при перемещении последнего на месте старой позиции возникнет прямоугольник, содержащий фрагмент изображения исходного фона, а не нового рисунка. Для исключения таких случаев изображение курсора удаляется с экрана перед построением нового рисунка, а затем вновь восстанавливается на экране.
Можно организовать анализ взаимного расположения курсора и добавляемого рисунка и удалять курсор только в тех случаях, когда рисунок закрывает часть его изображения. Однако проще удалить курсор на время выполнения любых изменений находящегося на экране изображения. Работа с курсором описана в главе 6.
I
Сохранение исходного фона. Для того чтобы рисунок можно было удалить с экрана или переместить на экране с одного места на другое, перед его построением надо сохранить содержимое тех адресов видеопамяти, в которые записываются коды точек строящегося рисунка. Иначе говоря, надо сохранить исходную картинку (исходный фон) на той части экрана, которую займет новый рисунок. В главе 5 (см. раздел) описаны подпрограммы для сохранения и восстановления исходного фона на месте информационных строк, содержащих различные текстовые сообщения.

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

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

Маскировка

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

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

Перемещение рисунков

Простейшим примером перемещаемого рисунка является изображение курсора. Работа с ним подробно описана в главе 6.

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

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

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

Идентификация находящихся на экране объектов

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

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

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

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