Сразу отметим, что такой способ построения не является универсальным. Его можно использовать для воспроизведения рисунков небольшого размера, образы которых помещаются в одном сегменте оперативной памяти (не превышают 65 536 байтов).
При работе с большими файлами его использовать не целесообразно
по следующей причине. Файл является структурой с последовательным доступом
к данным. Это значит, что для обработки конкретной строки надо либо прочитать
все предыдущие строки, либо просто пропустить их, выполнив принудительное
позиционирование файла на начало нужной строки. Позиционирование связано
с дополнительными обращениями к DOS. При обработке строк в обратном порядке
потребуется многократное позиционирование файла, что существенно замедлит
При выборке строк из оперативной памяти в обратном порядке каждый раз надо вычислять адрес начала предыдущей строки. Возможны разные способы таких вычислений. Например, можно зарезервировать специальную переменную, содержащую адрес последней обработанной строки и уменьшать этот адрес для доступа к новой строке. В таком случае для вычисления адреса нужны три команды (пересылка, вычитание, пересылка). Мы покажем, как можно обойтись без специальной переменной.
Подпрограмма Smlbmp
Текст подпрограммы, выполняющей построение небольшого рисунка формата BMP описанным способом, приведен в примере А.9. Перед ее вызовом адрес левого верхнего угла рисунка в видеопамяти помещается в регистр di и устанавливается окно, которому принадлежит этот адрес. Регистр es должен содержать код видеосегмента. В регистре si указывается размер образа рисунка в байтах.
Пример А.9. Построение рисунка формата ВМР сверху вниз
Smlbmp : pusha сохранение "всех" регистров
PushReg <fs,gs,Cur wi n>; сохранение fs, gs, Cur win
mov ax, horsize ax = horsize
sub ax, iwidth ax = horsize — iwidth
mul bytppnt ax = (horsize — iwidth) * bytppnt, dx = 0
mov dx , ax dx = ax, для коррекции адреса строки
mov ex , si ex = si, размер образа рисунка
mov SwpOffs, 0 начало считываемых данных
call readf чтение образа рисунка
jnc ok -> чтение без ошибок
; Здесь должны выполнят! эСЯ действия в случае ошибки при чтении
ok: mov fs, SwpSeg fs = сегмент буфера обмена
mov gs, GenSeg ! ! gs = сегмент таблицы цветов
mov ex, iheight сх = количество строк в рисунке
invout : push ex сохраняем счетчик повторов
mov ex, iwidth сх = размер строки рисунка
sub si, fwidth адрес начала новой строки
call drawline ! ! или call bx — построение строки
pop ex восстанавливаем счетчик повторов
add si, rmndr учитываем "лишние байты"
sub si, fwidth адрес начала построенной строки
add di, dx коррекция адреса видеопамяти
jne @F -> адрес в пределах сегмента
call NxtWin установка следующего окна
@@: loop invout ; управление повторами цикла
PopReg <Cur win,gs,fs> ; восстановление Cur win, gs, fs
popa ; восстановление "всех" регистров
call setwin ; восстановление исходного окна
ret ; возврат из подпрограммы
Выполнение примера А.9 начинается с сохранения в стеке содержимого регистров общего назначения, сегментных регистров fs, gs и переменной cur_win. Следующие четыре команды вычисляют константу для коррекции адресов строк видеопамяти по способу, описанному в примере 7.13.
Для чтения образа файла в регистр сх помещается его размер, очищается переменная Swpoffs и происходит обращение к подпрограмме readf. Если чтение прошло без ошибок, то команда jnc ok обойдет строку, состоящую только из комментария. Что делать при ошибках чтения, решать вам.
В подпрограммах построения строк (drawiine) регистр
fs используется при чтении кодов точек из оперативной памяти, а регистр
gs — при работе с таблицей цветов. Поэтому перед началом основного цикла
в них записываются коды соответствующих сегментов.
Цикл построения рисунка имеет метку invout. Работа с адресами в нем организована
так, что при каждом повторе регистр di содержит адрес очередной строки
видеопамяти, а регистр si — адрес последней обработанной строки образа
рисунка (при первом входе это размер рисунка в байтах).
Перед вызовом подпрограммы drawiine содержимое si уменьшается на размер строки в файле (fwidth) и указывает начало очередной строки. При выполнении подпрограммы drawiine регистр si увеличится на iwidth. Поэтому после возврата из drawiine значение si увеличивается на rmndr (количество лишних байтов) и уменьшается на fwidth. В результате регистр si будет содержать адрес начала последней обработанной строки.
Адрес начала следующей строки видеопамяти, как обычно,
увеличивается на константу переадресации, которая хранится в регистре
dx. Если при сложении (add di, dx) происходит переполнение, то устанавливается
следующее окно видеопамяти.
Команда loop invout повторяет выполнение цикла нужное количество раз,
после чего восстанавливаются сохраненные в стеке величины, исходное окно
видеопамяти и происходит возврат на вызывающий модуль.
По сравнению с обычным циклом построения рисунка (см. пример 7.23) в данном случае добавились три команды, корректирующие значение регистра si. При желании их количество можно сократить до двух. Подумайте, как это сделать, но не забывайте, что fwidth - rmndr не обязательно равно iwidth. Вспомните, как вычислялись эти величины.
Замечание
Напомним, что имя подпрограммы построения строки не обязательно указывать
в явном виде. Команду call drawline можно изменить на call bx, а перед
обращением формировать в регистре bх адрес подпрограммы построения строки
(см.