Рисунки, подготовленные в стандарте BMP
Принятый в стандарте BMP способ сжатия не эффективен, поэтому образы
полноцветных рисунков обычно не упакованы. Это упрощает цикл построения
рисунка, но не исключает необходимости преобразования кодов точек в формат,
соответствующий установленному задачей видеорежиму.
При построении полноцветных рисунков существенно изменяются подготовительные
действия, поэтому мы начнем с их подробного обсуждения. Полное описание
стандарта BMP приведено в приложении А данной книги. Вам лучше предварительно
прочитать его, для того чтобы узнать, как производится чтение заголовка
файла и анализ его основных полей. Без этого вы не сможете использовать
описанную ниже подпрограмму.
Строки образа рисунка хранятся в файле в
обратном порядке, т. е. первой записана последняя строка, а последней
— первая. Код каждой точки занимает три байта, содержащих базовые цвета
в формате bgr. Адрес начала строки в файле должен быть кратен 4, а т.
к. размер строк (в байтах) кратен 3, то после обработки каждой из них
может понадобиться пропустить от О до 3 байтов в файле. Специального признака,
указывающего наличие дополнительных байтов в строке, не существует, поэтому
надо вычислить размер строки в файле и количество "лишних" байтов.
Размер строки в файле равен утроенному количеству точек в строке, округленному
до значения кратного четырем. А разность между округленным и не округленным
значениями равна количеству дополнительных байтов.
Способ построения рисунка
При построении рисунка небольшого размера (см. пример 7.24) мы выбирали
из оперативной памяти и записывали в видеопамять строки образа рисунка
в порядке их естественного расположения (в строящемся рисунке). Однако
при построении таким способом большого рисунка, как уже говорилось, потребуется
дополнительное позиционирование файла, что нежелательно. Пройде позиционировать
строки в видеопамяти. Поэтому мы будем выбирать строки образа рисунка
в том порядке, в котором они хранятся в файле, а выводить на экран снизу
вверх. Первой в файле записана последняя строка, ее и надо строить первой,
затем изменится адрес видеопамяти и строится предпоследняя строка и так
вплоть до первой. Но при такой последовательности действий надо изменить
способ вычисления адресов строк рисунка в видеопамяти.
Коррекция адресов строк
Для определения адреса начала следующей строки мы прибавляли к текущему
адресу видеопамяти константу коррекции, которая вычисляет подпрограмма
caiioffs (см. пример 7.13) по формуле (horsize - widthrect)
*bytppnt. В данном случае нас интересует адрес предыдущей строки,
поэтому формулу для вычисления константы коррекции надо записать в виде
(horsize + widthrect} *bytppnt и вычитать вычисленное
значение из текущего адреса видеопамяти.
Объясним, почему так, а не иначе. В процессе построения строки исходный
адрес видеопамяти увеличивается на widthrect*bytppnt байтов. Если текущий
адрес видеопамяти уменьшить на эту величину, то получится адрес начала
построенной строки. А если адрес начала текущей строки уменьшить на величину
bperiine (horsize*bytppnt), то получится адрес начала предыдущей строки
(см. табл. 7.4).
Есть два способа вычисления нужной нам величины. Можно составить вариант
подпрограммы caiioffs, в котором вычитание заменено сложением. А можно
ничего не изменять в caiioffs, но перед ее вызовом указывать в регистре
dx отрицательное значение переменной widthrect (ширина рисунка). Мы используем
второй способ.
Адрес начала последней строки
Для построения рисунка в нужном месте экрана обычно задается адрес видеопамяти,
соответствующий левой верхней точке (началу) рисунка. В данном случае
построение рисунка начинается с его левой нижней точки (начало последней
строки). Поэтому перед построением рисунка в подпрограмме надо вычислять
адрес этой точки.
В видеопамяти начало последней строки рисунка отстоит от начала первой
на (N-1)*bperiine байтов, где N — количество
строк в рисунке. Следовательно, надо вычислить указанную величину, выразить
ее старшую часть в единицах приращения окна видеопамяти и сложить полученный
результат с адресом первой точки рисунка.
Размер порции данных
Рисунки большого размера считываются из файла и строятся по частям.
Размер считываемой части файла (порции данных) надо выбрать таким, чтобы
в нем укладывалось целое число строк. Это исключает лишние проверки в
процессе построения рисунка.
При чтении из файла размер порции данных задается в байтах, а для управления
повторами цикла построения рисунка используется количество считанных строк.
В примере 3.22 количество строк вычислялось путем деления числа 65 535
на размер строки, а размер порции — путем умножения количества строк на
65 535. В данном случае размер строки в файле может не совпадать с размером
строки в рисунке и перед делением его надо вычислить.
Новые переменные. Для хранения размеров рисунка и величин, вычисляемых
в процессе подготовительных действий, в примере 3.22 использовались 5
переменных, описанных в разделе данных задачи. В этом случае к ним добавляется
еще три, поэтому в разделе данных задачи должны быть описаны следующие
переменные:
iwidth dw 0 количество точек в строке (ширина) рисунка
fwidth dw 0 количество байтов в строке файла
iheight dw 0 количество строк в рисунке (высота рисунка)
rmndr dw 0 добавка к адресу для пропуска "лишних" байтов
part dw 0 количество строк в считываемой порции данных
numbyte dw 0 количество байтов в считываемой порции данных
remline dw 0 количество строк, которые еще не обработаны
Значения переменных iwidth и iheight
выбираются из полей заголовка файла, а значения остальных переменных формируются
в подпрограмме построения рисунка при выполнении подготовительных действий.
Подпрограмма BigBmp
Текст подпрограммы приведен в примере 7.25. Перед ее вызовом должно быть
установлено окно видеопамяти, в котором расположено начало рисунка. Его
адрес указывается в регистре di, a es должен содержать код видеосегмента.
Предполагается, что задача предварительно открыла файл для чтения и обработала
его заголовок так, как это рекомендовано в приложении А, поэтому известны
значения переменных iwidth и iheight и файл установлен на начало образа
рисунка.
Напомним
Перед построением любого рисунка, тем более большого размера, надо удалить
с экрана изображение курсора (call Hidepnt), а после построения рисунка
восстановить его на экране (call Showpnt).
Пример 7.25. Построение полноцветного рисунка формата
BMP
BigBmp : pusha сохранение "всех" регистров
PushReg <fs,Cur win> сохранение fs и Cur win
mov fs, SwpSeg fs = сегмент буфера обмена
mov SwpOffs, 0 очистка смещения в сегменте
mov dx, iwidth dx = количество точек в строке
neg dx dx = — iwidth
call calloffs bx = (horsize + iwidth) *bytppnt
mov ax, 03 ax = 3
mul iwidth ax = 3*iwidth, утроенный размер строки
mov dx , ax сохраняем его в dx
add ax, 03 с помощью. двух команд округляем
and al, OFCh размер строки до значения, кратного 4
mov f width, ax fwidth = размер строки в файле
sub ax, dx вычисляем добавку к адресу
mov rmndr, ax и сохраняем ее в rmndr
mov ax, -1 ах = 65535
mov numbyte, ax numbyte = 65535
xor dx, dx очистка старшей части делимого
div fwidth ах = 65535 / fwidth (частное от деления)
mov part, ax part = число строк в порции для чтения
sub numbyte, dx numbyte = numbyte — ах
mov ax, iheight ах = количество строк в рисунке
mov remline, ax remline — счетчик числа строк
dec ax ах = номер последней строки
mul bperline ах = (iheight — l)*bperline
add di, ax di = адрес последней строки рисунка
adc dx, 00 учитываем возможный перенос
mov ax, GrUnit ах = GrUnit (единица измерения окон)
mul dl вычисляем добавку к номеру окна
add Cur win, ax номер окна для последней строки
call Setwin установка окна
NewPart mov ex, numbyte сх = количество считываемых байтов
call Readf чтение порции в буфер обмена
jnc sucread -> чтение прошло без ошибок
/ Здесь должны выполнять ся действия в 'случае ошибки при чтении
sue re ad mov ex, part сх = кол-во строк в полной порции
cmp remline, ex считана полная порция данных ?
jae @F -> да, обходим следующую команду
mov ex, remline нет, сх = оставшееся число строк
@@: sub remline, ex уменьшаем значение счетчика строк
xor si, si si = начало буфера обмена
drwout : push ex сохраняем\ значение счетчика строк
mov ex, iwidth сх = размер строки- (в точках)
call drawline построение очередной строки
pop ex восстанавливаем счетчик строк
add s i , rmndr корректируем адрес в буфере обмена
sub di, bx di = адрес начала предыдущей строки
jnc @F -> адрес в пределах текущего окна
call PrevWin установка предыдущего окна
@@: loop drwout управление циклом построения строк
cir.p remline, 0 остались не обработанные строки ?
jne NewPart -> да, на чтение следующей порции
PopReg <Cur win, f s> восстановление Cur win и fs
popa восстановление "всех" регистров
call Setwin восстановление исходного окна
ret возврат из подпрограммы
Выполнение примера 7.25 начинается с сохранения в стеке содержимого
"всех" регистров. Команда pusha не сохраняет сегментные регистры,
поэтому содержимое fs и переменной cur_win сохраняет макровызов pushReg.
Для чтения данных вызывается подпрограмма Readf, текст которой приведен
в примере 3.23. Она размещает данные в буфере обмена, начиная с адреса,
указанного в swpoffs. Для максимального использования пространства буфера
эта переменная предварительно очищается, а в регистр fs записывается сегмент
буфера обмена из SwpSeg.
После этого выполняются подготовительные действия, смысл и назначение
которых описаны выше. Три первые команды вычисляют константу для коррекции
адресов строк, ее значение помещается в регистр bx и используется в основном
цикле. Следующие восемь команд формируют значения переменных fwidth и
rmndr. Затем шесть команд вычисляют значения переменных part и numbyte.
Последние десять команд формируют адрес начала последней строки рисунка
в видеопамяти и устанавливают окно, которому он принадлежит.
Основной цикл имеет метку NewPart. Он практически совпадает с одноименным
циклом примера 7.24. Отличие заключается в следующем. При переадресации
строк видеопамяти вычисляется адрес начала предыдущей строки, поэтому
содержимое регистра bx вычитается из содержимого регистра di, а в случае
переполнения результата вычитания устанавливается предыдущее окно видеопамяти.
Кроме того, для исключения "лишних" точек адрес буфера обмена,
хранящийся в регистре si, увеличивается на величину rmndr. Ну и, конечно
же, имя drawiine соответствует разным подпрограммам.
Подпрограммы для построения строк
В отличие от основного текста примера 7.25, тексты подпрограмм построения
строк существенно зависят от установленного задачей видеорежима. Это связано
с необходимостью преобразования исходного формата bgr в формат Hi-color
или True Color. Варианты подпрограмм для обоих режимов показаны в примере
7.26.
Пример 7.26. Варианты подпрограммы построения строки
; Вариант 1 для работы в режимах True Color
drawline: movs word ptr [di], f s: [si]; копируем коды синего и зеленого
lods byte ptr fs:[si] ; al = код красного цвета
xor ah, ah очистка резервного байта
stosw записываем старшее слово кода
or di, di адрес в пределах сегмента ?
jnz @F -> да, обход команды
call NxtWin установка следующего окна
@@: loop drawline управление повторами цикла
ret возврат из подпрограммы
; Вариант 2 для работы в 15-разрядных режимах Hi-Color
drawline: mov al, fs:[si+2] читаем код красного цвета в
al shr al, 03 сокращаем его до 5-ти разрядов
mov bh, fs:[si+1] читаем код зеленого цвета в
bh shld ax, bx, 05 добавляем в ах код зеленого цвета
mov bh, f s:[si] читаем код синего цвета в
bh shld ax, bx, 05 сдвигаем и дополняем код в
ах add si, 03 добавляем в ах код синего цвета
stosw записываем код в видеопамять
or di, di адрес в пределах сегмента ?
jnz @F -> да, обход команды
call NxtWin установка следующего окна
@@: loop drawline управление повторами цикла
ret возврат из подпрограммы
В разделе и разделе
описано преобразование цветов, хранящихся в палитрах, в форматы Hi-color
и True Color. Отличие рассмотренного здесь случая в том, что исходные
цвета расположены не в палитре, а в строке образа рисунка, находящейся
в буфере обмена. Поэтому первый вариант примера 7.26 является циклом примера
7.19, а второй вариант примера 7.26 является циклом примера 7.18.
Таким образом, при построении полноцветных рисунков формата BMP текст
основной подпрограммы не зависит, а вспомогательной (drawline) зависит
от видеорежима. Исключить эту зависимость невозможно, но при использовании
директив условного ассемблирования можно задавать признак для выбора нужной
подпрограммы.
|