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

         

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



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

При работе на 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 должно совпадать с номером окна, храня-лимся в видеоконтроллере.



Характеристика стандарта GIF



Характеристика стандарта GIF

Проблема сжатия рисунков приобрела особую важность по мере развития сначала локальных, а затем и глобальных сетей ЭВМ. В июне 1987 года компания CompuServe Incorporated опубликовала описание стандарта GIF (Graphics interchange Format — формат графического обмена). Для сжатия рисунков в нем применяется модифицированный алгоритм LZW (Lempei-ziv-weich), используемый в распространенных архиваторах текстовых данных. На сегодняшний день это наилучший способ сжатия растровых рисунков, подготовленных с использованием палитры цветов.
Алгоритмы LZW и RLE различаются принципиально. Главный недостаток RLE заключается в самой идее подсчитывать число подряд расположенных совпадающих кодов. Способ LZW избавлен от этого недостатка. При сжатии запоминаются и используются все последовательности встречающихся в рисунке точек, независимо от совпадения или различия их цветов. В выходной файл записываются не байты, а цепочки битов переменной длины, коды которых соответствуют кодам отдельных точек или их комбинаций.

Предположим, что размер кодов точек рисунка равен к, а их значения изменяются от 0 до N-I, где N = 2):. Например, если в рисунке используются все 256 цветов, то к= 8, а N = 256. Первоначально для хранения цепочек отводится к+1 разряд. Если код цепочки меньше N, то это просто код одной точки. Коды N и N+I имеют специачьное назначение. Комбинации точек кодируются, начиная от значения ы+2. Какой комбинации соответствует тот или иной код цепочки (если он больше чем N+I) можно узнать только из таблицы цепочек.

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

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

Для сравнения приведем размеры файла, хранящегося в трех стандартах:



ciouds.bmp 307514 файл не упакован, 640x480 точек
ciouds.pcx 300527 файл упакован по способу RLE
ciouds.gif 159287 файл упакован по способу LZW

Файл ciouds.bmp (облака.bmp) выбран по двум причинам. Во-первых, это одна из стандартных заставок Windows 9X и вы можете увидеть хранящийся в нем рисунок. Во-вторых, этот рисунок трудно сжимаемый, и применение способа RLE не дает ощутимых результатов. Тем не менее стандарт GIF сокращает размер файла почти в два раза, причем это не лучший результат. Сжатие файла выполнено с помощью графического редактора PhotoFinish фирмы ZSoft. Этот редактор (как и любой другой) при упаковке не изменяет количество цветов. В рисунке "Облака" используется 65 цветов. Если пожертвовать одним из них, то размер кода точки сократится с 7-ми до 6-ти разрядов, а размер файла в формате GIF сократится почти в 4 раза. Однако для анализа и сокращения количества цветов нужна специальная программа.

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

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

 



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



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

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

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

Описание объектов обычно оформляется в виде таблицы или списка.



Использование CallWln



Использование CallWln

В примере 3.5. показано, как можно поместить точку белого цвета в центр экрана. Будем считать, что переменные Horsize и versize содержат количество точек на экране по горизонтали и вертикали, белый цвет имеет код огь, а регистр es содержит код сегмента видеобуфера.



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



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

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

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

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



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



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

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

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

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



Маскировка



Маскировка

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

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



Недостатки сжатия по способу RLE



Недостатки сжатия по способу RLE

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

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









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



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

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

SwpOf fs dw 0 адрес (смещение) в буфере обмена
SwpSeg dw 0 значение сегмента, содержащего буфер обмена
iwidth dw 0 ширина строки рисунка
iheight dw 0 количество строк в рисунке
numbyte dw 0 количество байтов в считываемой порции данных
part dw 0 количество строк в считываемой порции данных
remline dw 0 остающееся не выведенным количество строк

Переменные Swpoffs и Swpseg указывают полный адрес буфера обмена, в который считываются данные из файла. Как резервируется пространство оперативной памяти, описано в приложении Б данной книги. Значения переменных iwidth и iheight получаются при обработке заголовка файла, содержащего образ строящегося рисунка. Значения переменных numbyte, part и remline формирует сама подпрограмма BigDraw.
Текст подпрограммы, выполняющей построение рисунка произвольного размера, приведен в примере 3.22. Перед ее вызовом в регистре di надо указать адрес левой верхней точки рисунка в видеопамяти и установить окно, которому принадлежит этот адрес. Напоминаем, что регистр es должен содержать код видеосегмента (содержимое переменной vbuff).



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



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

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



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



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

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



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



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

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

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

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

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



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



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

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



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



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

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

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

Способы резервирования пространства в оперативной памяти описаны в приложении Б данной книги.

Текст подпрограммы распаковки строки приведен в примере 3.24.



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



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

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

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



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



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

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

 



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



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

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

Предварительно вы должны открыть файл и прочитать его заголовок для определения значений переменных iheight, iwidth и fwidth. I После чтения заголовка указатель файла содержит значение 80h, соответствующее началу образа рисунка.

Перед вызовом подпрограммы в регистре di указывается адресе левой верхней точки рисунка в видеопамяти и устанавливается соответствующее окно. Регистр es должен содержать код видеосегмента. В разделе данных задачи надо описать переменную incount, имеющую размер слова, HI в ней подпрограмма хранит количество символов, прочитанных в буфер обм/мена.



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



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

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

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

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

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

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

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

 




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



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

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

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



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



Пример 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 показано применение строковой операции для очистки сегмента.



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



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

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

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

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

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

 


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



Пример 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.

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

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

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

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

 


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



Пример 3.4. Вычисление и установка окна и адреса точки

CallWin: PushReg <dx, ax> сохранение регистров dx и ах
mov ax, horsize помещаем в ах размер строки
mul dx умножаем на номер строки
add ax, ex прибавляем номер столбца
adc dx, 00 учитываем возможность переполнения
mov di, ax копируем адрес в регистр di
mov ax, GrUnit единица приращения окна
mul dl умножаем на номер окна
add ax, Base win ! ! только при работе со страницами ! !
mov Cur win, ax копируем окно в Cur win
PopReg <ax, dx> восстанавливаем регистры
jmp SetWin установка окна и выход

Прежде чем рассматривать примеры использования этой подпрограммы, несколько слов об особенностях команды умножения (mul). При ее записи явно указывается только один операнд, второй выбирается из аккумулятора (ai, ax или еах), куда его надо предварительно поместить. Команда может умножать байты, простые или двойные слова, размер сомножителей определяется по размеру (типу) указанного в команде операнда. В зависимости от размера сомножителей произведение может содержать Гб, 32 или 64 разряда и соответственно находиться в регистре ах, в регистрах dx и ах, или в регистрах edx и еах. В примере 3.4 первая команда mul dx умножает слова, поэтому произведение расположено в регистрах dx и ах, а вторая (mul di) умножает байты, поэтому результат занимает только регистр ах.

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

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

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

