Здесь приведены тексты процедур для вывода изображений шестью различными способами, рассмотренными в статье. Исходное изображение (256x192, с 256 градациями яркости) должно быть предварительно помещено в три банка ОЗУ, номера которых (точнее, числа, выводимые в порт #7FFD для их подключения) указываются в массиве N_BANK (по умолчанию там #10, #11 и #13). Информация о пикселах хранится «слева направо, сверху вниз»: в первом банке верхняя треть изображения, во втором средняя, в третьем нижняя.
Перед использованием процедур вы должны определить яркости градаций для вашей системы «компьютер-монитор» (см. Приложение 2) и поместить полученные значения в массивы BRIGHT_0 и BRIGHT_1. По умолчанию там находятся значения для моей системы, которые не будут в точности совпадать с вашими. Можно, конечно, оставить и их, но качество вывода будет хуже.
В процедурах, использующих multicolor, все задержки рассчитаны на «Пентагон». Для другой модели компьютера их, скорее всего, придётся изменить.
| 1. | Процедура для вывода изображения в
|
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 | 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 это будет короче и быстрее.
Как сформировать таблицу
При запуске программы требуется указать нужное значение гаммы. Одновременно с вычислением таблицы на экране строится график зависимости яркости пиксела от его значения в изображении для заданного значения гаммы. После окончания вычислений укажите имя файла, куда будет записана таблица, или просто нажмите «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).
При этом в любых двух частях количество включённых (как и выключенных) пикселов будет различаться не более чем на один. Если каждую часть опять разделить на четыре, это условие будет выполняться и для полученных меньших частей, и так далее.
Очевидно, что если номер текстуры (обозначим его n) кратен 4 (а значит, и количество включённых пикселов кратно 4), то единственный вариант их расположения в четвертях текстуры, удовлетворяющий приведённому выше условию, по n/4 пикселов в каждой четверти.
Пусть номер текстуры не кратен 4, m остаток от деления. Тогда, чтобы условие равномерности выполнялось, в m четвертях должно быть по [n/4]+1 пикселов, а в оставшихся 4m четвертях по [n/4] пикселов. Положим для определённости, что пикселы располагаются в трёх возможных случаях (когда m=1, 2 или 3) следующим образом (учитывая второе свойство соседние текстуры различаются лишь одним пикселом!):
|
|
|
Приведённой выше информации достаточно как для того, чтобы построить все N текстур, так и для того, чтобы определить, включён или выключен пиксел с данными координатами, находящийся в определённой текстуре.
Алгоритм очень простой. Пусть известны L длина стороны текстуры, n номер текстуры, x и y координаты пиксела на экране. Нужно получить a состояние пиксела: 0 выключен, 1 включён.
1. |
Если размер текстуры 1x1, то состояние пиксела равно номеру текстуры (т.е. если L=1, то a=n). |
|||||||||
2. |
Иначе формулируем условия новой, более простой задачи, для вдвое меньшей текстуры, являющейся четвертью исходной (т.е. получаем новые L и n). Ответ новой задачи будет ответом исходной задачи. |
|||||||||
|
В программе этим занимается процедура TEXTURE, длина которой всего 46 байтов. Сравните это с первоначальными 8224 байтами!