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

         

Рисунки, подготовленные в стандарте 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 возврат из подпрограммы

В

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