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