3.11. Закрашивание области.
Закрашивание произвольной области экрана - довольно часто
встречающаяся задача. Нелишне напомнить и о том, что она
требует серьезных затрат машинного времени.
Здесь речь идет о том, что если на экране есть некоторое
замкнутое изображение (контур), то закрасить его установленным
цветом INK - это значит включить все выключенные пикселы,
которые внутри этого контура могут быть найдены, оставив без
изменения ранее включенные. Может возникнуть вопрос: "А каким
же цветом будет заполнен наш контур?" - Это зависит от того,
какие атрибуты установлены для того или иного знакоместа. То
есть при включении пикселов на экране они приобретут тот цвет,
который ранее соответствовал параметру INK для данного знако-
места.
Назовем эту программу FN j(x,y), где x и y - абсолютные
координаты исходной точки, с которой начнется заполнение. Ра-
зумеется x<255, а y<175. Программа работает по методу "лесного
пожара". В окресностях исходной точки включаются все невключен-
ные пикселы (слева, сверху, справа, снизу) и их координаты за-
поминаются в специально отведенном для этой цели буфере, затем
следует переход к первой из новых включенных точек. Окрестнос-
ти новой точки тоже включаются и запоминаются и следует переход
ко второй включенной точке и т.д.
------------------¬
¦ T S O I ¦
¦ Q M G C L-----¬
¦ L F B A D J ¦
¦ R N H E K P ¦
L------------------------
Рис. 18
Если мы начали работу с точки A, то включение точек идет
так, как показано на рис. 18.
Важную роль играет в этой процедуре буфер. Он имеет ориги-
нальную организацию. Вы, очевидно, знаете один из приемов орга-
низации памяти, называемый стеком. На стек данные закладываются
сверху и сверху же и снимаются по принципу "последним пришел -
первым уйди". Здесь буфер имеет другую организацию - конвейер-
ную. Конвейер тоже, как и стек, заполняется сверху, но опорож-
няется снизу. Действует принцип "первым пришел, первым уйди".
Главное отличие состоит в том, что у стека динамической явля-
ется только верхняя граница - она постоянно "дышит" вверх или
вниз. Основание стека - фиксировано и потому для обслуживания
стека в программах достаточно одной переменной - указателя на
текущий адрес вершины стека. У конвейера же динамичными оказы-
ваются обе границы - и нижняя и верхняя, а потому надо иметь
две переменных-указателя. Зато они не "дышат" вверх-вниз, а
идут в одном направлении - только вверх. Так, при обслуживании
точки A буфер будет иметь вид, показанный на рис. 19 а , а
при обслуживании точки C - вид, показанный на рис. 19 б.
-- -- T--------------------XXXX------------------------- - -¬
¦ ¦ +-----+
¦ ¦ ---CONMAX-----------------------+ I ¦
¦ ¦ ¦ +-----+
¦ ¦ ¦ ¦ H ¦
¦ ¦ ¦ +-----+
¦ ¦ ¦ ¦ G ¦
¦ ¦ ¦ +-----+
¦ ¦ ¦ ¦ F ¦
+-----+ ¦ +-----+
¦ E +---------------- ¦ E ¦
+-----+
¦ D ¦ ¦ D ¦
+-----+
¦ C ¦ ----CONMIN---------------------+ C ¦
+-----+ ¦ +-----+
¦ B ¦ ¦ ¦ - ¦
+-----+ ¦ +-----+
¦ A +----------------- ¦ - ¦
L-----+------------------- BUFFER --------------------+------
Рис.19а Рис.19б
Каждая клетка на этих рисунках - это два байта памяти, хранящие
координаты x и y для данной точки. (Клетки F,G,H добавились при
обслуживании т.B, а клетка I - при обслуживании C).
Программная переменная BUFFER содержит адрес физической
нижней границы буфера. Он фиксирован. Переменная CONMIN - ниж-
няя граница конвейера. Отсюда берутся координаты очередной об-
служиваемой точки. Эта граница - плавающая. Переменная CONMAX -
верхняя граница конвейера. Сюда заносятся координаты точек,
соседних с обслуживаемой в данный момент, если они не включены.
Эта граница также плавающая (растет вверх). На верхнюю границу
буфера указывает адрес XXXX. Его Вы можете задать сами в стро-
ках 57779 и 57870. В конкретном примере, рассмотренном ниже,
этот адрес уже задан в строках DATA (мы были обязаны задать
хоть что-то, иначе нельзя было бы получить контрольную сумму)
из расчета, что на буфер расходуется 1000 байтов, начиная с
адреса 57900. В них можно разместить координаты 500 пикселов на
экране, что конечно же немного, но программа работает так, что
когда верхняя граница конвейера "дорастает" до физической верх-
ней границы буфера, она вновь устанавливается на нижнюю грани-
цу. Это сделать можно, поскольку к этому времени нижняя часть
буфера уже очищена и нижняя граница конвейера ушла вверх (это
происходит в строке 57879). То же происходит и когда нижняя
граница конвейера "дорастает" до верхней границы буфера
(57788). Таким образом, наш конвейер работает в отведенном ему
ограниченном буфере циклически.
Конец работы программы наступит, когда внутри замкнутого
контура не останется ни одного выключенного пиксела. В этом
случае на конвейер сверху перестанут поступать координаты и
CONMAX перестанет расти, в то же время снизу координаты будут
продолжать сниматься и скоро CONMIN достигнет CONMAX, что и
явится сигналом конца работы (57793-57803).
Программа работает весьма быстро и довольно элегантно.
Кажется, что заполнение идет по всем диагональным направлениям.
Верхняя и нижняя границы экрана - непроницаемы для заполнения,
зато правая и левая имеют возможность работы "с возвратом".
Так, если заполнение дошло до правого края экрана, то оно
продолжится слева и наоборот.
10 REM *** Загрузчик машинного кода
20 LET adr=57700: LET long=190: LET z=0
30 FOR i=0 TO long-1: READ a
40 POKE (adr+i),a: LET z=z+a
50 NEXT i
60 LET z=INT (((z/long)-INT (z/long))*long)
70 READ a
80 IF a<>z THEN PRINT "??": STOP
500 REM ***Данные для машинного кода
510 DATA 42, 11, 92, 1, 4
520 DATA 0, 9, 86, 14, 8
530 DATA 9, 94, 237, 83, 44
540 DATA 226, 237, 83, 42, 226
550 DATA 33, 44, 226, 229, 35
560 DATA 35, 34, 40, 226, 225
570 DATA 34, 38, 226, 42, 38
580 DATA 226, 94, 35, 86, 21
590 DATA 205, 207, 225, 42, 38
600 DATA 226, 94, 28, 35, 86
610 DATA 205, 207, 225, 42, 38
620 DATA 226, 94, 35, 86, 20
630 DATA 205, 207, 225, 42, 38
640 DATA 226, 94, 29, 35, 86
650 DATA 205, 207, 225, 42, 38
660 DATA 226, 35, 35, 229, 1
670 DATA 76, 229, 167, 237, 66
680 DATA 32, 5, 225, 33, 44
690 DATA 226, 229, 225, 34, 38
700 DATA 226, 237, 75, 40, 226
710 DATA 167, 237, 66, 200, 195
720 DATA 133, 225, 237, 83, 42
730 DATA 226, 62, 175, 147, 216
740 DATA 95, 167, 31, 55, 31
750 DATA 167, 31, 171, 230, 248
760 DATA 171, 103, 122, 7, 7
770 DATA 7, 171, 230, 199, 171
780 DATA 7, 7, 111, 122, 230
790 DATA 7, 71, 4, 62, 254
800 DATA 15, 16, 253, 6, 255
810 DATA 168, 71, 126, 160, 192
820 DATA 126, 176, 119, 42, 40
830 DATA 226, 237, 91, 42, 226
840 DATA 115, 35, 114, 35, 229
850 DATA 1, 76, 229, 167, 237
860 DATA 66, 32, 5, 225, 33
870 DATA 44, 226, 229, 225, 34
880 DATA 40, 226, 201, 193, 195
890 DATA 57, 0, 0, 0, 0
Дисассемблер программы:
57700 2A0B5C LD HL,(5C0BH) ;См. с. 109...111
57703 010400 LD BC,0004 ;Сдвиг от DEFADD на 4 бай-
57706 09 ADD HL,BC ;та (см. c.109...111).
57707 56 LD D,(HL) ;Координата x.
57708 0E08 LD C,08 ;Сдвиг на
57710 09 ADD HL,BC ;восемь байтов.
57711 5E LD E,(HL) ;Координата y.
57712 ED532CE2 LD(BUFFER),DE ;Запомнили x,y.
57716 ED532AE2 LD(TEMPXY),DE ;Запомнили x,y.
57720 212CE2 LD HL,E22C ;Указание на нижнюю гра-
;ницу буфера.
57723 E5 PUSH HL ;Запомнили ее на стеке.
57724 23 INC HL ;Инициализация верхней
57725 23 INC HL ;границы
57726 2228E2 LD(CONMAX),HL ;конвейера.
57729 E1 POP HL ;Восстановили нижнюю гра-
57730 2226E2 LD(CONMIN),HL ;ницу буфера и выставили в
;ней нижнюю границу кон-
;вейера.
57733 2A26E2 MAIN_L LD HL,(CONMIN) ;Адрес-указатель на теку-
;щую координату.
57736 5E LD E,(HL) ;Приняли y.
57737 23 INC HL ;Переход к x.
57738 56 LD D,(HL) ;Приняли x.
57739 15 DEC D ;x-1.
57740 CDCFE1 CALL PLOT ;Проверяем точку слева
;(x-1),y.
57743 2A26E2 LD HL,(CONMIN) ;Адрес-указатель на теку-
;щую координату.
57746 5E LD E,(HL) ;Приняли y.
57747 1C INC E ;y+1.
57748 23 INC HL ;Переход к x.
57749 56 LD D,(HL) ;Приняли x.
57752 CDCFE1 CALL PLOT ;Проверяем точку вверху
;x,(y+1).
57755 2A26E2 LD HL,(CONMIN) ;Адрес-указатель на теку-
;щую координату.
57756 5E LD E,(HL) ;Приняли y.
57757 23 INC HL ;Переход к x.
57758 56 LD D,(HL) ;Приняли x.
57759 14 INC D ;x+1.
57760 CDCFE1 CALL PLOT ;Проверяем точку справа
;x+1,y.
57763 2A26E2 LD HL,(CONMIN) ;Адрес-указатель на теку-
;щую координату.
57766 5E LD E,(HL) ;Приняли y.
57767 1D DEC E ;y-1
57768 23 INC HL ;Переход к x.
57769 56 LD D,(HL) ;Приняли x.
57772 CDCFE1 CALL PLOT ;Проверяем точку внизу
;x,y-1.
57775 2A26E2 LD HL,(CONMIN) ;Адрес-указатель на теку-
;щую координату.
57776 23 INC HL ;Переход к следующей пози-
57777 23 INC HL ;ции буфера.
57778 E5 PUSH HL ;Запомнили ее на стеке.
57779 01???? LD BC XXXX ;XXXX - предельный адреc,
;до которого может разви-
;ваться буфер - Вы задаете
;его сами.
57782 A7 AND A ;Сброс флагов регистра F.
57783 ED42 SBC HL,BC ;Проверка на переполнение
;буфера.
57785 2005 JR NZ PASS ;Обход, если буфер еще не
;переполняется.
57787 E1 POP HL ;Очистка стека.
57788 212CE2 LD HL,BUFFER ;Нижняя граница конвейера
;выставляется в нижнюю
;границу буфера.
57791 E5 PUSH HL ;Запомнили ее на стеке.
57792 E1 PASS POP HL ;Нижняя граница конвейера.
57793 2226E2 LD(CONMIN),HL ;Запомнили ее в переменной
57796 ED4B28E2 LD BC,(CONMAX) ;Вершина конвейера.
57800 A7 AND A ;Сброс флагов.
57801 ED42 SBC HL,BC ;Проверка не достигла ли
;нижняя граница конвейера
;верхнюю.
57803 C8 RET Z ;Если да, то конец работы.
57804 C385E1 JP MAIN_L ;Возврат в вершину глав-
;ного цикла.
Подпрограмма PLOT выполняет четыре задачи. Во-первых, по
координатам точки, выставленным в регистровой паре DE определя-
ет адрес в дисплейном файле, соответствующий этой точке (57807
- 57837). Эти операции делаются совершенно так же, как и в про-
цедурах FN f() и FN g() и мы их уже разбирали.
Вторая задача - проверка не включен ли уже пиксел, имеющий
данную координату и если да, то возврат (57852 - 57854).
Третья задача - если пиксел не включен, то его надо вклю-
чить (57855 - 57857).
Четвертая задача - включив пиксел, запомнить его координа-
ты на вершине конвейера и переместить указатель CONMAX на сле-
дующую позицию.
57807 ED532AE2 PLOT LD (TEMPXY),DE ;Текущие координаты x,y
57811 3EAF LD A,0AFH ;AFH=175 DEC
57813 93 SUB E ;Проверка на выход за пре-
57814 D8 RET C ;делы экрана по вертикали.
57815 5F LD E,A ;Дополнение y до 175
57816 A7 AND A ;Сброс флага переноса.
57817 1F RRA ;Ротация вправо.
57818 37 SCF ;Установка флага переноса.
57819 1F RRA ;Ротация вправо.
57820 37 SCF ;Установка флага переноса.
57821 1F RRA ;Ротация вправо.
57822 AB XOR E ;Комплексная
57823 E6F8 AND 0F8H ;операция замещения
57825 AB XOR E ;битов 0, 1 и 2.
57826 67 LD H,A ;Сформировали старший байт
;адреса в дисплейном файле
57827 7A LD A,D ;Координата x.
57828 07 RLCA ;Вращение влево.
57829 07 RLCA ;Вращение влево.
57830 07 RLCA ;Вращение влево.
57831 AB XOR A ;Операция замещения
57832 E6C7 AND C7 ;для битов 3,4,5.
57834 AB XOR A
57835 07 RLCA ;Вращение влево.
57836 07 RLCA ;Вращение влево.
57837 6F LD L,A ;Сформировали младший байт
;адреса в дисплейном файле
57838 7A LD A,D ;Координата x.
57839 E607 AND 07 ;Маскирование.
57841 47 LD B,A ;Инициализация счетчика
57842 04 INC B ;оборотов в регистре B.
57843 3EFE LD A,0FEH ;Байт FE=254 интересен
;тем, что все биты, кро-
;ме одного, равны едини-
;це. Вот эту "дырку" мы
;и вращаем, пока она не
;встанет на свое место.
57845 0F AGAIN RRCA ;Вращение влево.
57846 10FD DJNZ AGAIN ;Повтор вращения.
57848 06FF LD B,0FFH ;Инверсия, чтобы печать
57850 A8 XOR B ;точки была черным по бе-
;лому, а не наоборот.
57851 47 LD B,A ;Запомнили в B
57852 7E LD A,(HL) ;Приняли в аккумулятор то,
;что уже содержится на
;экране в нужной линии.
57853 A0 AND B ;Логическое сравнение.
57854 C0 RET NZ ;Если пиксел в текущей
;координате уже включен,
;то возврат в вызывающую
;программу.
57855 7E LD A,(HL) ;Приняли в аккумулятор то,
;что уже содержится на
;экране в нужной линии.
57856 B0 OR B ;Включаем требуемый бит.
57857 77 LD (HL),A ;Включаем требуемый пиксел
57858 2A28E2 LD HL,(CONMAX) ;Указатель вершины конв-ра
57861 ED5B2AE2 LD DE,(TEMPXY) ;Текущие x и y.
57865 73 LD (HL),E ;x
57866 23 INC HL ;Переход к y.
57867 72 LD (HL),D ;y
57868 23 INC HL ;Переместили CONMAX
57869 E5 PUSH HL ;и запомнили его.
57870 01???? LD BC,XXXX ;Верхняя граница буфера.
57873 A7 AND A ;Сброс флагов регистра F.
57874 ED42 SBC HL,BC ;Проверка заполненности
;буфера.
57876 2005 JR NZ,PASS_1 ;Обход, если буфер не
;заполнен.
57878 E1 POP HL ;Очистка стека
57879 212CE2 LD HL,E22C ;Если достигнут верхний
;предел буфера, то верх-
;нюю границу конвейера
;выставляем в нижнюю гра-
;ницу буфера.
57882 E5 PUSH HL ;И запоминаем ее.
57883 E1 PASS_1 POP HL ;Верхняя граница конв-ра.
57884 2228E2 LD (CONMAX),HL ;Запомнили ее.
57887 C9 RET ;Возврат.
57894 CONMIN DEFW ;Нижняя граница конвей-
;ера.
57896 CONMAX DEFW ;Верхняя граница конвей-
;ера.
57898 TEMPXY DEFW ;Переменная для временного
;хранения координат теку-
;щей точки при работе про-
;цедуры PLOT.
57900 BUFFER DEFW ;Нижняя граница буфера.
Примеры использования процедуры.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Вы можете без труда изобразить на БЕЙСИКе любой замкнутый
контур и, задав координаты x,y, принадлежащие области находя-
щейся внутри данного контура произвести его закрашивание цветом
INK. Эффект от работы этой процедуры очень напоминает динами-
ческую графику, что способно оживить любую программу, особенно
если есть дизайнерские способности и фантазия. если же фантазии
пока нет, то посмотрите, как может быть организовано использо-
вание этой процедуры на следующих примерах.
Пример 1. Требует подгрузки процедуры рисования отрезков
прямых FN g(x,y,p,q).
100 DEF FN g(x,y,p,q)=USR 60700
110 DEF FN j(x,y) = USR 57700
120 BORDER 1: PAPER 6: INK 2
130 CLS
140 FOR i=1 TO 12
150 LET x1=i*20
160 LET y1=174
170 LET x2=10+i*20
180 LET y2=20
190 RANDOMIZE FN g(x1,y1,x2,y2)
200 NEXT i
210 FOR i=1 TO 12
220 LET x1=i*20
230 LET y1=174
240 LET x2=i*20-10
250 LET y2=20
260 RANDOMIZE FN g(x1,y1,x2,y2)
270 NEXT i
280 RANDOMIZE FN g(10,20,130,2)
290 RANDOMIZE FN g(250,20,130,2)
300 PAUSE 100
310 RANDOMIZE FN j(10,5)
Пример 2. Требует подгрузки процедуры для изображения
прямоугольников FN h(x,y,h,v).
100 DEF FN h(x,y,h,v)=USR 60400
110 DEF FN j(x,y) = USR 57700
120 BORDER 2: PAPER 6: INK 2
130 CLS
140 LET x=140
150 FOR j=110 TO 110 STEP -5
160 RANDOMIZE FN h(x,j,60,60)
170 IF x/10 = INT (x/10) THEN RANDOMIZE FN j(x+1,j+1)
180 LET x=x-5
190 NEXT j
3.12. Наложение изображений.
Давайте рассмотрим команду OVER стандартного БЕЙСИКа.
Вы никогда не задумывались над тем, что эта команда является
фактически логической командой?
В БЕЙСИКе существуют логические команды AND, OR, NOT - вот
пожалуй и все. Кроме того, при программировании в машинных ко-
дах Вам доступна еще и команда XOR - "Исключающее ИЛИ". Так
вот, команда OVER 1 при печати на экране фактически эквивален-
тна функции XOR. Действительно, давайте рассмотрим, что делает
команда A XOR B. В результате ее действия включаются те биты,
которые были включены либо в A, либо в B, но если они были
включены и в A и в B, то они выключаются (в этом отличие от
команды OR).
А что делает команда OVER 1? Практически то же самое, но
при печати на экране. Когда Вы накладываете одно изображение на
другое, включаются те пикселы, которые были включены в одном
или в другом изображении, но если пиксел был включен и там и
там, то он выключается. Кстати, это довольно эффективный метод
стирания, когда в режиме OVER 1 Вы печатаете некоторое изобра-
жение поверх самого себя. Попробуйте для эксперимента:
10 PRINT AT 10,12; OVER 1: "SPECTRUM"
20 PAUSE 10:
30 GO TO 10
Не правда ли, очень похоже на действие команды FLASH, но
не совсем то же самое?
В качестве демонстрации возможности печати изображений с
наложением по XOR мы рассмотрим программу для наложения
отрезков прямых FN k(x,y,p,q). Здесь x,y - координаты исходной
точки, а p,q - координаты конечной точки отрезка (абсолютные).
Ограничения на величину x,y,p,q, связанные с размером экрана
256x176 очевидны.
10 REM *** Загрузчик машинного кода
20 LET adr=57600: LET long=15: LET z=0
30 FOR i=0 TO long-1: READ a
40 POKE (adr+i),a: LET z=z+a
50 NEXT i
60 LET z=INT (((z/long)-INT (z/long))*long)
70 READ a
80 IF a<>z THEN PRINT "??": STOP
500 REM ***Данные для машинного кода
510 DATA 62, 168, 50, 223, 237
520 DATA 205, 28, 237, 62, 176
530 DATA 50, 223, 237, 201, 0
540 DATA 13, 0, 0, 0, 0
Дисассемблер программы:
57600 3EA8 LD A,0A8H ;A8 - это код операции
;XOR B.
57602 32DFED LD (EDDF),A ;В процедуре рисования
;отрезков прямых FN g()
;по адресу 60895 стоит
;код операции BO (OR B).
;Он принудительно включа-
;ет требуемый пиксел на
;экране. Заменив его на
;XOR B мы включаем пиксел,
;если он был выключен. В
;противном случае - нао-
;борот выключаем.
57605 CD1CED CALL ED1C ;Вызов измененной прог-
;раммы изображения отрез-
;ков.
57608 3EB0 LD A,0B0H ;0B - код операции OR B.
57610 32DFED LD (EDDF),A ;В процедуре FN g() вос-
;становили то, что там и
;должно было быть.
57613 C9 RET ;Выход из процедуры.
Эта маленькая и очень простая процедура показывает нам
пример отнюдь не простой операции. Мы только что сделали изме-
нение машинного кода одной процедуры (FN g()) из другой. Нам и
раньше приходилось использовать несколько процедур совместно
и при этом они обменивались данными через выделенную для этой
цели ячейку памяти. Здесь же имеет место прямое изменение
рабочего кода одной процедуры из другой. Это уже совсем другой
стиль программирования.
В учебных целях надо сделать еще одно дополнение. Мы
хорошо знаем, что находится в процедуре FN g() по адресу 60895
и потому перед выходом из подпрограммы восстановили там
первоначальное число, равное B0H. Но не всегда мы заранее можем
точно знать, что там было. Поэтому в общем случае положено
принять содержимое модифицируемой ячейки памяти, потом
сохранить его в какой-либо переменной или на стеке (что менее
надежно) и только после этого вносить свои изменения. А перед
возвратом следует восстановить то, что было нами нарушено.
Примеры использования процедуры.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Два фантастических узора показывают как простыми графичес-
кими средствами можно добиться впечатляющих результатов.
Пример 1. Требует подгрузки процедуры рисования отрезков
прямых FN g(x,y,p,q).
100 DEF FN k(x,y,p,q)=USR 57600
110 BORDER 0: PAPER 0: INK 3
120 CLS
130 LET s=1
140 LET a=0
150 LET ad=s*PI/128
160 LET x1=80
180 LET y1=88
190 FOR c=1 TO 2
200 FOR i=0 TO 255 STEP s
210 LET x=x1+INT(70*SIN a)
220 LET y=y1+INT(70*COS a)
230 RANDOMIZE FN k(x,y,x1,y1)
240 LET a=a+ad
250 NEXT i
260 LET x1=x1+96
270 NEXT c
280 PAUSE 100
290 GO TO 110
Пример 2. Требует подгрузки процедуры рисования отрезков
прямых FN g(x,y,p,q).
100 DEF FN k(x,y,p,q)=USR 57600
110 BORDER 5: PAPER 5: INK 1
120 CLS
130 LET s=1
140 LET a=0
150 LET ad=s*PI/128
160 LET x1=127
180 LET y1=88
190 FOR i=0 TO 255 STEP s
200 LET x=x1+INT(110*SIN a)
200 LET y=y1+INT(70*COS a)
210 RANDOMIZE FN k(x,y,x1,y1)
220 LET a=a+ad
230 NEXT i
240 PAUSE 0
250 GO TO 110
3.13. Увеличение изображений.
Если Вы работали с графическим редактором ARTSTUDIO, то не
могли не восхититься изяществом того, как происходит увеличение
экрана в 2 раза (MAGNIFY X 2). Теперь Вы можете это сделать и
сами. Достигающийся при этом эффект способен оживить очень мно-
гие программы.
Процедура позволяет увеличить в 2 раза как по горизонтали,
так и по вертикали все, что находится в заданном Вами "окне".
При этом координаты и размеры выбранного Вами "окна" задаются в
знакоместах, а не в абсолютных координатах. Прежде чем
увеличивать Ваше изображение, процедура сделает его копию и
сохранит ее в верхних областях памяти (адрес временного буфера
можно задать самим) и только после этого начнет перестраивать
экран. Эту особенность процедуры можно и нужно использовать
например для того, чтобы в любой момент можно было бы подать
команду и вернуться к исходному изображению.
Вместе с тем, понятно, что для процедуры совершенно все
равно, что ей надо увеличивать, поэтому увеличив изображение в
два раза и получив новый экран, Вы можете подать команду еще
раз и вновь увеличить экран в два раза. Так можно увеличивать
изображение во много раз. Правда, буфер хранящий исходное изоб-
ражение,- только один и потому возвратиться Вы можете ТОЛЬКО К
ПРЕДЫДУЩЕМУ изображению. Так, если Вы выполните увеличение два-
жды, то есть увеличите рисунок в 4 раза, то возврат вернет Вас
к изображению, увеличенному в 2 раза, а исходное будет уже
утрачено.
Назовем эту процедуру FN l(x,y,h,v), где:
x - горизонтальная координата левого верхнего угла "окна"
(задается в знакоместах, - x < 32);
y - вертикальная координата левого верхнего угла "окна"
(задается в знакоместах, - y < 22);
h - размер "окна" по горизонтали (ширина "окна" - задается
в знакоместах, - 2h + x < 32);
v - размер "окна" по вертикали (высота "окна" - задается
в знакоместах, - 2v + y < 32);
Процедура задается командой DEF FN l(x,y,h,v) = USR 56700
и вызывается командой RANDOMIZE FN l(x,y,h,v).
Для восстановления исходного изображения, содержащегося в
буфере можно воспользоваться точкой входа в подпрограмму
COPY_D, которая находится по адресу 56957. Задайте функцию без
параметров, например DEF FN m() = USR 56957 и когда хотите
восстановить на экране исходное изображение пользуйтесь
командой RANDOMIZE FN m().
10 REM *** Загрузчик машинного кода
20 LET adr=56700: LET long=285: LET z=0
30 FOR i=0 TO long-1: READ a
40 POKE (adr+i),a: LET z=z+a
50 NEXT i
60 LET z=INT (((z/long)-INT (z/long))*long)
70 READ a
80 IF a<>z THEN PRINT "??": STOP
500 REM ***Данные для машинного кода
510 DATA 42, 11, 92, 1, 4
520 DATA 0, 9, 86, 14, 8
530 DATA 9, 94, 237, 83, 137
540 DATA 222, 9, 126, 50, 140
550 DATA 222, 9, 126, 50, 139
560 DATA 222, 58, 138, 222, 71
570 DATA 58, 140, 222, 128, 230
580 DATA 224, 40, 6, 62, 31
590 DATA 144, 50, 140, 222, 58
600 DATA 137, 222, 71, 58, 139
610 DATA 222, 128, 214, 22, 56
620 DATA 6, 62, 21, 144, 50
630 DATA 139, 222, 237, 91, 137
640 DATA 222, 123, 230, 24, 246
650 DATA 64, 103, 123, 230, 7
660 DATA 183, 31, 31, 31, 31
670 DATA 130, 111, 34, 141, 222
680 DATA 17, 0, 64, 167, 237
690 DATA 82, 17, 0, 118, 25
700 DATA 34, 143, 222, 205, 113
710 DATA 222, 42, 141, 222, 237
720 DATA 91, 143, 222, 58, 139
730 DATA 222, 71, 197, 1, 2
740 DATA 4, 197, 205, 20, 222
750 DATA 193, 16, 249, 42, 141
760 DATA 222, 205, 79, 222, 34
770 DATA 141, 222, 6, 4, 13
780 DATA 32, 235, 237, 91, 143
790 DATA 222, 205, 89, 222, 237
800 DATA 83, 143, 222, 193, 16
810 DATA 217, 201, 58, 140, 222
820 DATA 71, 34, 145, 222, 237
830 DATA 83, 147, 222, 197, 205
840 DATA 54, 222, 193, 16, 249
850 DATA 42, 145, 222, 237, 91
860 DATA 147, 222, 229, 205, 99
870 DATA 222, 225, 36, 36, 20
880 DATA 201, 26, 1, 2, 4
890 DATA 197, 245, 175, 119, 241
900 DATA 23, 245, 203, 22, 241
910 DATA 203, 22, 16, 247, 35
920 DATA 193, 13, 32, 237, 19
930 DATA 201, 62, 32, 133, 111
940 DATA 208, 62, 8, 132, 103
950 DATA 201, 62, 32, 131, 95
960 DATA 208, 62, 8, 130, 87
970 DATA 201, 58, 140, 222, 203
980 DATA 39, 71, 126, 36, 119
990 DATA 37, 35, 16, 249, 201
1000 DATA 33, 0, 64, 17, 0
1010 DATA 118, 1, 0, 26, 237
1020 DATA 176, 201, 33, 0, 118
1030 DATA 17, 0, 64, 1, 0
1040 DATA 26, 237, 176, 201, 2
1050 DATA 2, 5, 10, 130, 72
1060 DATA 226, 118, 98, 78, 194
1070 DATA 125, 0, 0, 0, 0
1080 DATA 38, 0, 0, 0, 0
Дисассемблер программы:
1. На первом этапе процедура принимает параметры и сохра-
няет их в соответствующих ячейках памяти.
56700 2A0B5C LD HL,(5C0BH) ;См. с.109...111
56703 010400 LD BC,0004 ;Сдвиг от DEFADD на 4 бай-
56706 09 ADD HL,BC ;та (см. c.109...111)
56707 56 LD D,(HL) ;Координата x.
56708 0E08 LD C,08 ;Сдвиг на
56710 09 ADD HL,BC ;восемь байтов.
56711 5E LD E,(HL) ;Координата y.
56712 ED5389DE LD(COORDY),DE ;Запомнили x,y.
56716 09 ADD HL,BC ;Сдвиг на восемь байтов.
56717 7E LD A,(HL) ;Параметр h.
56718 328CDE LD (WIDTH),A ;Запомнили его.
56721 09 ADD HL,BC ;Сдвиг на восемь байтов.
56722 7E LD A,(HL) ;Параметр v.
56723 328BDE LD (HEIGHT),A ;Запомнили его.
2. Второй этап - первичные проверки и настройки.
56726 3A8ADE LD A,(COORDX) ;Координата x.
56729 47 LD B,A ;Координата x.
56730 3A8CDE LD A,(WIDTH) ;Ширина окна h.
56733 80 ADD A,B ; x+h
56734 E6E0 AND E0 ;E0H=1110 0000 BIN
;Результатом этой операции
;может быть 0 только в том
;случае, если в аккумуля-
;торе выключены три стар-
;ших бита, то есть h<=31
56736 2806 JR Z,PASS_1 ;В этом случае все O.K. и
;делаем обход на PASS_1.
56738 3E1F LD A,1FH ;31
56740 90 SUB B ;31-x
56741 328CDE LD (WIDTH),A ;hmax=31-x - максимально
;допустимое значение h.
56744 3A89DE PASS_1 LD A,(COORDY) ;y
56747 47 LD B,A ;y
56748 3A8BDE LD A,(HEIGHT) ;v
56751 80 ADD A,B ;y+v
56752 D616 SUB 16H ;Проверка на <22
56754 3806 JR C,PASS_2 ;Если y меньше 22, то все
;O.K. и делаем обход.
56756 3E15 LD A,15H ;21
56758 90 SUB B ;21-y
56759 328BDE LD (HEIGHT),A ;vmax=21-y - вводим макси-
;мально допустимое значе-
;ние v.
3. На третьем этапе по координатам x и y (заданы в знако-
местах) определяем адрес в дисплейном файле, соответствующий
левому верхнему углу выделенного нами "окна" и помещаем его в
регистровую пару HL и в программную переменную ADDR.
56762 ED5B89DE PASS_2 LD DE,(COORDY) ;x,y
56766 7B LD A,E ;Координата y 000?????
56767 E618 AND 18H ;Выделение сег-
;мента экрана 000??000
56769 F640 OR 40 ;Указание на
;дисплейный файл 010??000
56771 67 LD H,A ;Выставили H (cм. рис. )
56772 7B LD A,E ;Координата y 000?????
56773 E607 AND 07 ;Выделили номер
;ряда в сегменте 00000???
56775 B7 OR A ;Очистили флаг "С"
56776 1F RRA ;Вращение 000000??
56777 1F RRA ;Вращение ?000000?
56778 1F RRA ;Вращение ??000000
56779 1F RRA ;Вращение ???00000
56780 82 ADD A,D ;Прибавили номер
;столбца ????????
56781 6F LD L,A ;Младший байт адреса в
;дисплейном файле.
56782 228DDE LD (ADDR),HL ;Запомнили адрес в пере-
;менной ADDR.
4. Зная адрес, с которого начинается наше "окно" в дис-
плейном файле, мы можем теперь сохранить текущее графическое
изображение в верхних областях памяти во временном буфере. В
качестве примера здесь принят начальный адрес буфера 7600 H =
30208 DEC. Но Вы можете поменять его после того как программа
будет уже набрана (если сделать это раньше, может не сойтись
контрольная сумма). О том, как это сделать, мы укажем после
описания процедуры.
Таким образом, в буфере получается копия исходного изобра-
жения.
56785 110040 LD DE 4000 ;Начало дисплейного файла.
56788 A7 AND A ;Очистка флага переноса.
56789 ED52 SBC HL,DE ;Определили "смещение" ад-
;реса начала окна относи-
;тельно начала дисплейного
;файла.
56791 110076 LD DE 7600 ;Начало буфера.
56794 19 ADD HL,DE ;Прибавив к нему смещение,
;получим начальный адрес
;нашего "окна" в копии.
56795 228FDE LD (ADDR_1),HL ;Запомнили этот адрес.
56798 CD71DE CALL COPYUP ;Переброска копии вверх.
5. Теперь, когда у нас есть копия исходного изображения в
буфере, мы можем начать строить удвоенное изображение на экра-
не, перебрасывая байт за байтом из буфера на экран и производя
при этом удвоение его образа как по горизонтали, так и по вер-
тикали. Логика работы при этом очень похожа на логику программ
FN (d) и FN e(), которые печатали текст символами двойного раз-
мера. Фактическая разница в том, что там образ брался из гене-
ратора шрифта ПЗУ, а здесь образ берется из буфера.
Сначала организуется цикл по вертикали (по рядам). В
результате вместо одного ряда шириной h исходного изображения
мы будем иметь 2 ряда шириной 2h на экране.
56801 2A8DDE LD HL,(ADDR) ;адрес в дисп. файле
56804 ED5B8FDE LD DE,(ADDR_1) ;адрес в копии
56808 3A8BDE LD A,(HEIGHT) ;v.
56811 47 LD B,A ;Подготавливаем цикл по
;горизонтальным рядам.
;Количество рядов = v.
56812 C5 LOOP_1 PUSH BC ;Вершина цикла по рядам.
56813 010204 LD BC 0402 ;Подготавливаем еще 2 вло-
;женных цикла. Внешний -
;на 2 прохода (C) и внут-
;ренний на 4 прохода (B).
56816 C5 LOOP_2 PUSH BC ;Вершина внутреннего и
;внешнего вложенных циклов
Цикл по горизонтали (по столбцам) для данного ряда органи- зуется в процедуре DOUBLE.
56817 CD14DE CALL DOUBLE ;8 раз вызывается проце-
;дура DOUBLE.
56820 C1 POP BC
56821 10F9 DJNZ LOOP_2 ;Конец внутреннего цикла
;по B (4 прохода).
56823 2A8DDE LD HL,(ADDR) ;Адрес в дисп.файле.
56826 CD4FDE CALL NEW_R ;Переход к новому ряду.
56829 228DDE LD (ADDR),HL ;Запомнили новый адрес
;в дисплейном файле.
56832 0604 LD B,04
56834 0D DEC C ;
56835 20EB JR NZ,LOOP_2 ;Конец внешнего цикла
;по C (2 прохода).
56837 ED5B8FDE LD DE,(ADDR_1) ;Установив новый адрес
56841 CD59DE CALL NEW_R_1 ;в дисплейном файле, мы
56844 ED538FDE LD (ADDR_1),DE ;переходим и к новому
;адресу в буфере.
56848 C1 POP BC
56849 10D9 DJNZ LOOP_1 ;Конец цикла по рядам.
56851 C9 RET
Процедура DOUBLE организует цикл по горизонтали для дан-
ного экранного ряда. Она вызывает процедуру DOUB_L, которая
выполняет цикл по восьми линиям данного знакоместа.
56852 3A8CDE DOUBLE LD A,(WIDTH) ;Ширина "окна"
56855 47 LD B,A ;Ширина "окна"
56856 2291DE LD (ADDR_2),HL ;Временно запомнили адрес
;в дисплейном файле.
56859 ED5393DE LD (ADDR_3),DE ;Временно запомнили адрес
;в копии дисплейного файла
56863 C5 LOOP_3 PUSH BC ;Вершина цикла по ширине
;"окна".
56864 CD36DE CALL DOUB_L ;Удвоение линии по гори-
;зонтали.
56867 C1 POP BC
56868 10F9 DJNZ LOOP_3 ;Конец цикла по ширине
;"окна".
56870 2A91DE LD HL,(ADDR_2) ;Перед выходом восстано-
56873 ED5B93DE LD DE,(ADDR_3) ;вили испорченные при ра-
;боте процедуры значения
;в HL и DE.
56877 E5 PUSH HL
Мы удвоили линию по горизонтали, растянув ее в два раза по
ширине экрана. Каждому пикселу исходной линии (из буфера)
соответствует пара пикселов в новой линии (на экране). Теперь,
чтобы удвоить ее и по вертикали, повторяем ее еще раз на один
пиксел ниже. Этим занимается процедура REPEAT.
56878 CD63DE CALL REPEAT ;Повторение линии.
56881 E1 POP HL
56882 24 INC H
56883 24 INC H
56884 14 INC D
56885 C9 RET
Процедура DOUB_L выполняет удвоение одной линии в одном
знакоместе. Побитная раскладка линии берется сверху (DE) и с
удвоением опускается в дисплейный файл (HL).
Ее логику работы мы разобрали на стр. 132 и здесь не
будем на ней останавливаться.
56886 1A DOUB_L LD A,(DE)
56887 010204 LD BC,0402
56890 C5 LOOP_4 PUSH BC
56891 F5 PUSH AF
56892 AF XOR A
56893 77 LD (HL),A
56894 F1 POP AF
56895 17 LOOP_5 RLA
56896 F5 PUSH AF
56897 CB16 RL (HL)
56899 F1 POP AF
56900 CB16 RL (HL)
56902 10F7 DJNZ LOOP_5
56904 23 INC HL
56905 C1 POP BC
56906 0D DEC C
56907 20ED JR NZ,LOOP_4
56909 13 INC DE
56910 C9 RET
По адресу начала экранного ряда, содержащегося в HL, про-
цедура NEW_R определяет адрес начала следующего нижележащего
ряда. При этом учитывается, что следующий ряд может принадле-
жать другому экранному сегменту.
56911 3E20 NEW_R LD A,20H ;Переход к новому ряду
56913 85 ADD A,L ;Проверка на переполнение
56914 6F LD L,A ;экранного сегмента.
56915 D0 RET NC ;Если все O.K., то возврат
56916 3E08 LD A,08 ;В противном случае изме-
;няется значение в регист-
56918 84 ADD A,H ;ре H, т.е. выполняется
56919 67 LD H,A ;переход к новому сегменту
56920 C9 RET ;Возврат в вызывающую про-
;цедуру.
Как процедура NEW_R отыскивала адрес начала очередного
ряда в дисплейном файле, точно так же процедура NEW_R_1 отыс-
кивает адрес начала нового ряда в копии исходного файла. т.е. в
буфере.
56921 3E20 NEW_R_1LD A,20H ;Переход к новому ряду
56923 83 ADD A,E ;Проверка на переполнение
56924 5F LD E,A ;экранного сегмента.
56925 D0 RET NC ;Если все O.K., то возврат
56926 3E08 LD A,08 ;В противном случае изме-
;няется значение в регист-
56928 82 ADD A,D ;ре H, т.е. выполняется
56929 57 LD D,A ;переход к новому сегменту
56930 C9 RET ;Возврат в вызывающую про-
;цедуру.
Процедура REPEAT повторяет на экране текущую линию на один
пиксел ниже.
56931 3A8CDE REPEAT LD A,(WIDTH) ;Ширина "окна".
56934 CB27 SLA A ;Удвоенная ширина
56936 47 LD B,A ;"окна".
56937 7E LOOP_6 LD A,(HL) ;Адрес в дисплейном файле.
56938 24 INC H ;Опустились на одну линию
;ниже.
56939 77 LD (HL),A ;Повторили изображение
;верхней линии.
56940 25 DEC H ;Вернулись к верхней линии
56941 23 INC HL ;Перешли к следующему зна-
;коместу.
56942 10F9 DJNZ LOOP_6 ;Повторяем процесс, пока
;пройдем всю ширину окна.
56944 C9 RET ;Возврат в вызывающую
;процедуру.
Процедура COPYUP копирует содержимое дисплейного файла в
отведенный буфер.
56945 210040 COPYUP LD HL,4000H ;Адрес источника - начало
;дисплейного файла.
56948 110076 LD DE,7600H ;Адрес места назначения -
;30208 (можно изменить).
56951 01001A LD BC,1A00H ;Количество перебрасывемых
;байтов.
56954 EDB0 LDIR ;Команда на переброску.
56956 C9 RET ;Выход.
Процедура COPY_D восстанавливает первоначальное изображе-
ние из буфера на экран путем копирования вниз.
56957 210076 COPY_D LD HL,7600H ;Адрес источника - начало
;буфера (можно изменить).
56960 110040 LD DE,4000H ;Адрес места назначения -
;начало дисплейного файла.
56963 01001A LD BC,1A00H ;Количество перебрасывемых
;байтов.
56966 EDB0 LDIR ;Команда на переброску.
56968 C9 RET ;Выход.
56969 COORDY DEFB ;Вертикальная координата
;левого верхнего угла
;"окна", подлежащего
;увеличению.
56970 COORDX DEFB ;Горизонтальная координата
;левого верхнего угла
;исходного "окна".
56971 HEIGHT DEFB ;Высота "окна" (в знако-
;местах.
56972 WIDTH DEFB ;Ширина "окна" (в знако-
;местах).
56973 ADDR DEFW ;Адрес в дисплейном фай-
;ле, соответствующий
;текущей координате печати
56975 ADDR_1 DEFW ;Адрес в буферном фай-
;ле, соответствующий
;текущей координате печати
56977 ADDR_2 DEFW ;То же, что и ADDR, но для
;временного хранения во
;время работы процедуры
;DOUBLE.
56979 ADDR_3 DEFW ;То же, что и ADDR_1, но
;для временного хранения
;во время работы
;процедуры DOUBLE.
В заключение описания этой процедуры мы должны сказать
несколько слов о буфере, в котором хранится исходное изображе-
ние. Здесь предполагается, что он начинается с адреса 30208. На
это значение настроен машинный код, приведенный в загручике
процедуры в строках DATA, на него же настроена проверка конт-
рольной суммы в этом загрузчике. После того, как Вы введете
процедуру в память, ничто уже не может помешать Вам изменить
этот адрес так, как Вам удобно и выгрузить перенастроенную про-
цедуру на ленту. В процессе работы программы Вы можете динами-
чески из БЕЙСИКа менять начальный адрес этого буфера оператора-
ми POKE, обращая внимание на то, чтобы этот буфер не затер со-
держащийся в памяти где-либо машинный код или Вашу БЕЙСИК-про-
грамму.
Изменения надо внести в ячейки:
56792,56793 - в главной процедуре;
56949,56950 - в процедуре COPYUP;
56958,56959 - в процедуре COPY_D.
При внесении изменений не забудьте, что в машинном коде
хранится сначала младший байт адреса и только потом старший.
Так, если Вам надо разместить буфер начиная с адреса 42005, то
Вы можете найти старший байт:
HB = INT (42005/256) = 164
и младший байт:
LB = 42005 - HB*256 = 42005 - 164*256 = 21
Изменения в коде выполните оператором POKE:
POKE 56792,19 POKE 56793,164 POKE 56949,19
POKE 56950,164 POKE 56958,19 POKE 56959,164
Мы не станем приводить примеров работы этой процедуры. Вы
с успехом можете сами выполнить любое доступное Вам изображение
на экране и, дав команду увеличить его в два раза, а потом вер-
нуться к исходному. Мы только ограничимся следующими рекоменда-
циями:
1. Если увеличиваемое изображение - многоцветное, то Вам
не избежать проблем с "клэшингом" атрибутов, ведь процедура
FN l(x,y,h,v) растягивает в два раза только пиксельное изобра-
жение и не трогает атрибутов. Поэтому желательно, чтобы увели-
чиваемое изображение было бы монохромным.
2. Имеет смысл установить цвет PAPER увеличенного изобра-
жения. Для этого прежде, чем делать увеличение в два раза, це-
лесообразно предварительно окрасить "удвоенное окно" заданным
Вами цветом PAPER, для чего можно воспользоваться процедурой
FN c(x,y,h,v,c,b,f).
3. Увеличенное изображение будет лучше смотреться, если
его выделить из общего поля экрана с помощью прямоугольной рам-
ки, для чего после окрашивания в цвет PAPER можно воспользо-
ваться процедурой изображения прямоугольников FN h(x,y,h,v).