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

         

Прямые линии

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

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

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

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


линии

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

anyline:

         

Содержание раздела