Важно
При замене команды call командой jmp надо следить за тем, чтобы в верхушке стека находился адрес возврата на вызывающий модуль.

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



Вывод белой точки в центр экрана



Пример 3.5. Вывод белой точки в центр экрана

mov dx, versize ; количество точек по вертикали
shr dx, 01 ; уменьшаем в 2 раза
mov ex, horsize ; количество точек по горизонтали
shr сх, 01 ; уменьшаем в 2 раза
call CallWin ; устанавливаем окно и адрес
mov al, OFh ; помещаем в al код белого цвета
mov es :[di], al ; рисуем точку
; продолжение программы

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

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

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



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



Пример 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. Рисование горизонтальной линии справа налево

; Вариант 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.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).



Замена команды rep stosb на rep stosw



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

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

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



Замена команды rep stosb на rep stosd



Пример 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 для ускоренного рисования горизонтальной прямой в обратном направлении. Все, что надо при этом учесть, уже описано в данной главе.

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

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



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



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

anyline: mov es: [di] , al запись кода точки в видеобуфер
add di, bx коррекция текущего адреса
jnc @F -> адрес в пределах окна
call NxtWin установка следующего окна
@@: loop anyline управление повторами цикла
ret возврат из подпрограммы

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

Давайте разберемся, что именно можно нарисовать с помощью подпрограммы, 3.11. Обозначим константу переадресации, значение которой записывался в Регистр bх' буквой и и условимся, что она может быть только по-жптельным числом Вспомните табл. 3.3 — положительные приращения адресов смежных точек могут иметь четыре значения: i, Horsize, Horsize+i HorSize-i. Если в качестве константы k использовать эти значения, то соответственно будут нарисованы горизонтальная, вертикальная и две наклонные прямые. Последние являются диагоналями квадрата, сторона которого содержит количество точек, указанное в регистре сх.

Подпрограмма 3.11 позволяет рисовать пунктирные линии. Например, если
задавать k=2, k=2*Horsize, k=2*(Horsize+1) И k=2*(Horsize-1), TO будут нари-
сованы пунктирные линии, у которых расстояние между соседними точками равно 2. Однако такая возможность является побочным эффектом и не представляет особого интереса.

Если вам часто приходится рисовать вертикальные линии, то сделайте копию примера 3.11, замените в ней команду add di, bx на add di, Horsize и подберите подходящее имя подпрограммы (не забудьте указать его в команде loop).



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



Пример 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, то адрес начала следующей строки должна устанавливать подпрограмма.



Подпрограмма закрашивания прямоугольной области



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

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

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

Закрашивание прямоугольной области производится в цикле, имеющем метку filiar. Он отличается от аналогичного цикла filibar примера 3.12 тем, что после рисования каждой строки производится коррекция текущего адреса (команда add di, bx). Если при сложении не происходит переполнение результата, то новое значение адреса находится в пределах сегмента и команда jnc @F исключает вызов процедуры Nxtwin. В случае переполнения условный переход не выполняется и происходит установка следующего окна.

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

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

Будем считать, что опорной точкой является верхний левый угол контура, адрес которого известен. Если грани прямоугольника рисовать, например, в таком порядке — верхняя, правая, нижняя, левая, то вычислять адреса начала граней вообще не потребуется. Однако в таком случае понадобятся не две, а четыре подпрограммы. При рисовании верхней и правой граней адреса видеопамяти будут изменяться в естественном порядке в сторону их увеличения и можно использовать подпрограммы horiine и anyline. При рисовании же нижней и левой граней адреса видеопамяти будут изменяться в сторону их уменьшения и понадобятся еще две подпрограммы, рисующие линии в обратном направлении. Чтобы ограничиться двумя подпрограммами, изменим последовательность рисования граней.

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

В примере 3.14 приведена подпрограмма, рисующая сначала верхнюю и правую, а затем левую и нижнюю грани. Входные параметры для нее совпадают с параметрами подпрограммы примера 3.13.



Подпрограмма рисования контура прямоугольника



Пример 3.14. Подпрограмма рисования контура прямоугольника

round: PushReg <bx,Cur win,cx,di> ; сохранение исходного состояния
mov ex , dx ; ex = ширина прямоугольника
dec ex уменьшаем ширину на 1
call horline рисуем верхнюю грань
mov bx, horsize bx = horsize
pop ex восстанавливаем содержимое сх
push ex и сохраняем его в стеке
call anyline рисуем правую грань
PopReg <di,cx,Cur win> восстанавливаем исходное состояние
PushReg <Cur win,cx,di > и вновь запоминаем его
call SetWin устанавливаем исходное окно
dec ex уменьшаем высоту на 1
call anyline рисуем левую грань
mov ex , dx сх = ширина прямоугольника
call horline рисуем нижнюю грань
PopReg <di,cx,Cur win,bx > восстановление исходного состояния
call SetWin восстановление исходного окна
ret возврат из подпрограммы

Выполнение примера 3.14 начинается с сохранения в стеке переменной cur_win и регистров bx, сх и di. При вызове подпрограммы переменная Cur_win и регистр di задают адрес левого верхнего угла контура прямоугольника, а в регистре сх указывается высота прямоугольника (количество точек по вертикали).

При рисовании верхней грани ее размер сокращается на 1 точку, для того чтобы при возврате из подпрограммы horline в регистре di находился адрес первой точки правой грани. Правую грань рисует подпрограмма anyline, поэтому в регистр bx надо записать значение Horsize, а из стека восстановить и тут же снова сохранить в нем содержимое регистра сх. После возврата из подпрограммы anyline будут нарисованы верхняя и правая грани.

Теперь надо вернуться в левый верхний угол контура прямоугольника, восстановив исходное состояние, сохраненное в стеке, и заново сохранить его для использования при выходе их подпрограммы. Кроме того, восстанавливается исходное окно (команда call setwin), поскольку оно могло измениться при рисовании.

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

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

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

 


Построение строки 256цветного рисунка



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

drawline: movs byte ptr [di] , fs:[si] ; запись кода точки в видеобуфер
or di, di ; начало нового сегмента ?
jne @F ; -> нет, обход команды call NxtWin
call NxtWin ; установка следующего окна
@@: loop drawline ; управление повторами цикла
ret ; возврат из подпрограммы

