Страница Ивана Рощина > Статьи > Вывод чёрно-белых изображений с градациями яркости >

Приложение 3

Здесь приведены тексты процедур для вывода изображений шестью различными способами, рассмотренными в статье. Исходное изображение (256x192, с 256 градациями яркости) должно быть предварительно помещено в три банка ОЗУ, номера которых (точнее, числа, выводимые в порт #7FFD для их подключения) указываются в массиве N_BANK (по умолчанию там #10, #11 и #13). Информация о пикселах хранится «слева направо, сверху вниз»: в первом банке — верхняя треть изображения, во втором — средняя, в третьем — нижняя.

Перед использованием процедур вы должны определить яркости градаций для вашей системы «компьютер-монитор» (см. Приложение 2) и поместить полученные значения в массивы BRIGHT_0 и BRIGHT_1. По умолчанию там находятся значения для моей системы, которые не будут в точности совпадать с вашими. Можно, конечно, оставить и их, но качество вывода будет хуже.

В процедурах, использующих multicolor, все задержки рассчитаны на «Пентагон». Для другой модели компьютера их, скорее всего, придётся изменить.

1.

Процедура для вывода изображения в чёрно-белом режиме с 15 градациями яркости (как на рис. 5). Формирует изображение в экранной области.

           ORG  #6000

           CALL CLS_1

;Начало главного цикла.

           LD   DE,0

MAIN       PUSH DE
           PUSH DE

;Берём исходные данные знакоместа:

           LD   B,8
           LD   HL,PIX_SRC
           CALL GET_DATA

;Обрабатываем:

           LD   HL,PIX_SRC
           LD   DE,PIX_DST
           LD   B,64
           LD   C,9
           CALL WORK_DATA

;Определяем адрес атрибута для текущего
;знакоместа и помещаем туда значение:

           POP  DE
           CALL GET_A_ATR
           LD   (HL),A

;Помещаем информацию о пикселах
;текущего знакоместа:

           LD   B,8
           LD   HL,PIX_DST
MAIN_1     PUSH BC
           PUSH DE

           LD   B,8
MAIN_2     PUSH BC

           LD   C,(HL)
           INC  HL
           LD   B,(HL)
           INC  HL
           CALL SET_TP_1

           INC  D
           POP  BC
           DJNZ MAIN_2

           POP  DE
           INC  E
           POP  BC
           DJNZ MAIN_1

;Переходим к следующему знакоместу:

           POP  DE
           LD   A,D
           ADD  A,8
           LD   D,A
           JR   NZ,MAIN

           LD   A,E
           ADD  A,8
           LD   E,A
           CP   192
           JR   NZ,MAIN

;Конец главного цикла.

           RET

PIX_SRC    DS   8*8
PIX_DST    DS   8*8*2

<далее следует текст общей части>
2.

Процедура для вывода изображения с удвоенным атрибутным разрешением за счёт использования multicolor’а (как на рис. 6). Формирует верхние половины знакомест со своими атрибутами на первом экране, а нижние половины со своими атрибутами — на втором. Затем, когда луч прорисовывает верхние половины знакомест, активным устанавливается первый экран, а когда нижние половины — второй. Выход из режима просмотра — по нажатию любой клавиши.

           ORG  #6000

           CALL CLS_1
           CALL CLS_2

;Начало главного цикла.

           LD   DE,0

MAIN       PUSH DE
           PUSH DE

;Берём исходные данные области 8x4:

           LD   B,4
           LD   HL,PIX_SRC
           CALL GET_DATA

;Обрабатываем:

           LD   HL,PIX_SRC
           LD   DE,PIX_DST
           LD   B,32
           LD   C,9
           CALL WORK_DATA

;В зависимости от того, верхняя или
;нижняя половина знакоместа обрабатыва-
;ется, результат нужно помещать на
;первый или на второй экран. Устанавли-
;ваем адрес соответствующей процедуры:

           POP  DE
           LD   HL,SET_TP_1
           BIT  2,E
           JR   Z,MAIN_A
           LD   HL,SET_TP_2
MAIN_A     LD   (ADR_CALL),HL

;Помещаем атрибут:

           CALL GET_A_ATR
           BIT  2,E
           JR   Z,MAIN_B

           SET  7,H
           EX   AF,AF'
           LD   A,#17
           CALL SETPORT
           EX   AF,AF'

MAIN_B     LD   (HL),A

;Помещаем пикселы:

           LD   B,4
           LD   HL,PIX_DST
MAIN_1     PUSH BC
           PUSH DE

           LD   B,8
MAIN_2     PUSH BC

           LD   C,(HL)
           INC  HL
           LD   B,(HL)
           INC  HL
           CALL SET_TP_1 ;Или SET_TP_2.
ADR_CALL   EQU  $-2

           INC  D
           POP  BC
           DJNZ MAIN_2

           POP  DE
           INC  E
           POP  BC
           DJNZ MAIN_1

;Переходим к следующей области:

           POP  DE
           LD   A,D
           ADD  A,8
           LD   D,A
           JR   NZ,MAIN

           LD   A,E
           ADD  A,4
           LD   E,A
           CP   192
           JR   NZ,MAIN

;Конец главного цикла.

;Вывод изображения: через каждые 4
;строки меняем активный экран.

           CALL ON_IM2

VIEW_LOOP  HALT
           LD   HL,17762
           CALL WAIT

           LD   B,24

VIEW_1     LD   A,#10
           CALL SETPORT

           EXX
           LD   HL,814
           CALL WAIT
           EXX

           LD   A,#18
           CALL SETPORT

           EXX
           LD   HL,795
           CALL WAIT
           EXX

           DJNZ VIEW_1

;Опрос клавиатуры:

           XOR  A
           IN   A,(254)
           CPL
           AND  #1F
           JR   Z,VIEW_LOOP

;Выход при нажатии любой клавиши:

           CALL OFF_IM2
           RET

PIX_SRC    DS   8*4
PIX_DST    DS   8*4*2

<далее следует текст общей части>
3.

Процедура для вывода изображения с максимальным атрибутным разрешением (как на рис. 7). При формировании изображения информация о пикселах записывается в экранную область, а информация об атрибутах (#1800 байтов — свой атрибут для каждой линии знакоместа) записывается в массив ATR (по умолчанию расположенный с адреса #8000). Затем в область атрибутов помещаются атрибуты для первой строки; пока луч прорисовывает первую строку, в область атрибутов помещаются атрибуты второй строки, и так далее. Так как за 224 такта (время прорисовки одной строки) не получается перебросить 32 байта атрибутов, процедура выводит изображение не целиком, а в окне размером 11 знакомест (88 пикселов) по горизонтали. Клавишами «O», «P» окно можно перемещать по изображению влево/вправо. Выход из режима просмотра — по нажатию пробела.

ATR        EQU  #8000 ;Атрибуты (#1800).
LINES      EQU  #7200 ;Адреса атрибутов
                      ;на экране (#180).

           ORG  #6000

           LD   HL,ATR
           LD   (ATR_ADR),HL

           CALL CLS_1

;Устанавливаем PAPER 0: INK 7, чтобы
;можно было видеть процесс построения
;изображения:

           LD   HL,#5800
           LD   (HL),7
           LD   DE,#5801
           LD   BC,#2FF
           LDIR

;Начало главного цикла.

           LD   DE,0

MAIN       PUSH DE
           PUSH DE

;Берём исходные данные области 8x1:

           LD   B,1
           LD   HL,PIX_SRC
           CALL GET_DATA

;Обрабатываем:

           LD   HL,PIX_SRC
           LD   DE,PIX_DST
           LD   B,8
           LD   C,9
           CALL WORK_DATA

;Помещаем атрибут:

           LD   HL,0
ATR_ADR    EQU  $-2
           LD   (HL),A
           INC  HL
           LD   (ATR_ADR),HL

;Помещаем пикселы:

           POP  DE
           LD   HL,PIX_DST

           LD   B,8
MAIN_1     PUSH BC

           LD   C,(HL)
           INC  HL
           LD   B,(HL)
           INC  HL
           CALL SET_TP_1

           INC  D
           POP  BC
           DJNZ MAIN_1

;Переходим к следующей области:

           POP  DE
           LD   A,D
           ADD  A,8
           LD   D,A
           JR   NZ,MAIN

           INC  E
           LD   A,E
           CP   192
           JR   NZ,MAIN

;Конец главного цикла.

;Вывод изображения:

           CALL ON_IM2

VIEW_LOOP  HALT
           LD   (SAVE_SP),SP
           DI

;Очищаем область атрибутов:

           LD   SP,#5B00
           LD   HL,0
           LD   B,48
CLS_ATR    PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           DJNZ CLS_ATR

;Создаём таблицу адресов атрибутов:

MAKE_LT    LD   HL,#5AE0
           LD   DE,0
X          EQU  $-2
           ADD  HL,DE
           LD   SP,LINES+#180
           LD   DE,-#20
           LD   B,24

MAKE_LT_1  PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           ADD  HL,DE
           DJNZ MAKE_LT_1

;Чем больше X, тем больше должна быть
;задержка: X больше на 1 - задержка
;больше на 4 такта.

           LD   HL,(X)
           ADD  HL,HL
           ADD  HL,HL
           LD   DE,9987
           ADD  HL,DE
           CALL WAIT

;Выводим свои атрибуты в каждой строке:

           LD   A,192
           LD   HL,ATR
X_2        EQU  $-2
           LD   SP,LINES
           LD   B,0

;Время выполнения нижеследующего
;участка - 224 такта, что равно времени
;прорисовки одной строки изображения.

ONE_LINE   LD   C,32        ;7
           POP  DE          ;10
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           ADD  HL,BC       ;11
           INC  BC          ;6 (WAIT)
           DEC  A           ;4
           JP   NZ,ONE_LINE ;10

           LD   SP,0
SAVE_SP    EQU  $-2
           EI

;Опрос клавиатуры:

           LD   A,#DF
           IN   A,(254)
           RRA
           JR   C,NE_P

;Нажата клавиша "P" - сдвиг видимого
;участка вправо:

           LD   A,(X)
           CP   21
           JR   NEW_X

NE_P       RRA
           JR   C,NE_O

;Нажата клавиша "O" - сдвиг видимого
;участка влево:

           LD   A,(X)
           SUB  1

NEW_X      ADC  A,0
           LD   (X),A
           LD   (X_2),A
           JP   VIEW_LOOP

;Если нажат пробел - выходим:

NE_O       LD   A,#7F
           IN   A,(254)
           RRA
           JP   C,VIEW_LOOP

           CALL OFF_IM2
           RET

PIX_SRC    DS   8
PIX_DST    DS   8*2

<далее следует текст общей части>
4.

Процедура для вывода изображения с повышенным качеством за счёт быстрого чередования двух изображений (как на рис. 9). Формирует одно изображение на первом экране, другое — на втором, и далее в каждом кадре меняет активный экран. Выход из режима просмотра — по нажатию любой клавиши.

           ORG  #6000

           CALL CLS_1
           CALL CLS_2

;Начало главного цикла.

           LD   DE,0

MAIN       PUSH DE
           PUSH DE

;Берём исходные данные знакоместа:

           LD   B,8
           LD   HL,PIX_SRC
           CALL GET_DATA

;Обрабатываем:

           LD   HL,PIX_SRC
           LD   DE,PIX_DST
           LD   B,64
           LD   C,10
           CALL WORK_DATA

;Записываем атрибут:

           POP  DE
           CALL GET_A_ATR
           LD   (HL),A

;Выводим пикселы:

           LD   B,8
           LD   HL,PIX_DST
           CALL PUT_DATA

;Переходим к следующему знакоместу:

           POP  DE
           LD   A,D
           ADD  A,8
           LD   D,A
           JR   NZ,MAIN

           LD   A,E
           ADD  A,8
           LD   E,A
           CP   192
           JR   NZ,MAIN

;Конец главного цикла.

           CALL A_FLICKER

;Копируем атрибуты на второй экран:

           LD   A,#17
           CALL SETPORT
           LD   HL,#5800
           LD   DE,#D800
           LD   BC,#300
           LDIR

;Переключаем экраны каждые 1/50 секунды:

           LD   A,#10

OUTPUT     HALT
           XOR  8
           CALL SETPORT

           LD   B,A
           XOR  A
           IN   A,(254)
           CPL
           AND  #1F
           LD   A,B
           JR   Z,OUTPUT

           RET

PIX_SRC    DS   8*8
PIX_DST    DS   8*8*2

<далее следует текст общей части>
5.

Процедура для вывода изображения с быстрым чередованием и удвоенным атрибутным разрешением (как на рис. 12). При формировании изображения записывает информацию о пикселах первого изображения на первый экран, а второго изображения — на второй экран. Атрибуты (#600 байтов) записываются в массив ATR. Далее, в первом кадре, когда луч прорисовывает верхние половины знакомест, активным устанавливается первый экран, а когда нижние половины — второй. В следующем кадре, наоборот, верхние половины знакомест берутся со второго экрана, а нижние — с первого, и так далее. Пока луч прорисовывает верхние половины знакомест, на другом экране записываются атрибуты для нижних половин знакомест, а когда луч прорисовывает нижние половины, на другом экране записываются атрибуты для верхних половин следующей строки знакомест, и так далее. Выход из режима просмотра — по нажатию любой клавиши.

ATR        EQU  #8000 ;Атрибуты (#600).

           ORG  #6000

           LD   HL,ATR
           LD   (ATR_ADR),HL
           LD   A,#D8
           LD   (ATR_SCR),A

           CALL CLS_1
           CALL CLS_2

;Устанавливаем PAPER 0: INK 7, чтобы
;можно было видеть процесс построения
;изображения:

           LD   HL,#5800
           LD   (HL),7
           LD   DE,#5801
           LD   BC,#2FF
           LDIR

;Начало главного цикла.

           LD   DE,0

MAIN       PUSH DE
           PUSH DE

;Берём исходные данные половины
;знакоместа:

           LD   B,4
           LD   HL,PIX_SRC
           CALL GET_DATA

;Обрабатываем:

           LD   HL,PIX_SRC
           LD   DE,PIX_DST
           LD   B,32
           LD   C,10
           CALL WORK_DATA

;Записываем атрибут:

           LD   HL,0
ATR_ADR    EQU  $-2
           LD   (HL),A
           INC  HL
           LD   (ATR_ADR),HL

;Выводим пикселы:

           POP  DE
           LD   B,4
           LD   HL,PIX_DST
           CALL PUT_DATA

;Переходим к следующей половине
;знакоместа:

           POP  DE
           LD   A,D
           ADD  A,8
           LD   D,A
           JR   NZ,MAIN

           LD   A,E
           ADD  A,4
           LD   E,A
           CP   192
           JR   NZ,MAIN

;Конец главного цикла.

           CALL A_FLICKER

;Вывод изображения:

           CALL ON_IM2
           LD   XH,#17

VIEW_LOOP  HALT

           LD   HL,16982
           CALL WAIT

           LD   XL,#18
           LD   HL,ATR
           LD   DE,0    ;#5800 или #D800.
ATR_SCR    EQU  $-1
           LD   B,E     ;=LD B,0.

;Выводим атрибуты на один из экранов:

VIEW_4PIX  PUSH DE
           LD   C,#20
           LD   A,D
           XOR  #80
           LD   D,A
VIEW_1     LDI:LDI:LDI:LDI
           LDI:LDI:LDI:LDI
           JP   PE,VIEW_1
           POP  DE

           EXX
           LD   HL,193
           CALL WAIT
           EXX

;Делаем активным этот экран:

           LD   A,XH
           CALL SETPORT

;Выводим атрибуты на другой экран:

           LD   C,#20
VIEW_2     LDI:LDI:LDI:LDI
           LDI:LDI:LDI:LDI
           JP   PE,VIEW_2

           EXX
           LD   HL,246
           CALL WAIT
           EXX

;Делаем активным этот экран:

           LD   A,XH
           XOR  8
           CALL SETPORT

           DEC  XL
           JR   NZ,VIEW_4PIX

;В следующем кадре экраны меняются
;местами:

           LD   A,XH
           XOR  8
           LD   XH,A
           LD   A,(ATR_SCR)
           XOR  #80
           LD   (ATR_SCR),A

;Опрос клавиатуры:

           XOR  A
           IN   A,(254)
           CPL
           AND  #1F
           JR   Z,VIEW_LOOP

;Выход по нажатию любой клавиши:

           CALL OFF_IM2
           RET

PIX_SRC    DS   8*4
PIX_DST    DS   8*4*2

<далее следует текст общей части>
6.

Процедура для вывода изображения с быстрым чередованием и максимальным атрибутным разрешением (как на рис. 13). При формировании изображения записывает информацию о пикселах первого изображения на первый экран, второго изображения — на второй экран, а значения атрибутов (#1800 байтов) — в массив ATR. Затем, как и в процедуре 3, область атрибутов динамически обновляется. В каждом кадре активный экран меняется. Изображение выводится в окне размером 11 знакомест (88 пикселов) по горизонтали. Клавишами «O», «P» окно можно перемещать по изображению влево/вправо. Выход из режима просмотра — по нажатию пробела.

ATR        EQU  #8000 ;Атрибуты (#1800).
LINES      EQU  #7200 ;Адреса атрибутов
                      ;на экране (#180).

           ORG  #6000

           LD   HL,ATR
           LD   (ATR_ADR),HL

           CALL CLS_1
           CALL CLS_2

;Устанавливаем PAPER 0: INK 7, чтобы
;можно было видеть процесс построения
;изображения:

           LD   HL,#5800
           LD   (HL),7
           LD   DE,#5801
           LD   BC,#2FF
           LDIR

;Начало главного цикла.

           LD   DE,0

MAIN       PUSH DE
           PUSH DE

;Берём исходные данные линии знакоместа:

           LD   B,1
           LD   HL,PIX_SRC
           CALL GET_DATA

;Обрабатываем:

           LD   HL,PIX_SRC
           LD   DE,PIX_DST
           LD   B,8
           LD   C,10
           CALL WORK_DATA

;Записываем атрибут:

           LD   HL,0
ATR_ADR    EQU  $-2
           LD   (HL),A
           INC  HL
           LD   (ATR_ADR),HL

;Выводим пикселы:

           POP  DE
           LD   B,1
           LD   HL,PIX_DST
           CALL PUT_DATA

;Переходим к следующей линии знакоместа:

           POP  DE
           LD   A,D
           ADD  A,8
           LD   D,A
           JR   NZ,MAIN

           INC  E
           LD   A,E
           CP   192
           JR   NZ,MAIN

;Конец главного цикла.

           CALL A_FLICKER

;Вывод изображения:

           CALL ON_IM2

VIEW_LOOP  HALT
           LD   (SAVE_SP),SP
           DI

;Очищаем область атрибутов:

CLS_ATR    LD   SP,#5B00
           LD   HL,0
           LD   B,48
CLS_ATR_1  PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           DJNZ CLS_ATR_1

;Создаём таблицу адресов атрибутов:

MAKE_LT    LD   HL,#5AE0
           LD   DE,0
X          EQU  $-2
           ADD  HL,DE
           LD   SP,LINES+#180
           LD   DE,-#20
           LD   B,24

MAKE_LT_1  PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           PUSH HL
           ADD  HL,DE
           DJNZ MAKE_LT_1

;Чем больше X, тем больше должна быть
;задержка: X больше на 1 - задержка
;больше на 4 такта.

           LD   HL,(X)
           ADD  HL,HL
           ADD  HL,HL
           LD   DE,9987
           ADD  HL,DE
           CALL WAIT

;Выводим свои атрибуты в каждой строке:

           LD   A,192
           LD   HL,ATR
X_2        EQU  $-2
           LD   SP,LINES
           LD   B,0

;Время выполнения нижеследующего
;участка - 224 такта, что равно времени
;прорисовки одной строки изображения.

ONE_LINE   LD   C,32        ;7
           POP  DE          ;10
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           LDI              ;16
           ADD  HL,BC       ;11
           INC  BC          ;6 (WAIT)
           DEC  A           ;4
           JP   NZ,ONE_LINE ;10

           LD   SP,0
SAVE_SP    EQU  $-2
           EI

;Задержка, чтобы луч успел прорисовать
;последнюю строку изображения до того,
;как установим другой активный экран:

           LD   HL,224
           CALL WAIT

;Устанавливаем другой экран:

           LD   A,#10
N_SCR      EQU  $-1
           XOR  #0F
           LD   (N_SCR),A
           CALL SETPORT

;Изменяем начальный адрес в процедуре
;очистки области атрибутов и в процедуре
;построения таблицы адресов атрибутов:

           LD   HL,CLS_ATR+2
           LD   A,(HL)
           XOR  #80
           LD   (HL),A

           LD   HL,MAKE_LT+2
           LD   A,(HL)
           XOR  #80
           LD   (HL),A

;Опрос клавиатуры:

           LD   A,#DF
           IN   A,(254)
           RRA
           JR   C,NE_P

;Нажата клавиша "P" - сдвиг видимого
;участка вправо:

           LD   A,(X)
           CP   21
           JR   NEW_X

NE_P       RRA
           JR   C,NE_O

;Нажата клавиша "O" - сдвиг видимого
;участка влево:

           LD   A,(X)
           SUB  1

NEW_X      ADC  A,0
           LD   (X),A
           LD   (X_2),A
           JP   VIEW_LOOP

;Если нажат пробел - выходим:

NE_O       LD   A,#7F
           IN   A,(254)
           RRA
           JP   C,VIEW_LOOP

           CALL OFF_IM2
           RET

PIX_SRC    DS   8
PIX_DST    DS   8*2

<далее следует текст общей части>

Общая часть процедур вывода

GAMMA_CORR EQU  0 ;Выполнять
                  ;гамма-коррекцию?

;---------------------------------------
;Номера банков памяти, в которых
;хранится исходное изображение:

N_BANK     DB   #10,#11,#13

;257-байтная таблица для указания
;адреса процедуры обработки прерываний
;(начинается с адреса, кратного 256):

INT_TAB    EQU  #7000

;Адрес процедуры обработки прерываний
;(старший байт равен младшему):

INT_ADR    EQU  #7171

;Яркости градаций в формате 8.8:

BRIGHT_0   DW   0,1112,2139,4412,6062
           DW   14555,26811,46556

BRIGHT_1   DW   0,1369,3129,6062,8995
           DW   21704,38045,65280

;---------------------------------------
;Процедуры очистки экрана:

CLS_1      LD   HL,#4000  ;1-й экран.
           LD   (HL),L
           LD   DE,#4001
           JR   CLS

CLS_2      LD   A,#17     ;2-й экран.
           CALL SETPORT
           LD   HL,#C000
           LD   (HL),L
           LD   DE,#C001

CLS        LD   BC,#1AFF
           LDIR
           RET

;---------------------------------------
;Процедура GET_DATA - поместить в буфер
;информацию о прямоугольной области
;изображения шириной 8 пикселов.
;
;Вход: DE - координаты левого верхнего
;           угла области,
;       B - высота области,
;      HL - адрес буфера.

GET_DATA   PUSH BC
           PUSH DE

           LD   B,8
GET_DATA1  CALL GET_1R
           LD   (HL),A
           INC  HL
           INC  D
           DJNZ GET_DATA1

           POP  DE
           INC  E
           POP  BC
           DJNZ GET_DATA

           RET

;---------------------------------------
;Процедура PUT_DATA выводит пикселы
;прямоугольной области изображения на
;первый и второй экраны (для способов
;с быстрой сменой двух экранов).
;
;Вход: DE - координаты левого верхнего
;           угла области,
;       B - высота области,
;      HL - адрес данных о пикселах.

PUT_DATA   PUSH BC
           PUSH DE

           LD   B,8
PUT_D_1    PUSH BC

           LD   C,(HL)
           INC  HL
           LD   B,(HL)
           INC  HL

;Если BC>=256, ставим на первый экран
;включённый пиксел и уменьшаем BC на
;256:

           INC  B   ;Если B=0, то BC<256.
           DEC  B
           JR   Z,PUT_D_2

           DEC  B       ;Т.е. BC:=BC-256.
           CALL SET_P_1

;Теперь BC - номер текстуры (0-256).
;Ставим на второй экран пиксел с этой
;текстурой:

PUT_D_2    CALL SET_TP_2
           INC  D
           POP  BC
           DJNZ PUT_D_1

           POP  DE
           INC  E
           POP  BC
           DJNZ PUT_DATA

           RET

;---------------------------------------
;Процедура WORK_DATA определяет по
;данным о некоторой области пикселов,
;какой атрибут использовать при её
;отображении, и какая псевдоградация
;будет наиболее близка по яркости для
;каждого пиксела.
;
;Осуществляется гамма-коррекция по
;таблице GAMMA_T (если GAMMA_CORR=1).
;
;Вход: HL - адрес исходных данных
;           (1 байт на пиксел),
;       B - кол-во пикселов,
;       C - количество псевдоградаций
;           (257 -> C=9; 513 -> C=10),
;      DE - адрес формируемых данных
;           (2 байта на пиксел).
;
;Выход: A - значение атрибутов, с DE
;           сформированы данные.

;Локальные переменные (8.8):

MIN_PIX    DS   2 ;Наим. яркость исходн.
MAX_PIX    DS   2 ;Наиб. яркость исходн.

MIN        DS   2 ;Яркость PAPER.
MAX        DS   2 ;Яркость INK.

;Пошла сама процедура:

WORK_DATA  PUSH DE
           PUSH HL
           LD   A,B
           LD   (N_PIX_1),A
           LD   A,C
           LD   (DIV_CNT),A

;Определяем наименьшее и наибольшее
;значения яркости.

           LD   DE,#FF00 ;Нач. значения.

WORK_ZN_1  LD   A,(HL)
           CP   D
           JR   NC,WORK_ZN_2
           LD   D,A
WORK_ZN_2  CP   E
           JR   C,WORK_ZN_3
           LD   E,A
WORK_ZN_3  INC  HL
           DJNZ WORK_ZN_1

;D - наименьшее значение яркости,
;E - наибольшее (без коррекции).

;Производим гамма-коррекцию и записываем
;скорректированные значения в MIN_PIX и
;MAX_PIX:

 IF GAMMA_CORR

           LD   H,GAMMA_T/256
           LD   L,D
           LD   B,(HL)
           INC  H
           LD   C,(HL)
           LD   (MIN_PIX),BC
           LD   L,E
           LD   C,(HL)
           DEC  H
           LD   B,(HL)
           LD   (MAX_PIX),BC

 ELSE

           LD   B,D
           LD   C,0
           LD   (MIN_PIX),BC
           LD   B,E
           LD   (MAX_PIX),BC

 ENDIF

;Находим наименьший промежуток, в
;котором содержатся минимальное и
;максимальное значения.

;Если MAX_PIX больше BRIGHT_0 (7), то
;таблицу BRIGHT_0 однозначно не
;рассматриваем:

           LD   HL,(BRIGHT_0+14)
           AND  A
           SBC  HL,BC
           JR   NC,TWO_TABL

           LD   HL,BRIGHT_1
           CALL DEF_MM
           LD   A,1
           JR   OK_MINMAX

;Рассматриваем обе таблицы:

TWO_TABL   LD   HL,BRIGHT_1
           CALL DEF_MM
           PUSH IX      ;Сохр. в стеке.
           EXX
           LD   HL,BRIGHT_0
           CALL DEF_MM

;Сравниваем HL и HL':

           LD   (SAVE_HL),HL
           EXX
           PUSH DE
           PUSH HL
           LD   DE,0
SAVE_HL    EQU  $-2
           XOR  A
           SBC  HL,DE
           POP  HL
           POP  DE
           ADC  A,0
           JR   NZ,OK_MM

           EXX
           POP  HL ;IX в стеке не нужно.
           JR   OK_MINMAX

OK_MM      POP  IX ;Восстанавливаем IX.

OK_MINMAX  LD   (MIN),DE
           LD   (MAX),BC

;A - значение BRIGHT (0 или 1)
;XH,XL - номера цветов.
;Формируем значение атрибута:

           RRCA        ;0-й бит
           RRCA        ;становится 6-м.
           LD   B,A
           LD   A,XH
           ADD  A,A
           ADD  A,A
           ADD  A,A    ;PAPER
           ADD  A,XL   ;+INK
           ADD  A,B    ;+BRIGHT

;Обработка пикселов:

           POP  HL   ;SOURCE
           POP  DE   ;DESTINY
           LD   B,0  ;COUNTER
N_PIX_1    EQU  $-1

           PUSH AF   ;Сохранили атрибуты.

PIX_LOOP   PUSH HL

 IF GAMMA_CORR

           LD   L,(HL)
           LD   H,GAMMA_T/256
           LD   A,(HL)
           INC  H
           LD   L,(HL)
           LD   H,A

 ELSE

           LD   H,(HL)
           LD   L,0

 ENDIF

           EX   (SP),HL
           INC  HL

           EXX

           LD   HL,(MAX)
           LD   DE,(MIN)
           AND  A
           SBC  HL,DE

;Так как MAX>MIN, флаг C сброшен.

           LD   B,H
           LD   C,L

;BC - MAX-MIN в 256-х долях.

           POP  HL      ;Текущая яркость.
           SBC  HL,DE

;HL - текущая яркость-MIN в 256-х долях.

;Делим HL на BC, получая инвертированное
;частное (в 256-х или 512-х долях) в IX:

           LD   XL,#FF
           LD   DE,0
           LD   A,0   ;Счётчик: 9 или 10.
DIV_CNT    EQU  $-1

DIV_LOOP   EX   AF,AF'

           LD   A,D
           SUB  E
           LD   D,A
           SBC  HL,BC
           JR   NC,DIV_1

           LD   A,D
           ADD  A,E
           LD   D,A
           ADC  HL,BC

DIV_1      LD   A,XL
           ADC  A,A
           LD   XL,A
           LD   A,XH
           ADC  A,A
           LD   XH,A
           SRL  B
           RR   C
           RR   E

           EX   AF,AF'
           DEC  A
           JR   NZ,DIV_LOOP

;Записываем результат, предварительно
;проинвертировав его:

           EXX

           LD   A,XL
           CPL
           LD   (DE),A
           INC  DE
           LD   A,XH
           CPL
           LD   (DE),A
           INC  DE
           DJNZ PIX_LOOP

           POP  AF   ;Атрибуты.
           RET

;---------------------------------------
;Процедура DEF_MM
;
;Вход: HL указывает на таблицу яркостей,
;      MIN_PIX - миним. яркость
;      MAX_PIX - макс. яркость.
;
;Выход: XH - номер яркости min,
;       XL - номер яркости max,
;       DE - миним. яркость,
;       BC - макс. яркость,
;       HL - разница между ними.
;
;(Проверка, что значения D и E должны
;быть <= максимального значения в
;таблице, должна выполняться перед
;вызовом!)

DEF_MM     LD   IX,#08FF

;XL=8, XH=-1 - первоначальные номера
;цветов с минимальной и максимальной
;яркостью.

           PUSH HL
           LD   DE,15
           ADD  HL,DE

;Сейчас HL указывает на последнее,
;наибольшее значение в таблице.
;Просматриваем таблицу от конца к
;началу, пока MIN_PIX не окажется больше
;или равно очередному значению.

           LD   DE,(MIN_PIX)

DEF_MM_1   DEC  XH
           LD   B,(HL)
           DEC  HL
           LD   C,(HL)
           DEC  HL
           PUSH DE
           EX   DE,HL
           AND  A
           SBC  HL,BC
           EX   DE,HL
           POP  DE
           JR   C,DEF_MM_1

           LD   H,B
           LD   L,C     ;Яркость мин.
           EX   (SP),HL

;Сейчас XH - номер яркости min.

;Просматривая таблицу от начала до
;конца, отыскиваем первое число, большее
;или равное MAX_PIX. Так как значения в
;таблице возрастают, оно же будет и
;наименьшим таким числом.

           LD   BC,(MAX_PIX)

DEF_MM_2   INC  XL
           LD   E,(HL)
           INC  HL
           LD   D,(HL)
           INC  HL
           EX   DE,HL
           AND  A
           SBC  HL,BC
           EX   DE,HL
           JR   C,DEF_MM_2

;Вычисляем разницу:

           EX   DE,HL
           ADD  HL,BC  ;Яркость макс.
           POP  DE     ;Яркость мин.

           PUSH HL
           AND  A
           SBC  HL,DE
           POP  BC

           RET

;---------------------------------------
;SET_TP_1, SET_TP_2 - процедуры вывода
;точки с заданной текстурой соответ-
;ственно на первый и второй экран.
;
;Вход: BC - номер текстуры (0-256),
;      D - X, E - Y.
;
;Выход: DE, HL - без изменений.

SET_TP_1   PUSH HL
           LD   HL,SET_P_1
           JR   SET_TP

SET_TP_2   PUSH HL
           LD   HL,SET_P_2

SET_TP     LD   (SET_YES+1),HL
           DEC  B
           JR   Z,SET_YES ;Если 256.

           LD   B,16
           CALL TEXTURE

           AND  A
           JR   Z,SET_NO

SET_YES    CALL SET_P_1 ;Или SET_P_2.

SET_NO     POP  HL
           RET

;---------------------------------------
;SET_P_1 и SET_P_2 - процедуры, ставящие
;точку соответственно на первый и второй
;экран.

SET_P_1    PUSH HL
           CALL BYTE
           JR   SET_P

SET_P_2    PUSH HL
           LD   A,#17
           CALL SETPORT
           CALL BYTE
           SET  7,H

SET_P      OR   (HL)
           LD   (HL),A
           POP  HL
           RET

;---------------------------------------
;Процедура TEXTURE определяет по
;заданной текстуре и координатам пиксела
;на экране, включён этот пиксел или
;выключен.
;
;Вход: B - длина стороны текстуры
;          (число вида 2^n),
;      C - номер текстуры,
;      D,E - координаты пиксела: x,y.
;
;Выход: A - состояние пиксела:
;           0 - выключен, 1 - включён.
;
;DE не изменяется.

TEXTURE

;Если длина стороны текстуры равна 1,
;то состояние пиксела равно номеру
;текстуры:

           SRL  B   ;Делим длину на 2.
           LD   A,C ;Номер текстуры.
           RET  Z   ;Выход, если длина
                    ;была равна 1.

;   Формулируем условия новой, более
;простой задачи, для вдвое меньшей
;текстуры, являющейся четвертью
;исходной. Ответ новой задачи будет
;ответом исходной задачи.
;   Длина стороны новой текстуры уже
;вычислена и находится в B. Осталось
;определить номер новой текстуры. Он
;равен целой части от деления номера
;исходной текстуры на 4, возможно,
;увеличенной на 1 (это зависит от
;полученного при делении остатка и от
;того, в какой четверти оказывается
;пиксел).

;Включаем в регистре H бит с номером
;четверти, в которой оказывается пиксел.
;Нумерация четвертей: 0 1
;                     2 3.

           LD   HL,#0101

           LD   A,B
           AND  D
           JR   Z,TX_1

           INC  H      ;H:=2

TX_1       LD   A,B
           AND  E
           JR   Z,TX_2

           SLA  H
           SLA  H

;Включаем в регистре L биты с номерами
;тех четвертей, где номер текстуры на 1
;больше.

TX_2       LD   A,C
           SRL  C
           SRL  C
           AND  3
           JR   Z,TEXTURE ;Нет таких.

           DEC  A
           JR   Z,PLUS_1 ;0 четверть.

           SET  3,L
           DEC  A
           JR   Z,PLUS_1 ;0,3 четверти.

           SET  1,L      ;0,1,3 четверти.

;Теперь проверяем: в той четверти, где
;оказался пиксел, номер текстуры должен
;быть на 1 больше? Если да, увеличиваем
;его:

PLUS_1     LD   A,H
           AND  L
           JR   Z,TEXTURE

           INC  C
           JR   TEXTURE

;---------------------------------------

BYTE       LD   A,E
           AND  A
           RRA
           SCF
           RRA
           AND  A
           RRA
           XOR  E
           AND  #F8
           XOR  E
           LD   H,A
           LD   A,D
           RLCA
           RLCA
           RLCA
           XOR  E
           AND  #C7
           XOR  E
           RLCA
           RLCA
           LD   L,A
           LD   A,D
           AND  7
           LD   B,A
           INC  B
           LD   A,1
LOOP       RRCA
           DJNZ LOOP
           RET

;---------------------------------------
;Процедура GET_A_ATR определяет адрес в
;области атрибутов.
;
;Вход:  DE - координаты в пикселах.
;Выход: HL - адрес в области атрибутов,
;       остальные регистры не изменены.

GET_A_ATR  PUSH AF
           PUSH DE
           LD   H,0
           LD   A,E
           AND  %11111000
           LD   L,A
           ADD  HL,HL
           ADD  HL,HL
           LD   A,D
           RRA
           RRA
           RRA
           AND  %00011111
           LD   E,A
           LD   D,#58
           ADD  HL,DE
           POP  DE
           POP  AF
           RET

;---------------------------------------
;Процедура GET_1R - прочитать значение
;одного пиксела исходного изображения.
;
;Вход:  D - X, E - Y.
;Выход: A - значение,
;       BC,DE,HL - без изменений.

GET_1R     PUSH BC
           PUSH DE
           PUSH HL

           LD   A,D ;Меняем местами.
           LD   D,E
           LD   E,A

           LD   A,D
           RLCA
           RLCA
           AND  3

           LD   HL,N_BANK
           LD   B,0
           LD   C,A
           ADD  HL,BC

           LD   A,(HL)   ;Установили
           CALL SETPORT  ;нужный
                         ;банк памяти.
           SET  7,D
           SET  6,D

;DE указывает на адрес, где хранится
;значение пиксела.

           LD   A,(DE)

           POP  HL
           POP  DE
           POP  BC
           RET

;---------------------------------------
;Процедура A_FLICKER - установка
;мерцающих пикселов в противофазе.

A_FLICKER  LD   A,#17
           CALL SETPORT

           LD   HL,#4000 ;1 изображение.
           LD   DE,#C000 ;2 изображение.
           LD   C,L

FL_MAIN    LD   B,#80    ;Маска.
           LD   A,(DE)
           XOR  (HL)

;Теперь в A установлены биты, соответ-
;ствующие пикселам, различающимся на
;первом и втором экране, т.е. мерцающим.

FL_PIX     RLA
           JR   NC,FL_NEXT

           EX   AF,AF'
           LD   A,B

;Младший бит регистра C указывает, на
;каком экране включать пиксел, а на
;каком выключать. Командой INC C его
;значение меняется на противоположное
;при обработке каждого мерцающего
;пиксела.

           INC  C
           BIT  0,C
           JR   NZ,FL_1

;На первом экране включаем пиксел, на
;втором - выключаем:

           OR   (HL)
           LD   (HL),A
           EX   DE,HL
           LD   A,B
           CPL
           AND  (HL)
           JR   FL_2

;На первом экране выключаем пиксел, на
;втором - включаем:

FL_1       CPL
           AND  (HL)
           LD   (HL),A
           EX   DE,HL
           LD   A,B
           OR   (HL)

FL_2       LD   (HL),A
           EX   DE,HL
           EX   AF,AF'

FL_NEXT    SRL  B
           JR   NZ,FL_PIX

           INC  HL
           INC  DE
           LD   A,H
           CP   #58
           JR   NZ,FL_MAIN

           RET

;---------------------------------------

SETPORT    PUSH BC
           LD   BC,#7FFD
           OUT  (C),A
           POP  BC
           RET

 IF GAMMA_CORR

;---------------------------------------
;512-байтная таблица гамма-коррекции
;(для gamma=1,5), адрес расположения
;кратен 256. В таблице сначала следуют
;старшие байты всех 256 значений, а
;потом младшие.

           DS   256-$\256 ;Выравнивание.

GAMMA_T

 db #00,#00,#00,#00,#00,#00,#00,#01
 db #01,#01,#01,#02,#02,#02,#03,#03
 db #04,#04,#04,#05,#05,#06,#06,#06
 db #07,#07,#08,#08,#09,#09,#0A,#0A
 db #0B,#0B,#0C,#0C,#0D,#0E,#0E,#0F
 db #0F,#10,#11,#11,#12,#12,#13,#14
 db #14,#15,#16,#16,#17,#18,#18,#19
 db #1A,#1A,#1B,#1C,#1D,#1D,#1E,#1F
 db #20,#20,#21,#22,#23,#23,#24,#25
 db #26,#27,#27,#28,#29,#2A,#2B,#2B
 db #2C,#2D,#2E,#2F,#30,#31,#31,#32
 db #33,#34,#35,#36,#37,#38,#39,#39
 db #3A,#3B,#3C,#3D,#3E,#3F,#40,#41
 db #42,#43,#44,#45,#46,#47,#48,#49
 db #4A,#4B,#4C,#4D,#4E,#4F,#50,#51
 db #52,#53,#54,#55,#56,#57,#58,#59
 db #5A,#5B,#5C,#5D,#5E,#60,#61,#62
 db #63,#64,#65,#66,#67,#68,#69,#6B
 db #6C,#6D,#6E,#6F,#70,#71,#73,#74
 db #75,#76,#77,#78,#7A,#7B,#7C,#7D
 db #7E,#7F,#81,#82,#83,#84,#85,#87
 db #88,#89,#8A,#8C,#8D,#8E,#8F,#90
 db #92,#93,#94,#95,#97,#98,#99,#9B
 db #9C,#9D,#9E,#A0,#A1,#A2,#A4,#A5
 db #A6,#A7,#A9,#AA,#AB,#AD,#AE,#AF
 db #B1,#B2,#B3,#B5,#B6,#B7,#B9,#BA
 db #BB,#BD,#BE,#BF,#C1,#C2,#C4,#C5
 db #C6,#C8,#C9,#CA,#CC,#CD,#CF,#D0
 db #D1,#D3,#D4,#D6,#D7,#D9,#DA,#DB
 db #DD,#DE,#E0,#E1,#E3,#E4,#E5,#E7
 db #E8,#EA,#EB,#ED,#EE,#F0,#F1,#F3
 db #F4,#F6,#F7,#F9,#FA,#FC,#FD,#FF

 db #00,#10,#2D,#53,#80,#B3,#EC,#29
 db #6B,#B1,#FB,#49,#9A,#EF,#48,#A3
 db #02,#64,#C8,#30,#9A,#07,#76,#E8
 db #5D,#D4,#4D,#C9,#47,#C8,#4A,#CF
 db #56,#DF,#6A,#F7,#87,#18,#AB,#41
 db #D8,#71,#0C,#A8,#47,#E7,#8A,#2E
 db #D3,#7B,#24,#CF,#7B,#2A,#DA,#8B
 db #3E,#F3,#A9,#61,#1B,#D6,#92,#50
 db #10,#D1,#94,#58,#1D,#E4,#AD,#77
 db #42,#0F,#DD,#AD,#7E,#50,#24,#F9
 db #CF,#A7,#80,#5A,#36,#13,#F1,#D1
 db #B2,#94,#78,#5D,#43,#2A,#12,#FC
 db #E7,#D3,#C1,#AF,#9F,#90,#83,#76
 db #6B,#61,#58,#50,#49,#44,#3F,#3C
 db #3A,#39,#39,#3A,#3D,#40,#45,#4B
 db #52,#5A,#63,#6D,#78,#84,#92,#A0
 db #B0,#C0,#D2,#E5,#F9,#0D,#23,#3A
 db #52,#6B,#85,#A0,#BC,#D9,#F7,#16
 db #36,#57,#79,#9C,#C0,#E5,#0B,#32
 db #5A,#83,#AD,#D8,#04,#31,#5F,#8D
 db #BD,#EE,#1F,#52,#85,#BA,#EF,#25
 db #5D,#95,#CE,#08,#43,#7F,#BB,#F9
 db #38,#77,#B7,#F9,#3B,#7E,#C2,#07
 db #4D,#93,#DB,#23,#6C,#B7,#02,#4D
 db #9A,#E8,#36,#86,#D6,#27,#79,#CC
 db #1F,#74,#C9,#20,#77,#CE,#27,#81
 db #DB,#36,#92,#EF,#4D,#AC,#0B,#6B
 db #CC,#2E,#91,#F4,#58,#BD,#23,#8A
 db #F1,#5A,#C3,#2D,#97,#03,#6F,#DC
 db #4A,#B9,#28,#99,#0A,#7B,#EE,#61
 db #D6,#4A,#C0,#37,#AE,#26,#9F,#18
 db #93,#0E,#89,#06,#83,#02,#80,#00

 ENDIF

;---------------------------------------
;Процедура WAIT - задержка от 188 до
;65535 тактов. Должна располагаться
;с адреса, кратного 256.
;
;Вход: HL - время задержки в тактах
;(сюда уже входит время выполнения
;команд загрузки HL и вызова процедуры).

           DS   256-$\256 ;Выравнивание.

TAB_ADR    DB   ADR_0\256
           DB   ADR_1\256
           DB   ADR_2\256
           DB   ADR_3\256
           DB   ADR_4\256
           DB   ADR_5\256
           DB   ADR_6\256
           DB   ADR_7\256
           DB   ADR_8\256
           DB   ADR_9\256
           DB   ADR_10\256
           DB   ADR_11\256
           DB   ADR_12\256
           DB   ADR_13\256
           DB   ADR_14\256
           DB   ADR_15\256
           DB   ADR_16\256
           DB   ADR_17\256
           DB   ADR_18\256
           DB   ADR_19\256
           DB   ADR_20\256
           DB   ADR_21\256
           DB   ADR_22\256
           DB   ADR_23\256
           DB   ADR_24\256
           DB   ADR_25\256
           DB   ADR_26\256
           DB   ADR_27\256
           DB   ADR_28\256
           DB   ADR_29\256
           DB   ADR_30\256
           DB   ADR_31\256

ADR_28     NOP
ADR_24     NOP
ADR_20     NOP
ADR_16     NOP
ADR_12     NOP
ADR_8      NOP
ADR_4      NOP
ADR_0      NOP          ;4 такта.
           RET

ADR_29     NOP
ADR_25     NOP
ADR_21     NOP
ADR_17     NOP
ADR_13     NOP
ADR_9      NOP
ADR_5      NOP
ADR_1      RET  NZ      ;5 тактов.
           RET

ADR_30     NOP
ADR_26     NOP
ADR_22     NOP
ADR_18     NOP
ADR_14     NOP
ADR_10     NOP
ADR_6      NOP
ADR_2      INC  HL      ;6 тактов.
           RET

ADR_31     NOP
ADR_27     NOP
ADR_23     NOP
ADR_19     NOP
ADR_15     NOP
ADR_11     NOP
ADR_7      NOP
ADR_3      LD   A,(HL)  ;7 тактов.
           RET

WAIT       LD   DE,-156
           ADD  HL,DE
           LD   A,L
           AND  31
           LD   E,A

;Чтобы разделить HL на 32, достаточно
;сдвинуть HL на 5 разрядов вправо, но
;есть более короткий и быстрый способ:
;сдвинуть HL на 3 разряда влево, помещая
;выдвигаемые старшие биты результата
;в аккумулятор, и затем произвести
;перестановку: A -> H -> L.

           XOR  A
           ADD  HL,HL
           RLA
           ADD  HL,HL
           RLA
           ADD  HL,HL
           RLA
           LD   L,H
           LD   H,A

;Цикл с временем выполнения 32 такта:

WAIT_1     DEC  HL
           LD   A,H
           OR   L
           NOP       ;Для
           NOP       ;задержки.
           JP   NZ,WAIT_1

           EX   DE,HL
           LD   H,TAB_ADR/256
           LD   L,(HL)
           JP   (HL)

;---------------------------------------
;Процедуры включения и выключения
;второго режима прерываний:

ON_IM2     LD   HL,INT_TAB
           LD   (HL),INT_ADR/256
           LD   DE,INT_TAB+1
           LD   BC,#100
           LDIR
           LD   A,INT_TAB/256
           LD   I,A
           LD   HL,#C9FB   ;EI:RET
           LD   (INT_ADR),HL
           IM   2
           RET

OFF_IM2    IM   1
           LD   A,#3F
           LD   I,A
           RET

Комментарии

Не все подпрограммы из общей части используются в каждой процедуре вывода. Для уменьшения объёма неиспользуемые подпрограммы можно удалить. Сведения о том, какие именно подпрограммы не используются в каждой из шести процедур вывода, приведены в таблице:

Табл. 1
Номер процедуры вывода Подпрограммы, не используемые в этой процедуре
1 CLS_2, SET_TP_2, SET_P_2, ON_IM2, OFF_IM2, WAIT, PUT_DATA, A_FLICKER
2 PUT_DATA, A_FLICKER
3 GET_A_ATR, CLS_2, SET_TP_2, SET_P_2, PUT_DATA, A_FLICKER
4 ON_IM2, OFF_IM2, WAIT
5 GET_A_ATR
6 GET_A_ATR

Как выводить изображение с увеличением? Если требуется увеличение в два раза (размер выводимой области изображения тогда будет 128x96), то в начало процедуры GET_1R, после команд PUSH, добавьте такой фрагмент:

        LD   A,D
        SRL  A
        ADD  A,координата X левого верхнего угла области
        LD   D,A

        LD   A,E
        SRL  A
        ADD  A,координата Y левого верхнего угла области
        LD   E,A

Если нужно увеличение в 4, 8… раз (размер выводимой области тогда будет соответственно 64x48, 32x24…), то ставим не одну, а две, три… команды SRL A.

Кстати, для увеличения в два раза вышеприведённый фрагмент можно оптимизировать: вначале прибавить удвоенную координату, а затем разделить на два командой RRA вместо SRL A — это будет короче и быстрее.


Как сформировать таблицу гамма-коррекции (GAMMA_T) для нужного значения гаммы? Ниже приведена соответствующая программа на Бейсике. Эта программа формирует таблицу в том же формате, в котором она хранится в общей части процедур вывода: значения в формате 8.8, в первых 256 байтах — целые части, в следующих 256 — дробные. Общая длина таблицы, таким образом, составляет 512 байтов.

При запуске программы требуется указать нужное значение гаммы. Одновременно с вычислением таблицы на экране строится график зависимости яркости пиксела от его значения в изображении для заданного значения гаммы. После окончания вычислений укажите имя файла, куда будет записана таблица, или просто нажмите «Enter» для перезапуска программы.

      10 CLEAR 32767
      15 CLS
      20 INPUT "Gamma: ";G
      30 FOR i=0 TO 255
      40 LET y=(i/255)^G
      50 LET Word= INT (y*255*256+0.5)
      60 LET HighByte= INT (Word/256)
      70 LET LowByte= Word-HighByte*256
      80 POKE 32768+i, HighByte
      90 POKE 32768+256+i, LowByte
     100 PLOT i*175/255,y*175
     110 NEXT i
     120 INPUT "File: ";a$
     130 IF a$="" THEN GO TO 15
     140 RANDOMIZE USR 15619: REM :SAVE a$ CODE 32768,512

При выводе изображения для получения псевдоградаций яркости используются 257 различных текстур 16x16. Простой подсчёт показывает, что для их хранения требуется 257*16*16/8=8224 байта памяти, или более 8 КБ. Тратить столько памяти не хотелось, и я использовал такой приём: когда нужно узнать, включён или выключен пиксел с данными координатами, находящийся в определённой текстуре, ответ получается с помощью специального алгоритма, основанного на свойствах текстур. Представляется интересным рассмотреть этот алгоритм подробнее.

Сначала — о свойствах, которым должны удовлетворять текстуры, используемые для получения псевдоградаций яркости (в общем случае). Пусть L — длина стороны текстуры, причём L является числом вида 2k (в данном случае L=16=24). Количество текстур N равно L2+1 (в данном случае N=162+1=257).

Во-первых, в каждой следующей текстуре на один включённый пиксел больше, чем в предыдущей. Если пронумеровать текстуры от 0 до N–1, то номер текстуры будет равен числу включённых в ней пикселов.

Во-вторых, любые две соседние текстуры отличаются лишь одним пикселом. Учитывая предыдущее свойство, получаем, что каждая следующая текстура получается из предыдущей путем включения ещё одного пиксела.

В-третьих, включённые и выключенные пикселы в каждой текстуре располагаются равномерно. Что это значит? Возьмём текстуру и разделим её на четыре равные части:

Рис. 20
   
   

При этом в любых двух частях количество включённых (как и выключенных) пикселов будет различаться не более чем на один. Если каждую часть опять разделить на четыре, это условие будет выполняться и для полученных меньших частей, и так далее.

Очевидно, что если номер текстуры (обозначим его n) кратен 4 (а значит, и количество включённых пикселов кратно 4), то единственный вариант их расположения в четвертях текстуры, удовлетворяющий приведённому выше условию, — по n/4 пикселов в каждой четверти.

Пусть номер текстуры не кратен 4, m — остаток от деления. Тогда, чтобы условие равномерности выполнялось, в m четвертях должно быть по [n/4]+1 пикселов, а в оставшихся 4–m четвертях — по [n/4] пикселов. Положим для определённости, что пикселы располагаются в трёх возможных случаях (когда m=1, 2 или 3) следующим образом (учитывая второе свойство — соседние текстуры различаются лишь одним пикселом!):

Рис. 21
а) m=1

[n/4]+1


 [n/4] 


 [n/4] 


 [n/4] 

б) m=2

[n/4]+1


 [n/4] 


 [n/4] 


[n/4]+1

в) m=3

[n/4]+1


[n/4]+1


 [n/4] 


[n/4]+1

Приведённой выше информации достаточно как для того, чтобы построить все N текстур, так и для того, чтобы определить, включён или выключен пиксел с данными координатами, находящийся в определённой текстуре.

Алгоритм очень простой. Пусть известны L — длина стороны текстуры, n — номер текстуры, x и y — координаты пиксела на экране. Нужно получить a — состояние пиксела: 0 — выключен, 1 — включён.

1.

Если размер текстуры — 1x1, то состояние пиксела равно номеру текстуры (т.е. если L=1, то a=n).

2.

Иначе формулируем условия новой, более простой задачи, для вдвое меньшей текстуры, являющейся четвертью исходной (т.е. получаем новые L и n). Ответ новой задачи будет ответом исходной задачи.

2.1.

Если n кратно 4, то L:=L/2, n:=n/4, и решаем задачу с этими условиями.

2.2.

Иначе определяем, в какой четверти текстуры оказывается пиксел. Если (L and x) = 0, то пиксел в левой половине, иначе в правой. Если (L and y) = 0, то пиксел в верхней половине, иначе в нижней.

2.3.

По m — остатку от деления n на 4 — определяем, в каких четвертях будет включено [n/4] пикселов, а в каких — [n/4]+1 (см. рис. 21).

2.4.

Если в той четверти текстуры, где оказывается пиксел, включено [n/4] пикселов, то L:=L/2, n:=[n/4], и решаем задачу с этими условиями.

2.5.

Иначе L:=L/2, n:=[n/4]+1, и решаем задачу с этими условиями.

В программе этим занимается процедура TEXTURE, длина которой всего 46 байтов. Сравните это с первоначальными 8224 байтами!

Страница Ивана Рощина > Статьи > Вывод чёрно-белых изображений с градациями яркости >