Сравните второй вариант примера 3.6 и пример 3.15. Приведенные в них тексты различаются только первой командой. Вместо stosb, записывающей в видеопамять содержимое регистра ai, в данном случае используется строковая операция movs, копирующая в видеопамять байты оперативной памяти. Обратите внимание на то, что в записи строковой операции сегментный регистр приемника (es) указывать не обязательно, а сегментный регистр источника (fs) вы можете изменить по своему усмотрению.

Подпрограмма примера 3.15 выводит точки в порядке увеличения их адресов. В некоторых случаях может возникнуть необходимость выводить точки в обратном порядке (справа налево) в сторону уменьшения адресов. Как можно нарисовать линию в направлении справа налево, было показано во втором варианте примера 3.7. Если в этом примере команду lodsb заменить командой movs byte ptr [di], fs:[si], то подпрограмма будет строить строку рисунка в обратном направлении. Когда и зачем может понадобиться такая замена, мы обсудим при описании построения рисунков.



Ускоренное построение строки рисунка



Пример 3.16. Ускоренное построение строки рисунка

drawline: push dx сохранение содержимого регистра dx
mov dx, di копирование адреса в регистр dx
add dx, ex сумма текущего адреса и количества точек
jc @F -> прямая расположена в двух окнах
xor dx, dx очистка регистра dx
@@: sub ex, dx вычисляем количество точек в текущем окне
rep movs byte p :r [di], fs:[sil; строим строку или ее часть
or di, di адрес в пределах текущего окна ?
jne dhl out -> да, строка построена полностью
call NxtWin установка следующего окна
mov ex , dx количество не построенных точек
rep movs byte p :r [di] , fs:[si]; строим остаток строки
dhl out : pop dx восстановление содержимого dx
ret возврат из подпрограммы

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

stosb на movs byte ptr [di], fs:[si]
stosw на movs word ptr [di], fs:[si]
stosd на movs dword ptr [di], fs:[si]

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

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

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

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



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



Пример 3.17. Подпрограмма построения строки 16-цветного рисунка

rwlin4 : mov al, fs:[si] читаем в al код пары точек
shr al, 04 выделяем код старшей точки
stosb записываем его в видеопамять
or di, di начало нового сегмента ?
jne @F -> нет, обход команды call NxtWin
call NxtWin установка следующего окна
@: lods byte ptr fs [si] ; повторное чтение кода пары точек
dec ex сх = сх — 1
je dr4ret если сх = 0, то конец строки
and al, OFh выделяем код младшей точки
stosb записываем его в видеопамять
or di, di начало нового сегмента ?
jne @F -> нет, обход команды call NxtWin
call NxtWin установка следующего окна
@ : loop drwlin4 управление повторами цикла
r4ret: ret выход из подпрограммы

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

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

Последняя команда loop управляет повторами цикла, если строка содержит четное количество точек, то именно она завершит выполнение подпрограммы. При нечетном количестве точек в строке выполнение подпрограммы завершит команда je dr4ret.

Распаковка 2-цветных строк. Если при построении рисунка использовано только два цвета, например черный и белый, то код точки помещается в одном разряде и может принимать только два значения 0 и 1. В таких случаях для сокращения размеров файла в одном байте располагаются коды восьми точек. В старшем разряде байта находится код первой точки, а в младшем -последней, поэтому выделять коды точек надо начиная со старшего разряда. В зависимости от количества точек в строке последний байт может быть заполнен частично. Не следует считать, что двухцветные рисунки обязательно черно-белые — их цвета зависят от кодов, находящихся в прилагаемой к файлу палитре.

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

Пример 3.18. Подпрограмма построения строки 2-цветного рисунка

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, и варианты его дополнительного ускорения, описанные в пояснениях к этому примеру.

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

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

 




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



Пример 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.22. Построение рисунка произвольного размера

SwpOf fs dw 0 адрес (смещение) в буфере обмена
SwpSeg dw 0 значение сегмента, содержащего буфер обмена
iwidth dw 0 ширина строки рисунка
iheight dw 0 количество строк в рисунке
numbyte dw 0 количество байтов в считываемой порции данных
part dw 0 количество строк в считываемой порции данных
remline dw 0 остающееся не выведенным количество строк
jnc sucread -> чтение без ошибок
i Здесь должны выполняться действия при ошибке чтения
sucread: mov ex, part сх = стандартное количество строк
cmp remline, ex осталось меньше строк ?
jae @F -> нет, обходим команду пересылки
mov ex, remline сх = оставшееся число строк
@@: sub remline, ex уменьшаем оставшееся число строк
xor si, si адрес начала в буфере обмена
drwout : push ex сохраняем счетчик повторов
mov ex, iwidth задаем размер строки рисунка
call drawline построение очередной строки
pop ex восстанавливаем счетчик повторов
add di , bx адрес начала следующей строки
jnc @F -> адрес в пределах сегмента
call NxtWin установка следующего окна
@@: loop drwout управление повторами цикла
cmp remline, 0 все строки выведены ?
jne NewPart -> нет, продолжаем построение
PopReg <Cur win, fs> восстановление Cur win и fs
popa восстановление всех регистров
call setwin восстановление исходного окна
ret возврат из подпрограммы

Выполнение подпрограммы примера 3.22 начинается с подготовительных действий. Две первые команды сохраняют в стеке содержимое используемых регистров и значение переменной Cur_win. Затем в регистр fs помещается сегмент буфера обмена, а переменная Swpoffs очищается для расположения считываемых из файла данных с начала буфера обмена. Следующие восемь команд формируют значение переменных part, numbyte, и remline, последняя является счетчиком еще не выведенных строк, поэтому ее исходное содержимое равно высоте рисунка. Остается сформировать в регистре bx константу horsize — iwidth для коррекции адресов строк в видеопамяти.
Построение рисунка выполняют два вложенных цикла. Внешний имеет метку NewPart, а внутренний — drwout. Во внешнем цикле производится чтение из файла очередной порции данных, уточнение размера прочитанной порции (содержимого регистра сх), оставшегося количества строк (переменная remline) и очистка регистра si. После этого выполняется внутренний цикл, выводящий на экран прочитанную порцию строк.

Внутренний цикл полностью совпадает с аналогичным циклом примера 3.21, поэтому мы опустим его описание.

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

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



Чтение фрагмента файла в буфер обмена



Пример 3.23. Чтение фрагмента файла в буфер обмена

Readf: PushReg <bx,dx,ds> ; сохраняем в стеке bx и ds
mov bx, handler ; указываем заголовок файла
Ids dx, dword ptr SwpOffs; задаем адрес буфера для чтения
mov ax, 3FOOh ; код функции DOS "чтение файла"
int 21h ; обращение к DOS
PopReg <ds,dx,bx> ; восстанавливаем из стека bx и ds
ret ; возврат из подпрограммы

В примере 3.23 адрес буфера обмена выбирается из переменных SwpOffs и swpseg, описанных выше. Если при чтении данных возникнет ошибка, то после возврата из int 2in и из подпрограммы будет установлен С-разряд регистра флагов (признак переполнения). При успешном чтении DOS возвращает в регистре ах размер прочитанной порции данных в байтах. Если при чтении обнаружен конец файла, то эта величина может быть меньше указанной в регистре сх.

В примере 3.23 используется еще одна переменная handler, содержащая ссылку на файл (file handle). Эту величину формирует DOS при открытии файла по указанной спецификации и передает задаче в регистре ах. Ее надо сохранить, например в переменной handler и использовать при дальнейшей работе с файлом.

Способ открытия файла для чтения описан в приложении А в примере А. 1. Для получения более подробной информации на эту тему рекомендуем обратиться к электронной справочной системе Tech Help, или к любому руководству по программированию MS DOS.



Распаковка строки рисунка (способ RLE для PCX)



Пример 3.24. Распаковка строки рисунка (способ RLE для PCX)

Unpack: PushReg <ax,cx,dx,di,es> ; сохранение содержимого регистров
les di, Dword ptr GenOffs; смещение и сегмент буфера
mov dx, fwidth ; логический размер строки
Unploop: call nxt sym ; читаем в al следующий символ
mov ex, 01 ; количество повторяемых символов
cmp al, OCOh ; символ содержит счетчик повторов ?
jbe Unl ; -> нет, это одиночный символ
mov cl, al ; копируем содержимое al в cl
and cl, 3Fh ; и выделяем количество повторов
call nxt sym ; читаем в al — повторяемый символ
Unl: sub dx, ex ; уменьшаем остаток строки
rep stosb ; записываем символы в буфер строки
or dx, dx ; строка распакована полностью ?
jnz Unploop ; нет, продолжение распаковки
PopReg <es, di, dx, ex, ax> ; восстанавливаем регистры
ret ;-> возврат из подпрограммы

В примере 3.24 перед началом распаковки в стеке сохраняется содержимое используемых регистров. Затем команда les загружает в регистры es:di адрес для записи распакованной строки. Размер строки в байтах помещается в регистр dx, используемый в качестве счетчика распакованных символов. После этого выполняется цикл Unploop.

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

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

Подпрограмма Nxtjsym. Для получения кода очередного байта в примере 3.24 вызывается подпрограмма Nxtjsym, текст которой приведен в примере 3.25.



Чтение очередного символа из буфера обмена



Пример 3.25. Чтение очередного символа из буфера обмена

Nxt sym: cmp si, incount в буфере есть символы ?
jb @F -> да, можно читать очередной символ
push ex сохраняем содержимое сх
mov ex, -I указываем размер порции данных
call Readf читаем данные из файла
mov incount, ax сохраняем размер порции данных
xor si, si очищаем указатель адреса
pop ex восстанавливаем содержимое сх
@@: lods byte ptr fs : [si] чтение очередного байта
ret ; возврат из подпрограммы

Подпрограмма примера 3.25 сравнивает текущее значение указат еля адреса буфера обмена (содержимое регистра si) с переменной incount.-, значение которой соответствует размеру считанной из файла порции данных, т. е. количеству байтов, находящихся в буфере обмена.

Если в буфере достаточно данных, то происходит переход на локальную метку @@ Для чтения кода символа в регистр al, увеличения указат-теля адреса на 1 и выхода из подпрограммы.

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

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

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

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

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



Построение рисунка упакованного в стандарте PCX



Пример 3.26. Построение рисунка, упакованного в стандарте PCX

PackDrw PushReg <cx,si,di,C jr win>; сохранение используемых величин
xor si, si очистка регистра si
mov incount, si incount = 0
mov ex, iheight ex = количество строк в рисунке
nake: push ex сохраняем счетчик повторов
call Unpack распаковка очередной строки
PushReg <fs,si> сохранение содержимого fs и si
Ifs si, dword ptr GenOffs; fs:si = адрес распакованной строки
mov ex, iwidth сх = количество точек в строке
call drawline вывод строки рисунка на экран
PopReg <si, fs> восстановление содержимого fs и si
mov ax, horsize копируем в ах ширину экрана и
sub ax, iwidth вычитаем из нее ширину рисунка
add di, ax адрес начала следующей строки
jnc @F -> адрес в пределах видеосегмента
call Nxtwin установка следующего окна
s@: pop ex восстановление счетчика повторов
loop make управление циклом рисования
PopReg <Cur win,di, з!,сх>; восстановление из стека
call SetWin восстановление исходного окна
ret возврат из подпрограммы

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

Дикл построения рисунка имеет метку make. Его выполнение начинается с охранения в стеке содержимого регистра сх и вызова подпрограммы unpack. Чосле распаковки в стеке сохраняется содержимое регистров fs, si и соманда ifs загружает в них адрес начала распакованной строки. В регистре ;х указывается размер строки iwidth и вызывается подпрограмма drawline шя записи строки в видеопамять. Какой именно вариант этой подпрограммы вы будете использовать, не имеет значения.

После возврата из подпрограммы drawline восстанавливается содержимое >егистров si и fs, вычисляется константа для коррекции адреса строки в мдеопамяти (horsize - iwidth), которая прибавляется к текущему адресу (видеопамяти, находящемуся в регистре di. Если при сложении произойдет переполнение, то подпрограмма Nxtwin установит следующее окно видео-памяти.

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

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



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



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

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



Прямые линии



Прямые линии

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

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

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



Произвольные линии



Произвольные линии

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

Рассмотрим, какие действия выполняют подпрограммы, предназначенные для рисования линии, проходящей через две заданные точки. При ее вызове задаются координаты двух крайних точек линии X1,Y1 и X2,Y2. Выполнение подпрограмм начинается с анализа значений координат. Если окажется, что > Х2, то производится перестановка значений координат в памяти так, чтобы Х2 > X1. Затем начало координат переносится в точку XI,YI, что позволит работать с приращениями адресов относительно этой точки. При выполнении дальнейших действий учитываются следующие величины:

Dx = Х2 - XI;
Dy = Y2 - Y1

хли DX = 0 или Dy = 0, то задана вертикальная или горизонтальная линия, оторая строится обычным способом, описанным в данной главе. В противном случае выбирается способ построения линии.

Если DX > оу, то линия строится относительно оси х. Это значит, что при эеходе от точки к точке приращение значения координаты х равно 1, приращение координаты Y равно DY/DX, причем о < оу/сх < 1.

Если Dх < Dy, то линия строится относительно оси Y. Это значит, что при еходе от точки к точке приращение значения координаты Y равно 1, а вращение координаты х равно DX/DV, причем о < Dx/Dy < 1.

При DX = Dy линия гладкая, например, с углом наклона 45 градусов, способ ее построения выбирается разработчиком.

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

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

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

Описание алгоритмов рисования линий и примеры подпрограмм, правда, предназначенных для работы в видеорежимах VGA, вы найдете в книге. Ее оригинал (на английском языке) распространялся на компакт-дисках вместе с текстами подпрограмм.

 



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



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

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

 



Распаковка 16цветных строк



Распаковка 16-цветных строк

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

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



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



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

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

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



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



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

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

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



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



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

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



Рисунок не помещается на экране



Рисунок не помещается на экране

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

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

Обрезать рисунок можно и в процессе его построения. Если ширина рисунка превосходит Horsize, то при построении строк в видеопамять записывается ровно Horsize точек, а остатки отбрасываются. Если высота рисунка превосходит versize, то выводится ровно versize строк, а остальные отбрасываются. Изменить подпрограмму построения рисунка не сложно, вопрос в том, целесообразно ли это делать?

Более гибкий способ заключается в принудительном изменении размера сканируемой строки (scaniine). Эта величина задает количество точек, после вывода которых видеоконтроллер переходит на новую строку. После установки видеорежима scaniine и Horsize равны. В разделе описана функция BIOS 4F06h, которая позволяет установить нужное значение Scaniine, но не меньшее чем Horsize. Если Scaniine > Horsize, то в процессе отображения каждой строки видеоконтроллер выводит на экран Horsize точек, а остальные (scaniine — Horsize) пропускает.

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

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

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

 



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



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

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



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



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

Очевидным недостатком строковых операций ЭДяются фиксированные сегменты операндов источника и приемника, -егмент приемника менять нельзя, а вот сегмент источника можно изменить. Для этого у имени операции отбрасывается последняя буква (ь, 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 байта.



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



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

Смежные точки расположены на экране монитора рядом друг с другом. Если опорная точка не лежит на границе экрана, TO ее окружает 8 смежных точек. Их расположение показано в левой части табл. 3.3, где опорной является точка с номером 0. В правой части таблицы приведены приращения адресов видеопамяти смежных точек относительно адреса опорной точки. Для вычисления адреса смежной точки к опорному адресу прибавляется смещение, указанное в соответствующей ячейке таблицы. Буква ь обозначает переменную Horsize.



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



Таблица 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


Расположение и адреса смежных точек



Таблица 3.3. Расположение и адреса смежных точек

Расположение точек Приращения адресов
8 6 7 -1-h -h 1-h
5 0 1 -1 0 1
4 2 3 h-1 h h+1

Замечание 2
Замечание 2

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

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

 


состоит из строк одинакового



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

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

 


Техника распаковки строки



Техника распаковки строки

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

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

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

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

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



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



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

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

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

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

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

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

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

N=row * Horsize + column

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

При умножении с помощью команды mul результат автоматически делится на две нужные нам части (вспомните пояснения к примеру 3.3). Номер окна находится в регистре dx, а адрес — в регистре ах. Адрес не требует никакой коррекции, а номер окна надо умножить на единицу приращения значения окна (GrUnit), которая зависит от особенностей видеоконтроллера, способ ее определения описан в главе 2. Если задача работает со страницами видеопамяти, то к результату умножения номера окна на GrUnit прибавляется значение базового окна (значение переменной Base_win).
При работе в графических режимах VGA такие вычисления выполняют две специальные функции прерывания int 10h. Функция ось записывает, а ооь считывает код точки по заданным номерам страницы, строки и столбца. Однако для режимов SVGA эти функции непригодны, поскольку реализованный в них алгоритм рассчитан на расположение точек в пределах одного сегмента видеопамяти. Поэтому вам придется составить и включить в текст задачи собственную подпрограмму для вычисления адресов точек видеопамяти.



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



Ускорение цикла построения.

Для ускорения построения строки используется микропрограммный цикл пересылки, аналогичный описанному в примере 3.8. Мы просто перепишем этот пример, изменив в нем две строковые операции (пример 3.16). Входные параметры указываются так же, как для подпрограммы примера 3.15.



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



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

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

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



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



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

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



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



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

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

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



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



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

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

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

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

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

 



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



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

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

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

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

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



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



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

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

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

Стандарт создала фирма ZSoft разработчик графических редакторов PaintBrush, PhotoFinish и пр. Ему, как и многим другим стандартам, присущи некоторые разночтения, вызванные тем, что улучшать устаревающие версии пыталась не только ZSoft, но и некоторые другие фирмы, например Genius Microprogramming.

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

Байт 0 должен содержать код ОАЬ, являющийся признаком того, что файл соответствует стандарту PCX.

Байт 1 содержит версию стандарта (от 0 до 5), в частности код 5 соответствует третьей версии стандарта, в которой впервые было введено использование 256-цветной палитры.

Байт 2 содержит 1, если образ рисунка хранится в сжатом виде, или 0 -в противном случае (распаковка не требуется).

Байт 3 содержит размер точки изображения в битах, для 256-цветных рисунков его значение равно 8.

Слова 4, 6, 8 и OAh содержат минимальные и максимальные значения координат рисунка (xmin, Ymin, xmax, Ymax). Ширина и высота рисунка вычисляются так: iwidth = Xmax - Xmin + 1, iheight = Ymax - Ymin + 1.

Слово 42h содержит размер строки рисунка в байтах, мы обозначим его содержимое fwidth. При четном количестве точек в строке iwidth = fwidth, при нечетном количестве точек в строке fwidth = iwidth + 1. В этом случае строка содержит дополнительный байт, который учитывается при распаковке, но не выводится на экран, т. к. его содержимое не определено.

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



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



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

В разделе 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 точек. По существу это единственное ограничение описанной процедуры. Вы можете составить подпрограммы для более сложных манипуляций со строками небольших рисунков. При этом должно соблюдаться только одно требование — по расположению параметров в регистрах они должны быть совместимы с описанными выше.



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



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

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

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

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

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

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



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



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

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



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



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

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

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

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



Исходная цветовая палитра



Исходная цветовая палитра

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

После включения пли перезагрузки компьютера в DAC-регистры записывается палитра цветов, хранящаяся в BIOS. Ее структура не зависит от установленного видеорежима, но в зависимости от установленного режима прикладным задачам доступны только первые 16 или все 256 цветов. В последнем случае принято говорить о стандартной палитре VGA.



Общими характеристика группы функций 10h



Общими характеристика группы функций 10h

В состав прерывания B10S int 10h входит группа функций с названием set Palette Registers (установка регистггров палитры), выполняющих разнообразные действия, связанные с обслухгживанием внутренних регистров видеокарты. Набор функций, обра-зующи их эту группу, вместе с перечнем входных и выходных параметров был разраОботан фирмой IBM для стандарта VGA и с тех пор не изменялся. Появление стандарта VESA не добавило ничего нового в способы работы с DAС-peгистрами, поскольку в этом не было необходимости.

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

В англоязычной документации на BI0S, DAC-регистры называют еще регистрами цвета (color registers). Такое название вполне соответствует их назначению, и мы будем его использовать в дальнейшем. Кроме них в составе видеооконтроллера существуют регистры палитры (palette registers). Они не нос пользуются при работе в режимах VESA и не имеют никакого отношения и к тем палитрам, о которых идет речь в настоящей книге. Их назначение описано ниже в данной главе.

Для запроса конкретной функции код группы (10h) помещается в регистр ah, а код запрашиваемой функции — в al. Расположение входных и выходных параметров в регистрах зависит от конкретной функции. B10S не проверяет допустимость значений параметров. О корректности запроса должен заботиться программист. Вызов функций B10S, как уже было сказано, выполняет прерывание int10h. Обращаем внимание на то, что совпадение кодов группы и вектора прерывания является случайным.









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



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

При выполнении графической задачи может возникнуть необходимость изменить уже установленную палитру, а спустя некоторое время восстановить ее. Чаше всего это делается при полном изменении находящейся на экране картинки, или при переходе к другой странице видеопамяти. В таких случаях перед изменением текущей палитры содержимое всех 256-ти регистров цвета надо сохранить в оперативной памяти. Для сохранения текущей палитры в памяти необходимо выделить пространство (буфер) размером в 3x256 = 768 байтов. Где именно будет расположен этот буфер, не имеет значения, но для возможности его использования в сегменте данных задачи надо выделить два слова и поместить в них смещение (адрес в сегменте) и значение сегмента, содержащего буфер.

Зарезервировать эти два слова можно, например, так:

BuffPal dw 0 ; для указания смещения буфера от начала сегмента
dw 0 ; для указания значения сегмента, содержащего буфер

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

В



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



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

Code SEGMENT начало сегмента "code"
ASSUME CS:code связь регистра CS с сегментом "code"
start: mov ax, 13h код установки видеорежима 13h int lOh установка видеорежима
outscr: mov ax, OAOOOh AOOO — сегмент видеопамяти
mov es, ax пишем его в регистр ES
xor di, di 0 — начальный адрес видеопамяти
mov ex, 200 количество строк на экране
1р_1: push ex сохраняем счетчик повторов
mov ex, 320 указываем размер строки
xor al, al код первой точки (0)
1р_2: stosb рисуем точку
inc al увеличиваем код точки на 1
loop lp_2 управление выводом строки
pop ex восстанавливаем счетчик строк
loop iP..1 управление выводом строк
mov ax, OCOlh код функции ожидания ввода
int 21h DOS ждет нажатия на клавишу
mov ax, 03 код установки видеорежима 3
int lOh установка видеорежима
mov ax, 4COOh код функции завершения задачи
int 21h DOS завершает выполнение задачи
ends конец сегмента "code"
end start конец текста программы
code

Суть выполняемых в программе действий заключается в следующем. На экран последовательно выводится 200 строк. При выводе каждой строки в видеопамять последовательно записывается содержимое регистра ai, которое в исходе равно 0 и после вывода каждой точки увеличивается на 1. Может показаться, что ai изменяется от 0 до 319, но это не так. Регистр al содержит восемь разрядов, поэтому его содержимое будет монотонно нарастать от 0 до 255, на 256-м шаге оно окажется равным нулю, затем будет снова нарастать и в конце строки достигнет значения 63. Все строки заполнены одинаково, поэтому при выполнении программы на экране возникнут разноцветные полосы ("занавес"), каждая из них показывает, какой цвет закодирован в конкретном оде-регистре. Прежде чем обсуждать получаемую картину, завершим описание программы.

Две первые и две последние строчки программы содержат информацию, относящуюся к ее оформлению. Точка входа в программу имеет метку start. Выполнение программы начинается с установки видеорежима VGA IBM, его код 13h, разрешение составляет 320x200 точек, размер палитры 256 цветов.

Далее в регистр es записывается код сегмента видеобуфера АОООЬ. Прямая запись значений в сегментные регистры невозможна, поэтому используется регистр-посредник ах. В регистре di устанавливается нулевой адрес, соответствующий началу строки. Пара регистров es:di выбрана для того, чтобы записывать коды точек командой stosb.
На экране переход с одной строки на другую выполняет видеоконтроллер при достижении конца очередной строки. Программа же просто выводит в цикле 1р_2 количество точек, совпадающее с размером строки для данного режима. Измените 320 на 319 или 321 и картинка "рассыпается", поскольку начало нового цикла вывода не будет совпадать с началом строки на экране.

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

Построенная задача выведет на экран интересующие нас цвета при условии, что палитра установлена. Дело в том, что загрузку палитры при смене режимов работы видеокарты можно запретить, записав 1 в третий разряд слова с адресом оооо:С48Э из области данных BIOS. Обычно этот разряд очищен, и палитра загружается при любых изменениях режимов (как текстовых, так и графических). Одна из функций прерывания int юъ, относящихся к группе i2h, предназначена для разрешения или запрещения загрузки палитры. При ее вызове в регистре ы указывается код 3ih, а в регистре ai — 0 или 1.

Устанавливаемая DOS палитра в книге описана примерно так (это не цитата, а скорее вольный перевод оригинала). Первые 16 DAC-регистров содержат палитру для режима CGA, в следующих 16-ти регистрах записаны коды разных оттенков серого цвета. Затем располагаются три основные группы, занимающие по 72 регистра и содержащие коды цветов высокой, средней и низкой интенсивности. Каждая группа делится на 3 одинаковых подгруппы, содержащие коды цветов высокого, среднего и низкого насыщения. Последние восемь регистров просто очищены, им соответствует черная полоса. Тут автор книги допустил неточность, — фактически при установке палитры последние 8 регистров не заполняются. После включения компьютера они очищаются, но их содержимое могут изменить программы, работающие в графических режимах. Поэтому вместо черной полосы, соответствующей последним восьми линиям, вы можете увидеть другой цвет.

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



Установка содержимого последнего регистра цвета



Пример 4.2. Установка содержимого последнего регистра цвета

mov dh, 3Fh интенсивность красного цвет а
mov ch, 3F'h интенсивность зеленого цвет а
mov cl, 3Fh интенсивность синего цвета
mov bx, 255 номер регистра цвета
mov ax, lOlOh код запрашиваемого действия
int ICh выполнение запроса



Сохранение текущей палитры в буфере



Пример 4.3. Сохранение текущей палитры в буфере

SavePa 1 : pusha сохранение "всех" регистров
push es сохранение содержимого es
les dx, dword pt г Buff Pal; сегмент и смещение буфера
xor bx, bx номер первого регистра цвета
mov ex, 256 количество сохраняемых регистров
mov ax, 1017h код запрашиваемой функции
int lOh обращение к функции B10S
pop es восстановление содержимого es
popa восстановление "всех" регистров
ret возврат из подпрограммы



Восстановление исходной палитры из буфера



Пример 4.4. Восстановление исходной палитры из буфера

RstPal:pusha сохранение регистров
push es сохранение содержимого es
les dx, dword pt г Buff Pal; сегмент и смещение буфера
xor bx, bx номер первого регистра цвета
rnov ex , 256 количество записываемых регистров
mov ax, 1012h код запрашиваемой функции.
int lOh обращение к B10S
pop es восстановление содержимого es
popa восстановление "всех" регистров
ret возврат из подпрограммы

Тексты примеров 4.3 и 4.4 не требуют особых пояснений, напомним только, что команда les копирует содержимое первого слова переменной BuffPal н регистр dx. а второго слова — в регистр es. Тексты примеров различаются только кодом запроса, помещаемым в регистр ах командой mov. При желании, для сохранения или восстановления палитры можно использовать только одну подпрограмму. Из ее текста надо исключить указанную команду mov, а код запроса (ioi2h или Ю17п) задавать в регистре ах перед вызовом подпрограммы.

Что такое "регистры палитры"

В заключение несколько замечаний о назначении регистров палитры. С ними можно работать только в режимах EGA. VGA и в 16-цветных режимах VESA, а в режимах PPG они не доступны. Если вас не интересуют особенности работы в перечисленных режимах, то можно пропустить оставшуюся часть данного раздела.

Появлению стандарта EGA сопутствовала разработка мониторов, которые позволяли выводить на экран 64 цвета. Однако в стандарте EGA код точки 4-разрядный и, соответственно, доступны только 16 регистров цвета. Для более полного использования возможностей EGA-мониторов количество регистров цвета на видеокартах было увеличено до 64-х. Одновременно работать со всеми регистрами было, по-прежнему, невозможно. Поэтому они делились на четыре одинаковые группы и были введены четыре специальных регистра палитры. Хранящееся в них число (от 0 до 3) указывает помер группы из 116-ти регистров цвета, доступной в данный момент времени.

С появлением VGA-мониторов количество регистров цвета на видеокартах увеличилось до 256-ти, и появилась возможность делить их на 4x64 пли 16x16 групп. Размер кода точки в стандарте VGA IBM позволяет использовать любой из 256-ти регистров цвета. Тем не менее, для совместимости с режимом EGA и расширения его возможностей деление на группы сохранилось, а у видеоконтроллера появилось 16 регистров палитры.

При работе в режиме VGA IBM (но не VESA) восьмиразрядный код точки рассматривается как две независимые тетрады XY. Содержимое старшей тетрады х является номером регистра палитры (от о до F), в котором находится номер одной из 16-ти групп регистров цвета (тоже от о до F). Младшая тетрада Y является номером регистра в этой группе. При установке видеорежима в регистры палитры записываются их порядковые номера от о до огп, в результате регистры цвета оказываются как бы пронумерованными от 0 до 255, и присутствие регистров палитры просто не заметно. Если же принудительно изменить содержимое регистров палитры, то изменится естественный порядок нумерации регистров цвета.

Дополнение к программе визуализации

В качестве примера приведем дополнение к примеру 4.1, позволяющее увидеть на экране эффект от изменения содержимого регистров палитры. Оно приведено в примере 4.5 и должно быть включено в текст примера 4.1 между командами int 2lh и mov ax, 03.

Напомним, что программа примера 4.1 заполняет экран разноцветными вертикальными линиями, ждет ввод любого символа и прекращает свою работу. После указанных изменений вместо прекращения работы будет выполняться группа команд из примера 4.5, которая записывает в регистры палитры значения, противоположные исходным (OFh, ОЕb, ..., i, о). В результате картинка на экране окажется повернутой вокруг вертикальной оси. Например, если в исходном варианте палитра сел располагалась в первых 16-ти столбцах на экране, то теперь она будет находиться в столбцах с номерами 240—255, а расположение цветов в этих столбцах будет противоположно исходному.

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

Пример 4.5. Дополнение (вставка) к программе примера 4.1

mov сх, 16 ; количество изменяемых регистров
mov bx, 0F00h ; код для записи в нулевой регистр
ip_3: mov ax,1000h коя запрашиваемой функции Bios
int lOh установка регистра палитры
inc Ы номер следующего регистра
dec bh код для следующего регистра
looj з lp_3 управление повторами цикла
mov ax, 0C0lh код функции DOS
int 21h ждет нажатия на клавишу

В примере 4.5 использован запрос 1000h, который производит запись в регистр палитры. Перед его вызовом заполняется регистр bx. В старший байт ьь записывается номер группы регистров цвета, а в младший bl — номер регистра палитры.
Содержимое регистра палитры можно прочитать с помощью запроса 1007h. Номер читаемого регистра палитры указывается в ы, а его содержимое после выполнения запроса будет помещено в bh.

В состав группы 10h входят два запроса, которые позволяют прочитать (код 1009h) или записать (код I002h) сразу все регистры палитры. Кроме того, они считывают или записывают еще один специальный регистр, который называется overscan или Border. О назначении этого регистра следует поговорить особо.

Регистр Overscan

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

Некоторые,программы, например русификатор Keyrus, изменяют содержимое Overscan для окрашивания неиспользуемого пространства экрана в разные цвета. Это позволяет различать режимы работы программы, например ввод русских или латинских букв, не используя рабочую область экрана. Существуют два запроса — 1001h и 1008h, выполняющие запись и чтение регистра Overscan.

Замечание 3
Замечание 3

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

 




Примерах 4 3 и 4 4 приведены подпрограммы



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









Программа для визуализации палитры



Программа для визуализации палитры

Словесное описание каждого цвета едва ли позволит наглядно представить, как выглядит эта палитра, целесообразнее составить программу, позволяющую увидеть все 256 цветов на экране монитора. Для упрощения программы надо использовать стандартный графический режим VGA IBM (его код I3h). В таком случае потребуется минимум вспомогательных действий, а сравнительно низкое разрешение позволит получить более наглядное изображение. Текст программы приведен в примере 4.1.
Для получения завершенной задачи надо выполнить следующие действия:

1. Создать исходный файл, содержащий текст примера 4.1. Имя файла вы можете выбрать по своему усмотрению, а тип должен быть asm. 2. Обработать исходный файл Макроассемблером (произвести компиляцию) для получения объектного модуля, имеющего тип obj. 3. Обработать объектный модуль с помощью компоновщика (LINK), в результате чего будет получен файл задачи, имеющий тип ехе.

Конкретный способ выполнения перечисленных действий зависит от того, используете вы пакет PWB иди нет. Начиная с версии 6.0, Макроассемблер MASM поставляется в комплекте с пакетом PWB (programmer's workBench— инструментальные средства для программирования). В случае использования пакета все перечисленные в списке действия выполняются непосредственно в рабочей среде, которую поддерживает PWB. Если же он не используется, то сначала создается исходный файл с помощью любого текстового редактора. Затем для его обработки вызывается MASM, который создает объектный модуль. Наконец, для построения задачи из объектного модуля вызывается компоновщик. В любом случае при построении задачи примера 4.1 будет выдано сообщение об отсутствии сегмента, содержащего стек. Это простое предупреждение, а не признак ошибки.



Работа с блоком регистров



Работа с блоком регистров

В группу 10h прерывания int 10h включены два запроса, позволяющие записать или прочитать сразу несколько (блок) регистров цвета.

Запрос 10i2h "Set Block of color Registers" записывает коды базовых цветов в несколько (в блок) регистров цвета. Предварительно в оперативной памяти надо сформировать массив, содержащий и троек байтов, где N соответствует размеру блока. В байтах каждой тройки последовательно указываются шестиразрядные коды красного, зеленого и синего цветов. В литературе такой массив принято называть палитрой используемых цветов. Перед обращением к B10S в регистрах задаются следующие величины: bx — номер первого изменяемого DAC-регистр, сх — количество изменяемых DAC-регистров (к), es:dx — адрес оперативной памяти, соответствующий началу массива кодов устанавливаемых цветов (палитры).

Запрос 10i7h "Read Block of Color Registers" предназначен для копирова-ния содержимого блока регистров цвета в оперативную память. Входные параметры задаются так же, как для запроса 10i2h. В регистре bх указывается номер первого копируемого регистра цвета, в сх — количество копируемых регистров (N), а в es:dx — адрес начала массива, размером в зы байтов для размещении копируемых значений, где N — число, указанное в сх.









Работа с одним регистром



Работа с одним регистром

В группу 10h прерывания int 10h включены два запроса, позволяющие записать или прочитать один регистр цвета.

Запрос 1010h "Set One Color Register" Записывает нужный код в один из регистров цвета. Перед его вызовом коды базовых цветов помещаются в регистры dh, ch, cl, соответственно красный, зеленый и синий, а номер регистра цвета указывается в bx.

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

Не забывайте, что при работе с регистрами цвета используется только 6 младших разрядов каждого байта. Содержимое двух старших разрядов регистров dh, ch и cl B10S просто игнорирует.

Запрос 1015h "Read One Color Register" выполняет чтоение содержимого регистра цвета. Перед вызовом в регистре bx указывается его номер, а содержимое после выполнения запроса находится в регистрах dh, ch, cl, соответственно красный, зеленый и синий. B10S возвращает шестиразрядные коды базовых цветов.

Замечание 2
Замечание 2

В запросах 1010h и I015h для указания кода цвета используются одни и те же регистры общего назначения.

В примере 4.2 приведена группа команд, записывающих в последит! DAC-регпстр видеокарты (OFFh) код яркого белого цвета.



Стандартная палитра CGA



Стандартная палитра CGA

Установка и поддержка BIOS стандартной палитры CGA вызвана требованием совместимости с устаревшим программным обеспечением.

Программы, создававшиеся для IBM PC/XT и IBM PC/AT, должны выполняться на современных моделях ПК без каких-либо ограничений. Кроме того, палитра CGA нужна при работе в текстовых режимах, которые устанавливаются при первоначальной загрузке компьютера и DOS. Наконец, при описании функций BIOS очень часто приводятся коды цветов палитры CGA. В этом отношении рассматриваемые в данной книге примеры не являются исключением.

Содержимое первых 16-ти DAC-регистров (палитра CGA) показано в табл. 4.2. В ней перечислены коды, названия цветов и содержимое байтов соответствуюших DAC-регмстров. По интенсивности цвета делятся на две группы -, средняя и высокая интенсивность, соответственно таблица разделена па две половины (серый цвет является исключением). Коды — это шестнадшгге-ричные числа. Соответствие между ними и уровнями интенсивности в процентах такое: 3F - 100%, 2А - 67%, 15 -33%.
Поскольку цвет точки зависит от содержимого соответствующего ее коду DAC-регистра, то в дальнейшем, говоря о конкретном цвете, мы будем приводить коды трех базовых цветов (г, д, ь), хранящихся в указанной последовательности в байтах ода-регистра. Например, черному цвету соответствуют коды 0, 0, 0, а белому — коды 3F, 3F, 3F.



Названия и коды цветов



Таблица 4.2. Названия и коды цветов палитры CGA
Код точки Название цвета Коды базовых цветов
Красный Зеленый Синий
0 Черный 00 00 00
1 Синий 00 00
2 Зеленый 00 2А ' 00
3 Циан 00
4 Красный 00 00
5 Фиолетовым 00
6 Коричневый 15 00
7 Белый
8 Серый 15 15 15
9 Синий 15 15 3F
А Зеленый 15 3F 15
В Циан 15 3F 3F
С Красный 3F 15 15
D Фиолетовый 3F 15 3F
Е Желтый 3F 3F 15
F Белый 3F 3F 3F

Сравните вторую половину табл. 4.2 с табл. 4.1. Вы увидите, что одноименным цветам в них соответствуют разные значения базовых цветов. Например- красному цвету высокой интенсивности в табл. 4.2 соответствует тройка кодов в 3F, 15, 15, а в табл. 4.1 — зг, оо, оо. Какой из них считать красным. 5 какойи не совсем красным? Еще один пример. В таблицах редактора CorelDrаw фиолетовому цвету средней интенсивности соответствуют коды 26, 00, 2б, а высокой интенсивности — 3F, 26, 3F. В табл. 4.2 для этих же названий цветов указаны другие значения.

Эти примеры иллюстрируют тот факт, что наше восприятие цвета весьма субъективно. То, что одному кажется фиолетовым цветом, другой склонен считатьо пурпурным. Кроме того, наша способность различать близкие цвета весьма индивидуальна. Разница в интенсивности двух близких цветов с кодами 26,00, 26 и 2А, 00, 2А составляет всего 4/64 = 6.25% и вполне возможно, что» она будет неразличима для глаза.