ZXNet эхоконференция «code.zx»


тема: Вывод черно-белых изображений с градациями яркости



от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 01 .C ══════════════════

(c) Иван Рощин, Москва

Fido : 2:5020/689.53
ZXNet : 500:95/462.53
E-mail: bestview@mtu-net.ru
WWW : http://www.ivr.da.ru

Вывод черно-белых изображений с градациями яркости
══════════════════════════════════════════════════

("Радиомир. Ваш компьютер" 8-10/2002)
(Дата последнего редактирования: 28.09.2002)

Как вы, наверно, уже догадались :), речь пойдет о различных
способах вывода на экран ZX Spectrum черно-белых изображений с
градациями яркости. Для определенности положим, что размер
изображения - 256*192, что соответствует разрешению экрана ZX
Spectrum, а количество градаций яркости - 256.
Понятно, что о реальных 256 градациях яркости на ZX Spectrum
можно только мечтать, так что вывести мы сможем лишь что-то
похожее на исходное изображение.
А как определить, какой способ вывода лучше? Можно, конечно,
сравнивать исходное и получаемое изображения "на глаз". Hо
гораздо удобнее иметь объективный критерий - вычисляемую по
некоторой формуле величину (обозначим ее R), отражающую различие
между этими изображениями: от 0 (изображения совпадают) до 1
(максимальное различие).
О том, как именно я определил эту величину, вы можете
прочесть в Приложении 1. Далее рядом с каждым изображением,
полученным при использовании того или иного способа вывода, я
буду приводить значение R.
Да, а какое же изображение возьмем в качестве исходного? Вот
оно:

┌─────────────────────────┐
│ bw_s_1.pcx │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 1

Итак, первый способ вывода. Все пикселы с яркостью меньше
128 выводим черными, а с яркостью 128 и больше - белыми:

┌─────────────────────────┐
│ bw_s_2.pcx │
│ │
│ │
│ │ R = 0,0906
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 2

Следующий способ - с использованием так называемых "чанков".
В каждом квадрате 4*4 выводим одну из 17 текстур с наиболее
близким уровнем яркости (в первой текстуре все 16 пикселов
черные, во второй - 15 черных и 1 белый... в последней - все 16
пикселов белые). Таким образом, получается 17 псевдоградаций
яркости.

┌─────────────────────────┐
│ bw_s_3.pcx │
│ │
│ │
│ │ R = 0,0708
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 3

Рассматривать такое изображение (как и изображения,
получаемые с использованием далее описанных способов) желательно
не с близкого расстояния, когда оно воспринимается как мешанина
пикселов, а издалека, когда яркость соседних пикселов
усредняется. Если невозможно отойти от монитора на достаточное
расстояние, или с такого расстояния изображение кажется очень
маленьким, то можно добиться того же эффекта, "размыв"
изображение - для этого достаточно установить перед экраном
фильтр из полупрозрачной полиэтиленовой пленки.

Еще один способ вывода. Для каждого пиксела (а не для
участка 4*4, как в предыдущем способе) определяем наиболее
близкую по яркости текстуру и выводим на экран пиксел из этой
текстуры. Координаты пиксела, который берется из текстуры,
определяются как остатки от деления координат пиксела в
изображении на размер текстуры.
Hа рис. 4а приведено изображение, получающееся при
использовании 17 текстур 4*4, а на рис. 4б - при использовании
256 текстур 16*16 (вообще-то их 257, а не 256, но, так как в
исходном изображении лишь 256 градаций яркости, одна из текстур
не используется).

┌─────────────────────────┐ ┌─────────────────────────┐
│ bw_s_4a.pcx │ │ bw_s_4b.pcx │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
└─────────────────────────┘ └─────────────────────────┘

R = 0,0683 R = 0,0680

а) б)

Рис. 4

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 01 .C ══════════════════

(c) Иван Рощин, Москва

Fido : 2:5020/689.53
ZXNet : 500:95/462.53
E-mail: bestview@mtu-net.ru
WWW : http://www.ivr.da.ru

Вывод черно-белых изображений с градациями яркости
══════════════════════════════════════════════════

("Радиомир. Ваш компьютер" 8-10/2002)
(Дата последнего редактирования: 28.09.2002)

Как вы, наверно, уже догадались :), речь пойдет о различных
способах вывода на экран ZX Spectrum черно-белых изображений с
градациями яркости. Для определенности положим, что размер
изображения - 256*192, что соответствует разрешению экрана ZX
Spectrum, а количество градаций яркости - 256.
Понятно, что о реальных 256 градациях яркости на ZX Spectrum
можно только мечтать, так что вывести мы сможем лишь что-то
похожее на исходное изображение.
А как определить, какой способ вывода лучше? Можно, конечно,
сравнивать исходное и получаемое изображения "на глаз". Но
гораздо удобнее иметь объективный критерий - вычисляемую по
некоторой формуле величину (обозначим ее R), отражающую различие
между этими изображениями: от 0 (изображения совпадают) до 1
(максимальное различие).
О том, как именно я определил эту величину, вы можете
прочесть в Приложении 1. Далее рядом с каждым изображением,
полученным при использовании того или иного способа вывода, я
буду приводить значение R.
Да, а какое же изображение возьмем в качестве исходного? Вот
оно:

┌─────────────────────────┐
│ bw_s_1.pcx │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 1

Итак, первый способ вывода. Все пикселы с яркостью меньше
128 выводим черными, а с яркостью 128 и больше - белыми:

┌─────────────────────────┐
│ bw_s_2.pcx │
│ │
│ │
│ │ R = 0,0906
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 2

Следующий способ - с использованием так называемых "чанков".
В каждом квадрате 4*4 выводим одну из 17 текстур с наиболее
близким уровнем яркости (в первой текстуре все 16 пикселов
черные, во второй - 15 черных и 1 белый... в последней - все 16
пикселов белые). Таким образом, получается 17 псевдоградаций
яркости.

┌─────────────────────────┐
│ bw_s_3.pcx │
│ │
│ │
│ │ R = 0,0708
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 3

Рассматривать такое изображение (как и изображения,
получаемые с использованием далее описанных способов) желательно
не с близкого расстояния, когда оно воспринимается как мешанина
пикселов, а издалека, когда яркость соседних пикселов
усредняется. Если невозможно отойти от монитора на достаточное
расстояние, или с такого расстояния изображение кажется очень
маленьким, то можно добиться того же эффекта, "размыв"
изображение - для этого достаточно установить перед экраном
фильтр из полупрозрачной полиэтиленовой пленки.

Еще один способ вывода. Для каждого пиксела (а не для
участка 4*4, как в предыдущем способе) определяем наиболее
близкую по яркости текстуру и выводим на экран пиксел из этой
текстуры. Координаты пиксела, который берется из текстуры,
определяются как остатки от деления координат пиксела в
изображении на размер текстуры.
На рис. 4а приведено изображение, получающееся при
использовании 17 текстур 4*4, а на рис. 4б - при использовании
256 текстур 16*16 (вообще-то их 257, а не 256, но, так как в
исходном изображении лишь 256 градаций яркости, одна из текстур
не используется).

┌─────────────────────────┐ ┌─────────────────────────┐
│ bw_s_4a.pcx │ │ bw_s_4b.pcx │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
└─────────────────────────┘ └─────────────────────────┘

R = 0,0683 R = 0,0680

а) б)

Рис. 4

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 02 .C ══════════════════

Это было, так сказать, вступление. :) Рассматривались уже
известные способы вывода, которые, как вы могли заметить, не
отличаются достаточным качеством. А вот теперь пойдет речь о
том, как выжать из ZX Spectrum все, на что он способен. Я
расскажу о способах, позволяющих повысить качество выводимого
изображения на порядок!
Смотрите: на ZX Spectrum нам доступны 8 цветов с двумя
градациями яркости (bright=0 и bright=1). Так как черный цвет
при bright=1 остается черным, всего получается не 16 цветов, а
15. При черно-белом режиме отображения эти 15 цветов
превращаются в 15 градаций яркости. За счет их использования и
можно повысить качество изображения!
Естественно, для этого нужно, чтобы ZX Spectrum был
подключен к черно-белому монитору или телевизору (или к
цветному, но с возможностью переключения в черно-белый режим).
Hо даже если в вашем случае это не так, все равно советую
дочитать статью до конца: вдруг что-то окажется полезным.
Сразу же отмечу, что основанный на этом эффекте способ
вывода черно-белых изображений с градациями яркости может быть
использован и на отличных от ZX Spectrum компьютерах, на которых
изображение может быть цветным, но нет градаций серого.
Кстати, как мне кажется (на практике не проверял), чтобы
получить черно-белое изображение на цветном мониторе, достаточно
соорудить простейший переходник: три сигнала R, G, B, идущие от
компьютера, преобразуем в один (черно-белый) и подаем его на
входы R, G, B монитора, вот и все. При этом из деталей
понадобятся лишь несколько сопротивлений.

Итак, как выводить изображение? Очевидно, сначала надо
определить яркость каждой из имеющихся 15 градаций - число в
диапазоне 0-255. Рассказ о том, как это сделать, получился
довольно объемным, и я решил вынести его в Приложение 2. А
сейчас будем считать, что яркости вычислены и мы получили
значения a0-a7 для цветов 0-7 при bright=0 и значения b0-b7
для тех же цветов при bright=1.
Так как атрибуты задаются для знакоместа (8*8 пикселов),
изображение будем выводить познакоместно. Рассмотрим процесс
вывода одного знакоместа.
Пусть MIN_PIX и MAX_PIX - соответственно минимальный и
максимальный уровни яркости в текущем знакоместе исходного
изображения. Пусть MIN и MAX - соответственно яркости paper и
ink (при выбранном значении bright) в текущем знакоместе
выводимого изображения. Зная MIN_PIX и MAX_PIX, надо определить
атрибуты знакоместа (ink, paper и bright) так, чтобы выполнялись
условия: MIN <= MIN_PIX, MAX >= MAX_PIX. Очевидно, в большинстве
случаев это можно сделать не единственным способом. Тогда из
всех возможных пар (MIN, MAX) выбираем такую, чтобы разность
между MAX и MIN была наименьшей.
Чтобы не перебирать все 128 возможных сочетаний ink, paper и
bright, удобно поступить следующим образом. Сначала проверяем,
выполняется ли условие MAX_PIX > a(7)? Если да, то bright не
может быть равно 0, т.е. bright=1. Среди b0-b7 отыскиваем
максимальное b(i), при котором выполняется условие b(i) <=
MIN_PIX, и минимальное b(j), при котором выполняется условие
b(j) >= MAX_PIX. Тогда paper=i, а ink=j.
Если же MAX_PIX <= a(7), придется рассмотреть два варианта:
bright=0 и bright=1. Hаходим b(i) и b(j), как было описано выше.
Затем среди a0-a7 отыскиваем максимальное a(k), при котором
выполняется условие a(k) <= MIN_PIX, и минимальное a(l), при
котором выполняется условие a(l) >= MAX_PIX. Теперь надо выбрать
из двух пар (b(i),b(j)), (a(k),a(l)) такую, где разница между
значениями меньше. Если это первая пара, то bright=1, ink=j,
paper=i. Если вторая пара, то bright=0, ink=l, paper=k.
После того, как атрибуты знакоместа определены, остается
узнать, какие пикселы в нем будут цвета ink, а какие - paper. В
предыдущем способе вывода мы получали 257 псевдоградаций яркости
между черным и белым с помощью 257 текстур 16*16. Hу а в этом
способе мы можем получить с помощью тех же текстур 257 псевдо-
градаций между MIN и MAX. Для каждого пиксела определяем
наиболее близкую по яркости текстуру и выводим на экран пиксел
из этой текстуры.
Вот что получается при таком способе вывода. Hе правда ли,
качество изображения значительно повысилось?

┌─────────────────────────┐
│ bw_s_5.pcx │
│ │
│ │
│ │ R = 0,0106
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 5

Процедура, выводящая изображение этим способом, приведена в
Приложении 3. Там же вы найдете процедуры для способов,
описанных далее.

Для дальнейшего повышения качества изображения можно
использовать так называемый multicolor-эффект. Сущность его в
следующем: если изменить атрибут знакоместа, когда луч уже
прорисовал его часть на экране, то оставшаяся часть знакоместа
будет прорисована уже с новым значением атрибута. Так можно
увеличить атрибутное разрешение по вертикали.
Если надо увеличить разрешение вдвое (т.е. задать свой
атрибут для каждой половины знакоместа - области размером 8*4
пиксела), удобнее не перезаписывать значения атрибутов при
прорисовке изображения, а сделать так: построить верхние
половины знакомест со своими атрибутами на одном экране, нижние
половины со своими атрибутами - на другом, а затем переключать
экраны каждый раз, когда прорисуются очередные 4 строки пикселов
(т.е. верхняя или нижняя половина строки знакомест).
Вот что получается при таком способе вывода:

┌─────────────────────────┐
│ bw_s_6.pcx │
│ │
│ │
│ │ R = 0,0090
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 6

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 02 .C ══════════════════

Это было, так сказать, вступление. :) Рассматривались уже
известные способы вывода, которые, как вы могли заметить, не
отличаются достаточным качеством. А вот теперь пойдет речь о
том, как выжать из ZX Spectrum все, на что он способен. Я
расскажу о способах, позволяющих повысить качество выводимого
изображения на порядок!
Смотрите: на ZX Spectrum нам доступны 8 цветов с двумя
градациями яркости (bright=0 и bright=1). Так как черный цвет
при bright=1 остается черным, всего получается не 16 цветов, а
15. При черно-белом режиме отображения эти 15 цветов
превращаются в 15 градаций яркости. За счет их использования и
можно повысить качество изображения!
Естественно, для этого нужно, чтобы ZX Spectrum был
подключен к черно-белому монитору или телевизору (или к
цветному, но с возможностью переключения в черно-белый режим).
Но даже если в вашем случае это не так, все равно советую
дочитать статью до конца: вдруг что-то окажется полезным.
Сразу же отмечу, что основанный на этом эффекте способ
вывода черно-белых изображений с градациями яркости может быть
использован и на отличных от ZX Spectrum компьютерах, на которых
изображение может быть цветным, но нет градаций серого.
Кстати, как мне кажется (на практике не проверял), чтобы
получить черно-белое изображение на цветном мониторе, достаточно
соорудить простейший переходник: три сигнала R, G, B, идущие от
компьютера, преобразуем в один (черно-белый) и подаем его на
входы R, G, B монитора, вот и все. При этом из деталей
понадобятся лишь несколько сопротивлений.

Итак, как выводить изображение? Очевидно, сначала надо
определить яркость каждой из имеющихся 15 градаций - число в
диапазоне 0-255. Рассказ о том, как это сделать, получился
довольно объемным, и я решил вынести его в Приложение 2. А
сейчас будем считать, что яркости вычислены и мы получили
значения a0-a7 для цветов 0-7 при bright=0 и значения b0-b7
для тех же цветов при bright=1.
Так как атрибуты задаются для знакоместа (8*8 пикселов),
изображение будем выводить познакоместно. Рассмотрим процесс
вывода одного знакоместа.
Пусть MIN_PIX и MAX_PIX - соответственно минимальный и
максимальный уровни яркости в текущем знакоместе исходного
изображения. Пусть MIN и MAX - соответственно яркости paper и
ink (при выбранном значении bright) в текущем знакоместе
выводимого изображения. Зная MIN_PIX и MAX_PIX, надо определить
атрибуты знакоместа (ink, paper и bright) так, чтобы выполнялись
условия: MIN <= MIN_PIX, MAX >= MAX_PIX. Очевидно, в большинстве
случаев это можно сделать не единственным способом. Тогда из
всех возможных пар (MIN, MAX) выбираем такую, чтобы разность
между MAX и MIN была наименьшей.
Чтобы не перебирать все 128 возможных сочетаний ink, paper и
bright, удобно поступить следующим образом. Сначала проверяем,
выполняется ли условие MAX_PIX > a(7)? Если да, то bright не
может быть равно 0, т.е. bright=1. Среди b0-b7 отыскиваем
максимальное b(i), при котором выполняется условие b(i) <=
MIN_PIX, и минимальное b(j), при котором выполняется условие
b(j) >= MAX_PIX. Тогда paper=i, а ink=j.
Если же MAX_PIX <= a(7), придется рассмотреть два варианта:
bright=0 и bright=1. Находим b(i) и b(j), как было описано выше.
Затем среди a0-a7 отыскиваем максимальное a(k), при котором
выполняется условие a(k) <= MIN_PIX, и минимальное a(l), при
котором выполняется условие a(l) >= MAX_PIX. Теперь надо выбрать
из двух пар (b(i),b(j)), (a(k),a(l)) такую, где разница между
значениями меньше. Если это первая пара, то bright=1, ink=j,
paper=i. Если вторая пара, то bright=0, ink=l, paper=k.
После того, как атрибуты знакоместа определены, остается
узнать, какие пикселы в нем будут цвета ink, а какие - paper. В
предыдущем способе вывода мы получали 257 псевдоградаций яркости
между черным и белым с помощью 257 текстур 16*16. Ну а в этом
способе мы можем получить с помощью тех же текстур 257 псевдо-
градаций между MIN и MAX. Для каждого пиксела определяем
наиболее близкую по яркости текстуру и выводим на экран пиксел
из этой текстуры.
Вот что получается при таком способе вывода. Не правда ли,
качество изображения значительно повысилось?

┌─────────────────────────┐
│ bw_s_5.pcx │
│ │
│ │
│ │ R = 0,0106
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 5

Процедура, выводящая изображение этим способом, приведена в
Приложении 3. Там же вы найдете процедуры для способов,
описанных далее.

Для дальнейшего повышения качества изображения можно
использовать так называемый multicolor-эффект. Сущность его в
следующем: если изменить атрибут знакоместа, когда луч уже
прорисовал его часть на экране, то оставшаяся часть знакоместа
будет прорисована уже с новым значением атрибута. Так можно
увеличить атрибутное разрешение по вертикали.
Если надо увеличить разрешение вдвое (т.е. задать свой
атрибут для каждой половины знакоместа - области размером 8*4
пиксела), удобнее не перезаписывать значения атрибутов при
прорисовке изображения, а сделать так: построить верхние
половины знакомест со своими атрибутами на одном экране, нижние
половины со своими атрибутами - на другом, а затем переключать
экраны каждый раз, когда прорисуются очередные 4 строки пикселов
(т.е. верхняя или нижняя половина строки знакомест).
Вот что получается при таком способе вывода:

┌─────────────────────────┐
│ bw_s_6.pcx │
│ │
│ │
│ │ R = 0,0090
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 6

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 03 .C ══════════════════

Очевидно, максимум возможного - свой атрибут для каждой
линии знакоместа (8 пикселов). Вот получаемое при этом
изображение:

┌─────────────────────────┐
│ bw_s_7.pcx │
│ │
│ │
│ │ R = 0,0071
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 7

При практической реализации максимального атрибутного
разрешения, однако, возникает проблема. Дело в том, что за 224
такта (время прорисовки одной строки) нужно обновить 32 байта
атрибутов строки, а сделать это не так-то просто: обычные
способы пересылки данных требуют гораздо больше времени. В
процедуре, приведенной в Приложении 3, я решил эту проблему так:
выводится не все изображение, а окно шириной 11 знакомест,
которое можно перемещать вправо-влево. Вы можете попробовать
увеличить ширину выводимой области изображения, используя
способы быстрой пересылки данных - например, описанные в [5].

Есть еще один способ улучшения качества. Можно сформировать
такие два изображения, чтобы среднее между ними было наиболее
похоже на оригинал, а затем быстро чередовать их - при этом
зритель как раз и увидит среднее изображение.
Пусть атрибуты обоих изображений одинаковы. Если раньше
яркость пиксела в знакоместе могла быть одной из двух: MIN или
MAX, то при быстром чередовании двух изображений она может быть
уже одной из трех: MIN, MAX или (MIN+MAX)/2 (когда на одном
изображении яркость пиксела MIN, а на другом - MAX). За счет
этого и повышается качество. Если раньше мы получали 257
псевдоградаций яркости от MIN до MAX с помощью 257 различных
текстур 16*16, то теперь получим 257 псевдоградаций от MIN до
(MIN+MAX)/2 и еще 257 - от (MIN+MAX)/2 до MAX, а всего, таким
образом, будет 513 псевдоградаций от MIN до MAX.
При формировании таких двух изображений их атрибуты
(они одинаковы) определяются так же, как и раньше. Для каждого
пиксела определяем, какая из 513 псевдоградаций (от 0 до 512)
наиболее близка к нему по яркости. Если номер псевдоградации
меньше 256, то на первом изображении соответствующий пиксел
выключаем, а на втором - берем из текстуры с номером, равным
номеру псевдоградации. Если же номер псевдоградации больше или
равен 256, то на первом изображении пиксел включаем, а на
втором - берем из текстуры с номером, равным номеру
псевдоградации, уменьшенному на 256.
Используя этот способ для вывода исходного изображения,
получим такие два изображения:

┌─────────────────────────┐ ┌─────────────────────────┐
│ bw_s_8a.pcx │ │ bw_s_8b.pcx │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
└─────────────────────────┘ └─────────────────────────┘

а) б)

Рис. 8

При их быстром чередовании мы увидим среднее изображение -
вот оно:

┌─────────────────────────┐
│ bw_s_9.pcx │
│ │
│ │
│ │ R = 0,0026
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 9

Однако, рассматривая его, вы будете разочарованы из-за
довольно заметного мерцания с частотой 25 Гц. Можно ли его
уменьшить, и если да, то как? Попробую объяснить на примере.
Пусть надо получить в некоторой области экрана серый цвет за
счет быстрой смены белого и черного. Можно сделать так: в одном
кадре показывать всю эту область белой, а в другом - черной
(рис. 10а). Мерцание при этом будет очень заметно. А можно в
одном кадре выводить в этой области "шахматную" текстуру, а в
другом кадре - такую же текстуру, но в которой вместо белых
пикселов - черные и наоборот (рис. 10б). Тогда мерцание будет
гораздо меньше, а издалека - вообще не будет заметно.

┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│████████│ │ │ │██ ██ │ │ ██ ██│
│████████│ + │ │ │ ██ ██│ + │██ ██ │
│████████│ │ │ │██ ██ │ │ ██ ██│
│████████│ │ │ │ ██ ██│ │██ ██ │
└────────┘ └────────┘ └────────┘ └────────┘

а) б)

Рис. 10

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 04 .C ══════════════════

Почему так получается? При рассматривании изображения
издалека яркость соседних пикселов усредняется, а так как во
втором случае соседние пикселы мерцают в противофазе, то их
средняя яркость и в одном, и в другом кадре будет одинаковой,
и в результате изображение не будет мерцать.
Иначе говоря, средняя яркость достаточно малых участков на
первом и втором изображениях должна быть примерно одинаковой, а
достигнуть этого можно, если соседние мерцающие пикселы будут в
противофазе.
В нашем случае (рис. 8) изображения сильно отличаются
друг от друга, и поэтому мерцание при их смене очень заметно.
Чтобы изображения стали более похожими друг на друга, сделаем
следующее: просматривая пикселы (для определенности, слева
направо и сверху вниз), определим мерцающие (т.е. различающиеся
на первом и втором изображениях); первый такой пиксел сделаем
темным на первом изображении и светлым на втором, следующий -
наоборот, светлым на первом изображении и темным на втором (т.е.
он будет в противофазе с предыдущим), и так далее.
Вот во что превращаются приведенные на рис. 8 изображения
после такой обработки:

┌─────────────────────────┐ ┌─────────────────────────┐
│ bw_s_11a.pcx │ │ bw_s_11b.pcx │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
└─────────────────────────┘ └─────────────────────────┘

а) б)

Рис. 11

Сразу видно, что теперь они гораздо более похожи друг на
друга. Мерцание при их быстрой смене становится значительно
меньше, а издалека - вообще не заметно. Результирующее
изображение будет, разумеется, тем же самым (рис. 9): ведь от
перемены мест слагаемых сумма не меняется.
Кстати, вот совет, как не видеть мерцания даже с близкого
расстояния. Возьмите лист бумаги, проткните в нем дырку и
смотрите на экран сквозь эту дырку. Количество света,
попадающего в глаз, будет меньше, и мерцание сильно уменьшится.

Быстрое чередование двух изображений можно совместить с
multicolor'om, что еще повысит качество. Вот что получится,
когда для каждой половины знакоместа задается свой атрибут:

┌─────────────────────────┐
│ bw_s_12.pcx │
│ │
│ │
│ │ R = 0,0022
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 12

А вот - когда свой атрибут задается для каждой линии
знакоместа:

┌─────────────────────────┐
│ bw_s_13.pcx │
│ │
│ │
│ │ R = 0,0017
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 13

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 05 .C ══════════════════

Описанные выше способы иллюстрировались на примере одного
исходного изображения. Hиже приведены примеры для еще нескольких
изображений (слева - исходное, справа - получающееся при
выводе). Использовался способ с быстрым чередованием двух
изображений и multicolor'ом, атрибуты задавались для каждой
линии знакоместа.
(Кстати, сканирование фотографий, послуживших исходными
изображениями, выполнил по моей просьбе Алексей Летаев -
спасибо!)

┌─────────────────────────┐┌─────────────────────────┐
│ bw_s_14a.pcx ││ bw_s_14b.pcx │
│ ││ │
│ ││ │
│ ││ │R = 0,0020
│ ││ │
│ ││ │
│ ││ │
│ ││ │
└─────────────────────────┘└─────────────────────────┘
┌─────────────────────────┐┌─────────────────────────┐
│ bw_s_14c.pcx ││ bw_s_14d.pcx │
│ ││ │
│ ││ │
│ ││ │R = 0,0021
│ ││ │
│ ││ │
│ ││ │
│ ││ │
└─────────────────────────┘└─────────────────────────┘
┌─────────────────────────┐┌─────────────────────────┐
│ bw_s_14e.pcx ││ bw_s_14f.pcx │
│ ││ │
│ ││ │
│ ││ │R = 0,0029
│ ││ │
│ ││ │
│ ││ │
│ ││ │
└─────────────────────────┘└─────────────────────────┘
┌─────────────────────────┐┌─────────────────────────┐
│ bw_s_14g.pcx ││ bw_s_14h.pcx │
│ ││ │
│ ││ │
│ ││ │R = 0,0023
│ ││ │
│ ││ │
│ ││ │
│ ││ │
└─────────────────────────┘└─────────────────────────┘

Рис. 14

Чтобы увидеть на изображении еще больше подробностей, можно,
во-первых, использовать вывод с увеличением, и, во-вторых,
повысить контрастность выводимого участка изображения.
При выводе с увеличением мы не сможем увидеть сразу всю
картинку, но за счет этого на видимом фрагменте сможем различить
больше деталей. Вот пример: на рис. 15а показана центральная
часть рис. 5, для удобства сравнения увеличенная вдвое, а на
рис. 15б - то, что получится, если увеличивать вдвое центральную
часть непосредственно в процессе вывода.

┌─────────────────────────┐ ┌─────────────────────────┐
│ bw_s_15a.pcx │ │ bw_s_15b.pcx │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
└─────────────────────────┘ └─────────────────────────┘

а) б)

Рис. 15

Приведенные в Приложении 3 процедуры вывода могут выводить
указанную область изображения с увеличением в 2, 4, 8... раз.
Думаю, вам не составит труда догадаться, как увеличивать
изображение в количество раз, не являющееся степенью двойки.
Теперь о повышении контрастности. Если в выводимом
изображении (или в выводимой части изображения) самая темная
точка светлее 0 и/или самая светлая точка темнее 255, то можно
"растянуть" диапазон яркости до 0 - 255 по следующей формуле:

A-Amin
A'= 255 ───────────
Amax-Amin

где A - исходная яркость,
A' - новая яркость,
Amin - яркость самой темной точки,
Amax - яркость самой светлой точки.

После такого преобразования мы увидим темные участки
изображения темнее, чем надо, а светлые - светлее, но при этом
можно будет различить больше подробностей. Hа рис. 16 вы можете
увидеть изображение, полученное, как и рис. 15б, но с повышением
контрастности.

┌─────────────────────────┐
│ bw_s_16.pcx │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 16

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 05 .C ══════════════════

Описанные выше способы иллюстрировались на примере одного
исходного изображения. Ниже приведены примеры для еще нескольких
изображений (слева - исходное, справа - получающееся при
выводе). Использовался способ с быстрым чередованием двух
изображений и multicolor'ом, атрибуты задавались для каждой
линии знакоместа.
(Кстати, сканирование фотографий, послуживших исходными
изображениями, выполнил по моей просьбе Алексей Летаев -
спасибо!)

┌─────────────────────────┐┌─────────────────────────┐
│ bw_s_14a.pcx ││ bw_s_14b.pcx │
│ ││ │
│ ││ │
│ ││ │R = 0,0020
│ ││ │
│ ││ │
│ ││ │
│ ││ │
└─────────────────────────┘└─────────────────────────┘
┌─────────────────────────┐┌─────────────────────────┐
│ bw_s_14c.pcx ││ bw_s_14d.pcx │
│ ││ │
│ ││ │
│ ││ │R = 0,0021
│ ││ │
│ ││ │
│ ││ │
│ ││ │
└─────────────────────────┘└─────────────────────────┘
┌─────────────────────────┐┌─────────────────────────┐
│ bw_s_14e.pcx ││ bw_s_14f.pcx │
│ ││ │
│ ││ │
│ ││ │R = 0,0029
│ ││ │
│ ││ │
│ ││ │
│ ││ │
└─────────────────────────┘└─────────────────────────┘
┌─────────────────────────┐┌─────────────────────────┐
│ bw_s_14g.pcx ││ bw_s_14h.pcx │
│ ││ │
│ ││ │
│ ││ │R = 0,0023
│ ││ │
│ ││ │
│ ││ │
│ ││ │
└─────────────────────────┘└─────────────────────────┘

Рис. 14

Чтобы увидеть на изображении еще больше подробностей, можно,
во-первых, использовать вывод с увеличением, и, во-вторых,
повысить контрастность выводимого участка изображения.
При выводе с увеличением мы не сможем увидеть сразу всю
картинку, но за счет этого на видимом фрагменте сможем различить
больше деталей. Вот пример: на рис. 15а показана центральная
часть рис. 5, для удобства сравнения увеличенная вдвое, а на
рис. 15б - то, что получится, если увеличивать вдвое центральную
часть непосредственно в процессе вывода.

┌─────────────────────────┐ ┌─────────────────────────┐
│ bw_s_15a.pcx │ │ bw_s_15b.pcx │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
└─────────────────────────┘ └─────────────────────────┘

а) б)

Рис. 15

Приведенные в Приложении 3 процедуры вывода могут выводить
указанную область изображения с увеличением в 2, 4, 8... раз.
Думаю, вам не составит труда догадаться, как увеличивать
изображение в количество раз, не являющееся степенью двойки.
Теперь о повышении контрастности. Если в выводимом
изображении (или в выводимой части изображения) самая темная
точка светлее 0 и/или самая светлая точка темнее 255, то можно
"растянуть" диапазон яркости до 0 - 255 по следующей формуле:

A-Amin
A'= 255 ───────────
Amax-Amin

где A - исходная яркость,
A' - новая яркость,
Amin - яркость самой темной точки,
Amax - яркость самой светлой точки.

После такого преобразования мы увидим темные участки
изображения темнее, чем надо, а светлые - светлее, но при этом
можно будет различить больше подробностей. На рис. 16 вы можете
увидеть изображение, полученное, как и рис. 15б, но с повышением
контрастности.

┌─────────────────────────┐
│ bw_s_16.pcx │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 16

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 06 .C ══════════════════

Гамма-коррекция
───────────────

Градации яркости в исходном изображении обозначены числами
от 0 до 255. Эти числа мы, собственно, и считали яркостью
пикселов (имея в виду определение яркости, данное в Приложении
2). То есть мы считали, что яркость пиксела линейно зависит от
числа, соответствующего этому пикселу в изображении: если,
например, одному пикселу соответствует число 10, а другому - 20,
то второй пиксел в два раза ярче первого.
Однако в общем случае это не так. Зависимость здесь не
линейная, а степенная (показатель степени называют "гамма").
Линейная зависимость - лишь частный случай, когда гамма равна 1.
Hа рис. 17 приведены графики для трех возможных случаев (по оси
x откладывается значение, соответствующее пикселу в изображении,
а по оси y - фактическая яркость пиксела).

┌───────────────────┐┌───────────────────┐┌───────────────────┐
│ bw_s_17a.pcx ││ bw_s_17b.pcx ││ bw_s_17c.pcx │
│ ││ ││ │
│ ││ ││ │
│ ││ ││ │
│ ││ ││ │
│ ││ ││ │
└───────────────────┘└───────────────────┘└───────────────────┘

а) гамма=1 б) гамма>1 в) гамма<1

Рис. 17

Как видим, если гамма больше 1, то в изображении с большей
точностью представлена информация о темных областях и с меньшей
точностью - о светлых, а если гамма меньше 1 - наоборот.
Чтобы правильно выводить изображения с гаммой, отличной от
единицы, и нужна гамма-коррекция. Из значения пиксела (обозначим
его x) мы должны получить его яркость (обозначим ее y). Если и
значение, и яркость - числа от 0 до 255, то формула такова:

y=255*(x/255)^Gamma

Удобно сначала вычислить y для всех x от 0 до 255, помещая
значения в таблицу, а при выводе просто брать из таблицы уже
готовое значение.
Приведенные в Приложении 3 процедуры вывода могут выполнять
гамма-коррекцию, если установить в 1 флаг условной компиляции
GAMMA_CORR. При этом значением гаммы по умолчанию считается 1,5.
Для другого значения надо будет пересчитать таблицу коррекции с
помощью программы на Бейсике, приведенной там же. Кстати, эту
программу можно запустить и просто, чтобы посмотреть, какие
графики получаются при разных значениях гаммы.
А как узнать, какое значение гаммы у выводимого изображения?
Если вы берете это изображение из файла в формате png (или в
каком-либо другом формате, где значение гаммы указывается в
самом файле), то сложностей не возникает. А если вы (как и я)
будете брать изображение из pcx-файла, где таких сведений не
содержится? Тогда придется подбирать этот коэффициент опытным
путем. Попробуйте сначала вывести изображение без
гамма-коррекции. Если оно выглядит естественно, то больше ничего
и не надо. Если оно слишком темное - значит, гамма больше 1;
если слишком светлое - гамма меньше 1.
Кстати: изображения с гаммой больше 1 будут выводиться более
точно за счет того, что они содержат больше информации о темных
областях, а распределение спектрумовских градаций яркости (см.
рис. 19 в Приложении 2) как раз таково (по крайней мере, у
меня), что темные области воспроизводятся гораздо точнее
светлых.


Работа с pcx-файлами
────────────────────

Исходные изображения надо откуда-то брать. Я считывал их из
файлов в формате pcx. В Приложении 4 приведены две процедуры для
работы с такими файлами. Первая процедура читает данные из
pcx-файла и формирует в памяти массив данных о пикселах. Вторая
процедура выполняет обратную задачу - записывает pcx-файл по
находящимся в памяти данным о пикселах (именно с ее помощью я
подготовил все pcx-файлы с рисунками для этой статьи).
Еще одна приведенная там процедура непосредственно с
pcx-файлами не работает, но окажется полезной, если вам нужно
преобразовать изображение в спектрумовском формате (6912 байтов)
в черно-белый pcx-файл. Она формирует в памяти массив данных
о пикселах такого изображения по заданным значениям градаций
яркости, а на основе этих данных уже можно создать pcx-файл.


И еще одно небольшое дополнение
───────────────────────────────

Если исходное изображение - цветное, и вы хотите вывести его
в черно-белом виде, то для получения яркости пиксела по
известным значениям компонент R, G, B воспользуйтесь следующей
формулой:

Y = 0,299R + 0,587G + 0,114B

Так как вычислять яркость нужно для каждого пиксела, а даже
изображение размером со спектрумовский экран - 256*192 -
содержит 49152 пиксела, то понятно, что это вычисление должно
быть как можно более быстрым. Удобно применить табличный способ.
Пусть имеется таблица длиной 768 (т.е. 3*256) байтов,
начинающаяся с адреса, кратного 256 (обозначим этот адрес
TAB_RGB). Пусть в первых 256 байтах этой таблицы содержатся
значения функции y=0,299x для x от 0 до 255, округленные до
целых (для практического применения такая точность вполне
достаточна), во вторых 256 байтах - значения функции y=0,587x,
а в третьих 256 байтах - значения функции y=0,114x (также для x
от 0 до 255).

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 06 .C ══════════════════

Гамма-коррекция
───────────────

Градации яркости в исходном изображении обозначены числами
от 0 до 255. Эти числа мы, собственно, и считали яркостью
пикселов (имея в виду определение яркости, данное в Приложении
2). То есть мы считали, что яркость пиксела линейно зависит от
числа, соответствующего этому пикселу в изображении: если,
например, одному пикселу соответствует число 10, а другому - 20,
то второй пиксел в два раза ярче первого.
Однако в общем случае это не так. Зависимость здесь не
линейная, а степенная (показатель степени называют "гамма").
Линейная зависимость - лишь частный случай, когда гамма равна 1.
На рис. 17 приведены графики для трех возможных случаев (по оси
x откладывается значение, соответствующее пикселу в изображении,
а по оси y - фактическая яркость пиксела).

┌───────────────────┐┌───────────────────┐┌───────────────────┐
│ bw_s_17a.pcx ││ bw_s_17b.pcx ││ bw_s_17c.pcx │
│ ││ ││ │
│ ││ ││ │
│ ││ ││ │
│ ││ ││ │
│ ││ ││ │
└───────────────────┘└───────────────────┘└───────────────────┘

а) гамма=1 б) гамма>1 в) гамма<1

Рис. 17

Как видим, если гамма больше 1, то в изображении с большей
точностью представлена информация о темных областях и с меньшей
точностью - о светлых, а если гамма меньше 1 - наоборот.
Чтобы правильно выводить изображения с гаммой, отличной от
единицы, и нужна гамма-коррекция. Из значения пиксела (обозначим
его x) мы должны получить его яркость (обозначим ее y). Если и
значение, и яркость - числа от 0 до 255, то формула такова:

y=255*(x/255)^Gamma

Удобно сначала вычислить y для всех x от 0 до 255, помещая
значения в таблицу, а при выводе просто брать из таблицы уже
готовое значение.
Приведенные в Приложении 3 процедуры вывода могут выполнять
гамма-коррекцию, если установить в 1 флаг условной компиляции
GAMMA_CORR. При этом значением гаммы по умолчанию считается 1,5.
Для другого значения надо будет пересчитать таблицу коррекции с
помощью программы на Бейсике, приведенной там же. Кстати, эту
программу можно запустить и просто, чтобы посмотреть, какие
графики получаются при разных значениях гаммы.
А как узнать, какое значение гаммы у выводимого изображения?
Если вы берете это изображение из файла в формате png (или в
каком-либо другом формате, где значение гаммы указывается в
самом файле), то сложностей не возникает. А если вы (как и я)
будете брать изображение из pcx-файла, где таких сведений не
содержится? Тогда придется подбирать этот коэффициент опытным
путем. Попробуйте сначала вывести изображение без
гамма-коррекции. Если оно выглядит естественно, то больше ничего
и не надо. Если оно слишком темное - значит, гамма больше 1;
если слишком светлое - гамма меньше 1.
Кстати: изображения с гаммой больше 1 будут выводиться более
точно за счет того, что они содержат больше информации о темных
областях, а распределение спектрумовских градаций яркости (см.
рис. 19 в Приложении 2) как раз таково (по крайней мере, у
меня), что темные области воспроизводятся гораздо точнее
светлых.


Работа с pcx-файлами
────────────────────

Исходные изображения надо откуда-то брать. Я считывал их из
файлов в формате pcx. В Приложении 4 приведены две процедуры для
работы с такими файлами. Первая процедура читает данные из
pcx-файла и формирует в памяти массив данных о пикселах. Вторая
процедура выполняет обратную задачу - записывает pcx-файл по
находящимся в памяти данным о пикселах (именно с ее помощью я
подготовил все pcx-файлы с рисунками для этой статьи).
Еще одна приведенная там процедура непосредственно с
pcx-файлами не работает, но окажется полезной, если вам нужно
преобразовать изображение в спектрумовском формате (6912 байтов)
в черно-белый pcx-файл. Она формирует в памяти массив данных
о пикселах такого изображения по заданным значениям градаций
яркости, а на основе этих данных уже можно создать pcx-файл.


И еще одно небольшое дополнение
───────────────────────────────

Если исходное изображение - цветное, и вы хотите вывести его
в черно-белом виде, то для получения яркости пиксела по
известным значениям компонент R, G, B воспользуйтесь следующей
формулой:

Y = 0,299R + 0,587G + 0,114B

Так как вычислять яркость нужно для каждого пиксела, а даже
изображение размером со спектрумовский экран - 256*192 -
содержит 49152 пиксела, то понятно, что это вычисление должно
быть как можно более быстрым. Удобно применить табличный способ.
Пусть имеется таблица длиной 768 (т.е. 3*256) байтов,
начинающаяся с адреса, кратного 256 (обозначим этот адрес
TAB_RGB). Пусть в первых 256 байтах этой таблицы содержатся
значения функции y=0,299x для x от 0 до 255, округленные до
целых (для практического применения такая точность вполне
достаточна), во вторых 256 байтах - значения функции y=0,587x,
а в третьих 256 байтах - значения функции y=0,114x (также для x
от 0 до 255).

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 07 .C ══════════════════

Вот 32-байтная процедура построения такой таблицы:

MK_T_RGB LD BC,TAB_RGB+#2FF ;Адрес последнего байта таблицы.
LD H,29 ;0,114*256
CALL MK_T_RGB_1

LD H,150 ;0,587*256
CALL MK_T_RGB_1

LD H,77 ;0,299*256

MK_T_RGB_1 XOR A
LD L,A
LD D,A
LD E,H

MK_T_RGB_2 SBC HL,DE
RET C

;Запись округленного результата:

LD A,H
BIT 7,L
JR Z,MK_T_RGB_3
INC A
MK_T_RGB_3 LD (BC),A

DEC BC
JR MK_T_RGB_2

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

;Вход: значения компонент R,G,B - в регистрах L,D,E
; соответственно.
;Выход: вычисленное значение Y - в аккумуляторе.
;Длина: 9 байтов.
;Время выполнения: 44 такта.

LD H,TAB_RGB/256
LD A,(HL) ;A=0,299R
INC H
LD L,D
ADD A,(HL) ;A=0,299R+0,587G
INC H
LD L,E
ADD A,(HL) ;A=0,299R+0,587G+0,114B

Если надо обработать сразу группу пикселов, выгоднее будет
не загружать каждый раз регистр H, а загрузить его только один
раз, а затем только изменять командами INC и DEC (обрабатывая по
два пиксела в цикле), как в нижеприведенной процедуре:

;Вход: IX - адрес начала исходных данных (R1,G1,B1,R2,G2,B2...);
; DE - куда помещать результат (Y1,Y2...);
; BC - количество обрабатываемых пикселов (должно быть
; четным), деленное на 2.
;Длина: 49 байтов.

CALC_Y EXX
LD DE,6
EXX
LD H,TAB_RGB/256

CALC_Y_1 LD L,(IX) ;L=R1
LD A,(HL) ;A=0,299R1

INC H
LD L,(IX+1) ;L=G1
ADD A,(HL) ;A=0,299R1+0,587G1

INC H
LD L,(IX+2) ;L=B1
ADD A,(HL) ;A=0,299R1+0,587G1+0,114B1

LD (DE),A
INC DE

LD L,(IX+5) ;L=B2
LD A,(HL) ;A=0,114B2

DEC H
LD L,(IX+4) ;L=G2
ADD A,(HL) ;A=0,114B2+0,587G2

DEC H
LD L,(IX+3) ;L=R2
ADD A,(HL) ;A=0,114B2+0,587G2+0,299R2

LD (DE),A
INC DE

EXX
ADD IX,DE ;IX:=IX+6
EXX

DEC BC
LD A,B
OR C
JR NZ,CALC_Y_1

RET

Если заранее известно, что DE на входе будет четным (или
нечетным), то первую (или, соответственно, вторую) команду
INC DE можно заменить на более быструю INC E.
Если обрабатывается не более 512 пикселов, то в качестве
счетчика можно использовать не регистровую пару BC, а только
один регистр B, используя для организации цикла команду DJNZ.


Литература
──────────

1. А.Бордачев. "О формате PCX". Библиотека информационной
технологии, вып. 8. Москва, "Инфоарт", 1993.
2. Т.Кенцл. "Форматы файлов Internet". СПб, "Питер", 1997.
3. К.Бочков. "Сканирование - это так просто...". "Мир ПК"
11/2000.
4. С.Кащавцев. "Волшебная сила кривых - II". "Компьютерра"
49-50/1998.
5. Е.Зарецкий. "Быстрый вывод графики". "Радиолюбитель. Ваш
компьютер" 5,6/2001.

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 08 .C ══════════════════

Приложение 1
────────────

Здесь я расскажу о том, как определял R - различие между
двумя изображениями.
Вычислим среднеквадратичное отклонение:

h-1 l-1
┌── ┌── 2
> > (p1(x,y)-p2(x,y))
└── └──
y=0 x=0
r1 = ─────────────────────────
l*h

где h - высота сравниваемых изображений,
l - ширина,
p1(x,y) - яркость пиксела с координатами (x,y) в первом
изображении, приведенная к диапазону 0-1 (если,
например, в изображении 256 градаций яркости, то
значение пиксела надо разделить на 255),
p2(x,y) - то же самое для второго изображения.

Далее аналогично вычислим среднеквадратичное отклонение для
всех участков 2*2 пиксела, считая яркостью участка среднюю
яркость входящих в него пикселов:

h-2 l-2
┌── ┌── 2
> > (m1(x,y)-m2(x,y))
└── └──
y=0 x=0
r2 = ─────────────────────────
(l-1)*(h-1)

1 1 1 1
┌── ┌── ┌── ┌──
> > p1(x+i,y+j) > > p2(x+i,y+j)
└── └── └── └──
j=0 i=0 j=0 i=0
где m1(x,y) = ──────────────────, m2(x,y) = ──────────────────.
4 4

И для участков 4*4 пиксела:

h-4 l-4
┌── ┌── 2
> > (n1(x,y)-n2(x,y))
└── └──
y=0 x=0
r3 = ─────────────────────────
(l-3)*(h-3)

3 3 3 3
┌── ┌── ┌── ┌──
> > p1(x+i,y+j) > > p2(x+i,y+j)
└── └── └── └──
j=0 i=0 j=0 i=0
где n1(x,y) = ──────────────────, n2(x,y) = ──────────────────.
16 16

Величина r1 отражает различие с близкого расстояния (когда
на сравниваемых изображениях можно различить отдельные пикселы).
Величина r2 - различие со среднего расстояния (когда нельзя
различить деталей меньше чем 2*2 пиксела). Hаконец, величина
r3 - различие с дальнего расстояния (когда уже не видно деталей
меньше чем 4*4 пиксела). Определим R как среднее между r1, r2 и
r3:

r1 + r2 + r3
R = ──────────────
3

R (как и r1, r2, r3) может принимать значения в диапазоне от
0 до 1, причем 0 соответствует полному совпадению сравниваемых
изображений, а 1 - максимальному различию.

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 08 .C ══════════════════

Приложение 1
────────────

Здесь я расскажу о том, как определял R - различие между
двумя изображениями.
Вычислим среднеквадратичное отклонение:

h-1 l-1
┌── ┌── 2
> > (p1(x,y)-p2(x,y))
└── └──
y=0 x=0
r1 = ─────────────────────────
l*h

где h - высота сравниваемых изображений,
l - ширина,
p1(x,y) - яркость пиксела с координатами (x,y) в первом
изображении, приведенная к диапазону 0-1 (если,
например, в изображении 256 градаций яркости, то
значение пиксела надо разделить на 255),
p2(x,y) - то же самое для второго изображения.

Далее аналогично вычислим среднеквадратичное отклонение для
всех участков 2*2 пиксела, считая яркостью участка среднюю
яркость входящих в него пикселов:

h-2 l-2
┌── ┌── 2
> > (m1(x,y)-m2(x,y))
└── └──
y=0 x=0
r2 = ─────────────────────────
(l-1)*(h-1)

1 1 1 1
┌── ┌── ┌── ┌──
> > p1(x+i,y+j) > > p2(x+i,y+j)
└── └── └── └──
j=0 i=0 j=0 i=0
где m1(x,y) = ──────────────────, m2(x,y) = ──────────────────.
4 4

И для участков 4*4 пиксела:

h-4 l-4
┌── ┌── 2
> > (n1(x,y)-n2(x,y))
└── └──
y=0 x=0
r3 = ─────────────────────────
(l-3)*(h-3)

3 3 3 3
┌── ┌── ┌── ┌──
> > p1(x+i,y+j) > > p2(x+i,y+j)
└── └── └── └──
j=0 i=0 j=0 i=0
где n1(x,y) = ──────────────────, n2(x,y) = ──────────────────.
16 16

Величина r1 отражает различие с близкого расстояния (когда
на сравниваемых изображениях можно различить отдельные пикселы).
Величина r2 - различие со среднего расстояния (когда нельзя
различить деталей меньше чем 2*2 пиксела). Наконец, величина
r3 - различие с дальнего расстояния (когда уже не видно деталей
меньше чем 4*4 пиксела). Определим R как среднее между r1, r2 и
r3:

r1 + r2 + r3
R = ──────────────
3

R (как и r1, r2, r3) может принимать значения в диапазоне от
0 до 1, причем 0 соответствует полному совпадению сравниваемых
изображений, а 1 - максимальному различию.

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 09 .C ══════════════════

Приложение 2
────────────

Здесь речь пойдет о том, как определить величины a0-a7
(яркость цветов 0-7 при bright=0) и b0-b7 (яркость тех же
цветов при bright=1), когда изображение выводится в черно-белом
виде.
Очевидно, эти величины могут быть различны для разных
моделей ZX Spectrum, для разных преобразователей цветного
сигнала в черно-белый, а также для разных мониторов и
телевизоров. Поэтому вместо того, чтобы привести готовый набор
чисел, я расскажу о том, как вы сможете определить их именно для
своей системы "компьютер-монитор", причем для этого вам не
понадобятся какие-либо специальные приборы.

Что можно сказать сразу?

1. Самый темный цвет - черный:

a0 = 0, b0 = 0.

2. Самый яркий цвет - белый при bright=1:

b7 = 255.

3. Яркость всех остальных цветов принимает промежуточные
значения:

0 < (а1-а7, b1-b6) < 255.

4. С увеличением номера цвета яркость возрастает:

a1 > a0 b1 > b0
a2 > a1 b2 > b1
....... .......
a7 > a6 b7 > b6.

5. Для каждого цвета, кроме черного, яркость при bright=1
больше, чем при bright=0:

b1 > a1
b2 > a2
.......
b7 > a7.

Итак, кое-что мы уже знаем. Основываясь на этой информации,
можно предположить, что распределение яркостей будет примерно
таким:

┌─────────────────────────┐
│ bw_s_18.pcx │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 18

Выше мы говорили "яркость", а каков физический смысл этого
понятия? Определим его так: яркость некоторого цвета (точнее,
яркость градации, соответствующей этому цвету при черно-белом
режиме отображения) - это величина, пропорциональная количеству
фотонов, испускаемому единичной областью данного цвета за
единицу времени. Hапример, если яркость одного цвета вдвое
больше яркости другого, это значит, что некоторая область этого
цвета испускает за единицу времени вдвое больше фотонов, чем
такая же область другого цвета.

Если вывести в одной половине экрана какой-либо цвет
(далее - исходный цвет), а в другой половине - смесь пикселов
двух цветов, темнее и светлее исходного (далее - соответственно
темный цвет и светлый цвет), то, подбирая соотношение числа
пикселов этих двух цветов, можно добиться равенства яркостей
обеих половин экрана. Равенство яркостей означает, что обе
половины экрана за единицу времени испускают одинаковое
количество фотонов. Тогда справедлива следующая формула:

x = k*y + (1-k)*z (1)

где x - яркость исходного цвета,
у - яркость темного цвета,
z - яркость светлого цвета,
k - доля пикселов темного цвета,
1-k - доля пикселов светлого цвета.

Hапример, если яркости уравнялись при заполнении второй
половины экрана текстурой 4*4, в которой 3 пиксела темного цвета
и 13 пикселов светлого цвета, это будет записано так:

x = 3/16*y + 13/16*z.

Используем вышеизложенное для вычисления искомых яркостей
a0-a7 и b0-b7. Сначала выразим яркость каждого из цветов b1-b6
через яркость соседних с ним цветов:

b1 = k1*b0 + (1-k1)*b2 (2)
b2 = k2*b1 + (1-k2)*b3
......................
b6 = k6*b5 + (1-k6)*b7.

Из (2) следует:

b1 - k1*b0
b2 = ──────────
1 - k1

И далее:

b2 - k2*b1
b3 = ──────────
1 - k2

b3 - k3*b2
b4 = ──────────
1 - k3

...............

b6 - k6*b5
b7 = ──────────
1 - k6

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 09 .C ══════════════════

Приложение 2
────────────

Здесь речь пойдет о том, как определить величины a0-a7
(яркость цветов 0-7 при bright=0) и b0-b7 (яркость тех же
цветов при bright=1), когда изображение выводится в черно-белом
виде.
Очевидно, эти величины могут быть различны для разных
моделей ZX Spectrum, для разных преобразователей цветного
сигнала в черно-белый, а также для разных мониторов и
телевизоров. Поэтому вместо того, чтобы привести готовый набор
чисел, я расскажу о том, как вы сможете определить их именно для
своей системы "компьютер-монитор", причем для этого вам не
понадобятся какие-либо специальные приборы.

Что можно сказать сразу?

1. Самый темный цвет - черный:

a0 = 0, b0 = 0.

2. Самый яркий цвет - белый при bright=1:

b7 = 255.

3. Яркость всех остальных цветов принимает промежуточные
значения:

0 < (а1-а7, b1-b6) < 255.

4. С увеличением номера цвета яркость возрастает:

a1 > a0 b1 > b0
a2 > a1 b2 > b1
....... .......
a7 > a6 b7 > b6.

5. Для каждого цвета, кроме черного, яркость при bright=1
больше, чем при bright=0:

b1 > a1
b2 > a2
.......
b7 > a7.

Итак, кое-что мы уже знаем. Основываясь на этой информации,
можно предположить, что распределение яркостей будет примерно
таким:

┌─────────────────────────┐
│ bw_s_18.pcx │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 18

Выше мы говорили "яркость", а каков физический смысл этого
понятия? Определим его так: яркость некоторого цвета (точнее,
яркость градации, соответствующей этому цвету при черно-белом
режиме отображения) - это величина, пропорциональная количеству
фотонов, испускаемому единичной областью данного цвета за
единицу времени. Например, если яркость одного цвета вдвое
больше яркости другого, это значит, что некоторая область этого
цвета испускает за единицу времени вдвое больше фотонов, чем
такая же область другого цвета.

Если вывести в одной половине экрана какой-либо цвет
(далее - исходный цвет), а в другой половине - смесь пикселов
двух цветов, темнее и светлее исходного (далее - соответственно
темный цвет и светлый цвет), то, подбирая соотношение числа
пикселов этих двух цветов, можно добиться равенства яркостей
обеих половин экрана. Равенство яркостей означает, что обе
половины экрана за единицу времени испускают одинаковое
количество фотонов. Тогда справедлива следующая формула:

x = k*y + (1-k)*z (1)

где x - яркость исходного цвета,
у - яркость темного цвета,
z - яркость светлого цвета,
k - доля пикселов темного цвета,
1-k - доля пикселов светлого цвета.

Например, если яркости уравнялись при заполнении второй
половины экрана текстурой 4*4, в которой 3 пиксела темного цвета
и 13 пикселов светлого цвета, это будет записано так:

x = 3/16*y + 13/16*z.

Используем вышеизложенное для вычисления искомых яркостей
a0-a7 и b0-b7. Сначала выразим яркость каждого из цветов b1-b6
через яркость соседних с ним цветов:

b1 = k1*b0 + (1-k1)*b2 (2)
b2 = k2*b1 + (1-k2)*b3
......................
b6 = k6*b5 + (1-k6)*b7.

Из (2) следует:

b1 - k1*b0
b2 = ──────────
1 - k1

И далее:

b2 - k2*b1
b3 = ──────────
1 - k2

b3 - k3*b2
b4 = ──────────
1 - k3

...............

b6 - k6*b5
b7 = ──────────
1 - k6

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 10 .C ══════════════════

Таким образом, зная b0 и b1, а также k1-k6 (находимые
опытным путем), можно последовательно вычислить b2-b7.
Значение b0 нам известно - это 0. b1 неизвестно, зато мы
знаем, что b7 = 255. Можно выразить b7 через b1, затем
определить b1 и далее вычислить b2-b6. Hо этот путь связан с
громоздкими преобразованиями. Поступим проще. Заметим, что
b2-b7 линейно зависят от b1. В этом нетрудно убедиться из
вышеприведенных формул (не забывая, что b0=0). Примем b1=1 и
вычислим b2-b7. Тогда достаточно определить, во сколько раз
получившееся значение b7 оказалось меньше 255, и затем умножить
каждое из вычисленных значений b1-b7 на эту величину, чтобы
получить их истинные значения.
После того, как яркости цветов с bright=1 известны, остается
определить яркости цветов с bright=0 (a1-a7). Для этого
достаточно найти опытным путем для каждого из этих цветов
эквивалентную по яркости смесь двух цветов с bright=1, после
чего воспользоваться формулой (1).

Это была теория, а теперь перейдем к практике. Рассмотрим,
как я определял уровни яркости на своем компьютере и что для
этого использовал.
Сначала я написал процедуру, которая заполняет верхнюю
часть экрана одним цветом, а нижнюю - смесью пикселов двух
других цветов (с помощью текстуры 4*4). Вот она:

LD H,1 ;исходный цвет
LD D,0 ;темный цвет
LD E,2 ;светлый цвет
LD L,4 ;номер текстуры (0-16)

;Заполняем верхнюю половину экрана:

LD A,H
ADD A,A
ADD A,A
ADD A,A
ADD A,H

;Если исходный цвет - с повышенной яркостью (bright=1), то
;следует команда ADD A,#40, иначе она не нужна (можно заменить
;на ADD A,0):

ADD A,#40

EXX
LD HL,#5800
LD DE,#5801
LD BC,#180
LD (HL),A
LDIR
EXX

;Заполняем нижнюю половину экрана:

LD A,D
ADD A,A
ADD A,A
ADD A,A
ADD A,E
ADD A,#40

EXX
LD BC,#17F
LD (HL),A
LDIR
EXX

;Заполнение текстурой:

LD H,0
ADD HL,HL
ADD HL,HL ;*4
LD DE,TEXTURES
ADD HL,DE
EX DE,HL

;DE указывает на первый байт текстуры.

LD B,24
LD HL,#4880

M2 PUSH DE
PUSH BC
LD B,4
M1 LD A,(DE)
PUSH BC
PUSH DE
PUSH HL
LD D,H
LD E,L
LD (HL),A
INC DE
LD BC,#1F
LDIR
POP HL
POP DE
POP BC
CALL DOWN_HL
INC DE
DJNZ M1
POP BC
POP DE
DJNZ M2

RET

DOWN_HL INC H
LD A,H
AND 7
RET NZ
LD A,L
ADD A,#20
LD L,A
RET C
LD A,H
SUB 8
LD H,A
RET

TEXTURES DB %00000000
DB %00000000
DB %00000000
DB %00000000

DB %00000000
DB %01000100
DB %00000000
DB %00000000

DB %00000000
DB %01000100
DB %00000000
DB %00010001

DB %00000000
DB %01010101
DB %00000000
DB %00010001

DB %00000000
DB %01010101
DB %00000000
DB %01010101

DB %00000000
DB %01010101
DB %00100010
DB %01010101

DB %10001000
DB %01010101
DB %00100010
DB %01010101

DB %10101010
DB %01010101
DB %00100010
DB %01010101

DB %10101010
DB %01010101
DB %10101010
DB %01010101

DB %10101010
DB %01110111
DB %10101010
DB %01010101

DB %10101010
DB %01110111
DB %10101010
DB %11011101

DB %10101010
DB %01110111
DB %10101010
DB %11111111

DB %10101010
DB %11111111
DB %10101010
DB %11111111

DB %10101010
DB %11111111
DB %11101110
DB %11111111

DB %10111011
DB %11111111
DB %11101110
DB %11111111

DB %11111111
DB %11111111
DB %11101110
DB %11111111

DB %11111111
DB %11111111
DB %11111111
DB %11111111

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 10 .C ══════════════════

Таким образом, зная b0 и b1, а также k1-k6 (находимые
опытным путем), можно последовательно вычислить b2-b7.
Значение b0 нам известно - это 0. b1 неизвестно, зато мы
знаем, что b7 = 255. Можно выразить b7 через b1, затем
определить b1 и далее вычислить b2-b6. Но этот путь связан с
громоздкими преобразованиями. Поступим проще. Заметим, что
b2-b7 линейно зависят от b1. В этом нетрудно убедиться из
вышеприведенных формул (не забывая, что b0=0). Примем b1=1 и
вычислим b2-b7. Тогда достаточно определить, во сколько раз
получившееся значение b7 оказалось меньше 255, и затем умножить
каждое из вычисленных значений b1-b7 на эту величину, чтобы
получить их истинные значения.
После того, как яркости цветов с bright=1 известны, остается
определить яркости цветов с bright=0 (a1-a7). Для этого
достаточно найти опытным путем для каждого из этих цветов
эквивалентную по яркости смесь двух цветов с bright=1, после
чего воспользоваться формулой (1).

Это была теория, а теперь перейдем к практике. Рассмотрим,
как я определял уровни яркости на своем компьютере и что для
этого использовал.
Сначала я написал процедуру, которая заполняет верхнюю
часть экрана одним цветом, а нижнюю - смесью пикселов двух
других цветов (с помощью текстуры 4*4). Вот она:

LD H,1 ;исходный цвет
LD D,0 ;темный цвет
LD E,2 ;светлый цвет
LD L,4 ;номер текстуры (0-16)

;Заполняем верхнюю половину экрана:

LD A,H
ADD A,A
ADD A,A
ADD A,A
ADD A,H

;Если исходный цвет - с повышенной яркостью (bright=1), то
;следует команда ADD A,#40, иначе она не нужна (можно заменить
;на ADD A,0):

ADD A,#40

EXX
LD HL,#5800
LD DE,#5801
LD BC,#180
LD (HL),A
LDIR
EXX

;Заполняем нижнюю половину экрана:

LD A,D
ADD A,A
ADD A,A
ADD A,A
ADD A,E
ADD A,#40

EXX
LD BC,#17F
LD (HL),A
LDIR
EXX

;Заполнение текстурой:

LD H,0
ADD HL,HL
ADD HL,HL ;*4
LD DE,TEXTURES
ADD HL,DE
EX DE,HL

;DE указывает на первый байт текстуры.

LD B,24
LD HL,#4880

M2 PUSH DE
PUSH BC
LD B,4
M1 LD A,(DE)
PUSH BC
PUSH DE
PUSH HL
LD D,H
LD E,L
LD (HL),A
INC DE
LD BC,#1F
LDIR
POP HL
POP DE
POP BC
CALL DOWN_HL
INC DE
DJNZ M1
POP BC
POP DE
DJNZ M2

RET

DOWN_HL INC H
LD A,H
AND 7
RET NZ
LD A,L
ADD A,#20
LD L,A
RET C
LD A,H
SUB 8
LD H,A
RET

TEXTURES DB %00000000
DB %00000000
DB %00000000
DB %00000000

DB %00000000
DB %01000100
DB %00000000
DB %00000000

DB %00000000
DB %01000100
DB %00000000
DB %00010001

DB %00000000
DB %01010101
DB %00000000
DB %00010001

DB %00000000
DB %01010101
DB %00000000
DB %01010101

DB %00000000
DB %01010101
DB %00100010
DB %01010101

DB %10001000
DB %01010101
DB %00100010
DB %01010101

DB %10101010
DB %01010101
DB %00100010
DB %01010101

DB %10101010
DB %01010101
DB %10101010
DB %01010101

DB %10101010
DB %01110111
DB %10101010
DB %01010101

DB %10101010
DB %01110111
DB %10101010
DB %11011101

DB %10101010
DB %01110111
DB %10101010
DB %11111111

DB %10101010
DB %11111111
DB %10101010
DB %11111111

DB %10101010
DB %11111111
DB %11101110
DB %11111111

DB %10111011
DB %11111111
DB %11101110
DB %11111111

DB %11111111
DB %11111111
DB %11101110
DB %11111111

DB %11111111
DB %11111111
DB %11111111
DB %11111111

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 11 .C ══════════════════

Чтобы было удобнее сравнивать яркости половин экрана, когда
на одной половине яркость равномерная, а на другой - текстура, я
рассматривал изображение через полупрозрачную полиэтиленовую
пленку (для этого годится обычный пакет).

С помощью вышеприведенной процедуры были получены следующие
соотношения:

b1 = 9/16 b0 + 7/16 b2
b2 = 10/16 b1 + 6/16 b3
b3 = 8/16 b2 + 8/16 b4
b4 = 13/16 b3 + 3/16 b5
b5 = 9/16 b4 + 7/16 b6
b6 = 10/16 b5 + 6/16 b7

(т.е. k1=9/16, k2=10/16, k3=8/16, k4=13/16, k5=9/16, k6=10/16)

a1 = 3/16 b0 + 13/16 b1
a2 = 9/16 b1 + 7/16 b2
a3 = 9/16 b2 + 7/16 b3
a4 = b3
a5 = 9/16 b4 + 7/16 b5
a6 = 11/16 b5 + 5/16 b6
a7 = 11/16 b6 + 5/16 b7

Затем я написал программу, вычисляющую на основе этих данных
искомые значения a0-a7 и b0-b7:

10 DIM k(6): DIM a(7): DIM b(7)
20 LET k(1)=9/16: LET k(2)=10/16: LET k(3)=8/16: LET k(4)=
13/16: LET k(5)=9/16: LET k(6)=10/16
30 REM CALCULATE b(1)-b(7)
40 LET b(1)=1
50 LET b(2)=1/(1-k(1))
60 FOR i=3 TO 7
70 LET b(i)=(b(i-1)-k(i-1)*b(i-2))/(1-k(i-1))
80 NEXT i
90 LET n=255/b(7)
100 FOR i=1 TO 7
110 LET b(i)=b(i)*n
120 NEXT i
130 REM CALCULATE a(1)-a(7)
140 LET a(1)=13/16*b(1)
150 LET a(2)=9/16*b(1)+7/16*b(2)
160 LET a(3)=9/16*b(2)+7/16*b(3)
170 LET a(4)=b(3)
180 LET a(5)=9/16*b(4)+7/16*b(5)
190 LET a(6)=11/16*b(5)+5/16*b(6)
200 LET a(7)=11/16*b(6)+5/16*b(7)
210 REM PRINT RESULT
220 PRINT "a0=0",0
230 FOR i=1 TO 7
240 PRINT "a";i;"=";a(i), INT (a(i)*256+0.5)
250 NEXT i
260 PRINT
270 PRINT "b0=0",0
280 FOR i=1 TO 7
290 PRINT "b";i;"=";b(i), INT (b(i)*256+0.5)
300 NEXT i

Результаты работы программы: в левом столбце - вычисленные
значения яркостей, в правом - они же, но в 256-х долях - в таком
формате они хранятся в процедурах вывода изображения (см.
Приложение 3).

а0 = 0 0
а1 = 4,344111 1112
а2 = 8,3540597 2139
а3 = 17,233232 4412
а4 = 23,677792 6062
а5 = 56,855343 14555
а6 = 104,72922 26811
а7 = 181,85936 46556

b0 = 0 0
b1 = 5,3465982 1369
b2 = 12,220796 3129
b3 = 23,677792 6062
b4 = 35,134788 8995
b5 = 84,781772 21704
b6 = 148,61361 38045
b7 = 255 65280

А вот как это выглядит на графике:

┌─────────────────────────┐
│ bw_s_19.pcx │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└─────────────────────────┘

Рис. 19

Как видите, это отличается от первоначальных предположений
(рис. 18). Значения располагаются неравномерно: чем меньше
яркость, тем ближе они друг к другу. Чем это можно объяснить?
Очевидно, при преобразовании цветного изображения в черно-белое
стремятся к тому, чтобы различные цвета превращались в хорошо
отличающиеся друг от друга градации серого. А для этого яркости
соседних градаций должны отличаться друг от друга не на одну и
ту же величину, а в одно и то же количество раз - таковы
особенности человеческого зрения.

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 12 .C ══════════════════

Приложение 3
────────────

Здесь приведены тексты процедур для вывода изображений
шестью различными способами, рассмотренными в статье. Исходное
изображение (256*192, с 256 градациями яркости) должно быть
предварительно помещено в три банка ОЗУ, номера которых (точнее,
числа, выводимые в порт #7FFD для их подключения) указываются в
массиве N_BANK (по умолчанию там #10, #11 и #13). Информация о
пикселах хранится "слева направо, сверху вниз": в первом банке -
верхняя треть изображения, во втором - средняя, в третьем -
нижняя.
Перед использованием процедур вы должны определить яркости
градаций для вашей системы "компьютер-монитор" (см. Приложение
2) и поместить полученные значения в массивы BRIGHT_0 и
BRIGHT_1. По умолчанию там находятся значения для моей системы,
которые не будут в точности совпадать с вашими. Можно, конечно,
оставить и их, но качество вывода будет хуже.
В процедурах, использующих multicolor, все задержки
рассчитаны на "Пентагон". Для другой модели компьютера их,
скорее всего, придется изменить.

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

ORG #6000

CALL CLS_1

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

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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 12 .C ══════════════════

Приложение 3
────────────

Здесь приведены тексты процедур для вывода изображений
шестью различными способами, рассмотренными в статье. Исходное
изображение (256*192, с 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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 13 .C ══════════════════

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

ORG #6000

CALL CLS_1
CALL CLS_2

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

LD DE,0

MAIN PUSH DE
PUSH DE

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

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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 14 .C ══════════════════

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

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

LD DE,0

MAIN PUSH DE
PUSH DE

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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 13 .C ══════════════════

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

ORG #6000

CALL CLS_1
CALL CLS_2

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

LD DE,0

MAIN PUSH DE
PUSH DE

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

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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 14 .C ══════════════════

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

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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 15 .C ══════════════════

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

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

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

LD A,(X)
CP 21
JR NEW_X

NE_P RRA
JR C,NE_O

;Hажата клавиша "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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 15 .C ══════════════════

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

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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 16 .C ══════════════════

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

ORG #6000

CALL CLS_1
CALL CLS_2

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

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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 16 .C ══════════════════

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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 17 .C ══════════════════

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

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

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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 17 .C ══════════════════

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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 18 .C ══════════════════

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

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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 18 .C ══════════════════

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 19 .C ══════════════════

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

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

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

LD A,(X)
CP 21
JR NEW_X

NE_P RRA
JR C,NE_O

;Hажата клавиша "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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 19 .C ══════════════════

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

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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 20 .C ══════════════════

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

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

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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 20 .C ══════════════════

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

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

════════════════════════════════════════════════


С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 21 .C ══════════════════

;---------------------------------------
;Процедура 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

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

;Если 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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 21 .C ══════════════════

;---------------------------------------
;Процедура 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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 22 .C ══════════════════

;Сравниваем 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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 23 .C ══════════════════

;---------------------------------------
;Процедура 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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 24 .C ══════════════════

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

TEXTURE

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

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

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

;Включаем в регистре H бит с номером
;четверти, в которой оказывается пиксел.
;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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 24 .C ══════════════════

;---------------------------------------
;Процедура 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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 25 .C ══════════════════

;---------------------------------------
;Процедура 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

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

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

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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 25 .C ══════════════════

;---------------------------------------
;Процедура 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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 26 .C ══════════════════

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

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 27 .C ══════════════════

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

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

TAB_ADR DB ADR_0256
DB ADR_1256
DB ADR_2256
DB ADR_3256
DB ADR_4256
DB ADR_5256
DB ADR_6256
DB ADR_7256
DB ADR_8256
DB ADR_9256
DB ADR_10256
DB ADR_11256
DB ADR_12256
DB ADR_13256
DB ADR_14256
DB ADR_15256
DB ADR_16256
DB ADR_17256
DB ADR_18256
DB ADR_19256
DB ADR_20256
DB ADR_21256
DB ADR_22256
DB ADR_23256
DB ADR_24256
DB ADR_25256
DB ADR_26256
DB ADR_27256
DB ADR_28256
DB ADR_29256
DB ADR_30256
DB ADR_31256

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

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 28 .C ══════════════════

Комментарии
───────────

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

┌──────────────────┬───────────────────────────────────────────┐
│ Hомер │ Подпрограммы, │
│ процедуры вывода │ не используемые в этой процедуре │
├──────────────────┼───────────────────────────────────────────┤
│ 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 │
└──────────────────┴───────────────────────────────────────────┘
Табл. 1

* * *

Как выводить изображение с увеличением? Если требуется
увеличение в два раза (размер выводимой области изображения
тогда будет 128*96), то в начало процедуры 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... раз (размер выводимой
области тогда будет соответственно 64*48, 32*24...), то ставим
не одну, а две, три... команды SRL A.
Кстати, для увеличения в два раза вышеприведенный фрагмент
можно оптимизировать: вначале прибавить удвоенную координату, а
затем разделить на два командой RRA вместо SRL A - это будет
короче и быстрее.

* * *

Как сформировать таблицу гамма-коррекции (GAMMA_T) для
нужного значения гаммы? Hиже приведена соответствующая программа
на Бейсике. Эта программа формирует таблицу в том же формате, в
котором она хранится в общей части процедур вывода: значения в
формате 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 различных текстур 16*16. Простой подсчет
показывает, что для их хранения требуется 257*16*16/8=8224 байта
памяти, или более 8 КБ. Тратить столько памяти не хотелось, и я
использовал такой прием: когда нужно узнать, включен или
выключен пиксел с данными координатами, находящийся в
определенной текстуре, ответ получается с помощью специального
алгоритма, основанного на свойствах текстур. Представляется
интересным рассмотреть этот алгоритм подробнее.
Сначала - о свойствах, которым должны удовлетворять
текстуры, используемые для получения псевдоградаций яркости (в
общем случае). Пусть l - длина стороны текстуры, причем l
является числом вида 2^k (в данном случае l=16=2^4). Количество
текстур N равно l^2+1 (в данном случае N=16^2+1=257).
Во-первых, в каждой следующей текстуре на один включенный
пиксел больше, чем в предыдущей. Если пронумеровать текстуры от
0 до N-1, то номер текстуры будет равен числу включенных в ней
пикселов.
Во-вторых, любые две соседние текстуры отличаются лишь одним
пикселом. Учитывая предыдущее свойство, получаем, что каждая
следующая текстура получается из предыдущей путем включения
еще одного пиксела.
В-третьих, включенные и выключенные пикселы в каждой
текстуре располагаются равномерно. Что это значит? Возьмем
текстуру и разделим ее на четыре равные части:

┌─────┬─────┐
│ | │
│ | │
├-----+-----┤
│ | │
│ | │
└─────┴─────┘

Рис. 20

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 28 .C ══════════════════

Комментарии
───────────

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

┌──────────────────┬───────────────────────────────────────────┐
│ Номер │ Подпрограммы, │
│ процедуры вывода │ не используемые в этой процедуре │
├──────────────────┼───────────────────────────────────────────┤
│ 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 │
└──────────────────┴───────────────────────────────────────────┘
Табл. 1

* * *

Как выводить изображение с увеличением? Если требуется
увеличение в два раза (размер выводимой области изображения
тогда будет 128*96), то в начало процедуры 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... раз (размер выводимой
области тогда будет соответственно 64*48, 32*24...), то ставим
не одну, а две, три... команды 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 различных текстур 16*16. Простой подсчет
показывает, что для их хранения требуется 257*16*16/8=8224 байта
памяти, или более 8 КБ. Тратить столько памяти не хотелось, и я
использовал такой прием: когда нужно узнать, включен или
выключен пиксел с данными координатами, находящийся в
определенной текстуре, ответ получается с помощью специального
алгоритма, основанного на свойствах текстур. Представляется
интересным рассмотреть этот алгоритм подробнее.
Сначала - о свойствах, которым должны удовлетворять
текстуры, используемые для получения псевдоградаций яркости (в
общем случае). Пусть l - длина стороны текстуры, причем l
является числом вида 2^k (в данном случае l=16=2^4). Количество
текстур N равно l^2+1 (в данном случае N=16^2+1=257).
Во-первых, в каждой следующей текстуре на один включенный
пиксел больше, чем в предыдущей. Если пронумеровать текстуры от
0 до N-1, то номер текстуры будет равен числу включенных в ней
пикселов.
Во-вторых, любые две соседние текстуры отличаются лишь одним
пикселом. Учитывая предыдущее свойство, получаем, что каждая
следующая текстура получается из предыдущей путем включения
еще одного пиксела.
В-третьих, включенные и выключенные пикселы в каждой
текстуре располагаются равномерно. Что это значит? Возьмем
текстуру и разделим ее на четыре равные части:

┌─────┬─────┐
│ | │
│ | │
├-----+-----┤
│ | │
│ | │
└─────┴─────┘

Рис. 20

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 29 .C ══════════════════

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

┌───────┬───────┐ ┌───────┬───────┐ ┌───────┬───────┐
│ | │ │ | │ │ | │
│[n/4]+1| [n/4] │ │[n/4]+1| [n/4] │ │[n/4]+1|[n/4+1]│
│ | │ │ | │ │ | │
├-------+-------┤ ├-------+-------┤ ├-------+-------┤
│ | │ │ | │ │ | │
│ [n/4] | [n/4] │ │ [n/4] |[n/4+1]│ │ [n/4] |[n/4+1]│
│ | │ │ | │ │ | │
└───────┴───────┘ └───────┴───────┘ └───────┴───────┘

а) m=1 б) m=2 в) m=3

Рис. 21

Приведенной выше информации достаточно как для того, чтобы
построить все N текстур, так и для того, чтобы определить,
включен или выключен пиксел с данными координатами, находящийся
в определенной текстуре.
Алгоритм очень простой. Пусть известны l - длина стороны
текстуры, n - номер текстуры, x и y - координаты пиксела на
экране. Hужно получить a - состояние пиксела: 0 - выключен, 1 -
включен.

1. Если размер текстуры - 1*1, то состояние пиксела равно
номеру текстуры (т.е. если 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 байтами!

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 29 .C ══════════════════

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

┌───────┬───────┐ ┌───────┬───────┐ ┌───────┬───────┐
│ | │ │ | │ │ | │
│[n/4]+1| [n/4] │ │[n/4]+1| [n/4] │ │[n/4]+1|[n/4+1]│
│ | │ │ | │ │ | │
├-------+-------┤ ├-------+-------┤ ├-------+-------┤
│ | │ │ | │ │ | │
│ [n/4] | [n/4] │ │ [n/4] |[n/4+1]│ │ [n/4] |[n/4+1]│
│ | │ │ | │ │ | │
└───────┴───────┘ └───────┴───────┘ └───────┴───────┘

а) m=1 б) m=2 в) m=3

Рис. 21

Приведенной выше информации достаточно как для того, чтобы
построить все N текстур, так и для того, чтобы определить,
включен или выключен пиксел с данными координатами, находящийся
в определенной текстуре.
Алгоритм очень простой. Пусть известны l - длина стороны
текстуры, n - номер текстуры, x и y - координаты пиксела на
экране. Нужно получить a - состояние пиксела: 0 - выключен, 1 -
включен.

1. Если размер текстуры - 1*1, то состояние пиксела равно
номеру текстуры (т.е. если 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 байтами!

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 30 .C ══════════════════

Приложение 4
────────────

Здесь приведены тексты процедур для работы с pcx-файлами,
содержащими черно-белые изображения с 256 градациями яркости.
Кратко расскажу о формате таких файлов - это необходимо для
понимания логики работы процедур.

╔════════════════════════╤════════════╗
║ Часть файла │ Длина ║
╠════════════════════════╪════════════╣
║ Заголовок │ 128 ║
╟────────────────────────┼────────────╢
║ Упакованная информация │ переменная ║
║ о пикселах изображения │ ║
╟────────────────────────┼────────────╢
║ Тип палитры │ 1 ║
╟────────────────────────┼────────────╢
║ Палитра │ 768 ║
╚════════════════════════╧════════════╝
Табл. 2

_Заголовок файла_ содержит информацию о размерах
изображения, количестве цветов и т.п. В процедуре чтения
(READ_PCX) заголовок игнорируется, так как эти данные
предполагаются уже известными. В процедуре записи (MAKE_PCX)
заголовок записывается всегда один и тот же.
_Информация о пикселах изображения_ хранится в упакованном
виде. При упаковке последовательность одинаковых байтов
заменяется на два байта: счетчик и оригинал. Чтобы можно было
распознавать байт-счетчик при распаковке, его старшие два бита
устанавливаются в 1, а длина последовательности (от 1 до 63)
хранится в младших шести битах. Если длина последовательности
больше 63, записывается несколько пар "счетчик-значение". Если
при упаковке попадается отдельный байт, старшие два бита
которого равны 1, то, чтобы при распаковке он не был перепутан с
байтом-счетчиком, перед ним ставится байт-счетчик с количеством
повторений, равным 1.
_Тип палитры_ - #0A, если значения байтов палитры находятся
в диапазоне 0-63, и #0C, если в диапазоне 0-255. В процедуре
чтения тип палитры игнорируется, а в процедуре записи -
записывается #0C.
_Палитра_ - в ней содержится информация о компонентах R, G,
B каждого из 256 цветов изображения. В процедуре чтения палитра
игнорируется, а в процедуре записи - записывается
последовательность байтов (0,0,0, 1,1,1,..., 255,255,255).

Исходя из вышесказанного, можно определить максимальную
длину файла: 2 байта на пиксел - это 256*192*2 = #18000, еще
#80 байтов - заголовок, 1 байт - тип палитры и #300 байтов -
сама палитра, итого #18381 байт. Hо в TR-DOS длина файла не
может превышать #FF00 байтов. Поэтому в процедуре MAKE_PCX, если
длина файла получается больше #FF00, на диск записываются два
файла: первый длиной #FF00, а во втором - остаток.

Итак, тексты процедур.

1. READ_PCX - чтение изображения из pcx-файла в три банка
памяти.

Вход: имя и расширение файла указано в FILENAME, номера банков
памяти - в N_BANK.
Выход: A=1 - OK, A=0 - файл не найден.

;Структуры данных:

BUFER EQU #7000 ;256-байтный буфер (адрес кратен 256!)

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

ORG #6000

READ_PCX LD HL,BUFER
LD (GET_B_0),HL
LD HL,N_BANK
LD (PUT_B_1),HL
LD HL,#C000
LD (PUT_B_2),HL

;Ищем файл:

LD HL,FILENAME
LD C,#13
CALL #3D13

LD C,#0A
CALL #3D13

LD A,C
INC A
RET Z ;нет такого файла

DEC A
LD C,8 ;читаем
CALL #3D13 ;дескриптор

;Определяем трек/секторный адрес начала
;файла:

LD HL,(#5CEB)
LD (GET_B_1),HL

;Пропускаем первые 128 байтов файла -
;это заголовок.

LD B,128
M1 CALL GET_B
DJNZ M1

LD HL,#C000 ;кол-во pix.

;Главный цикл:

M3 CALL GET_B ;читаем байт

;Если это байт-счетчик, то два его
;старших бита установлены в 1, а в
;младших 6 битах - количество
;повторений.

LD B,1
LD C,A
CPL
AND %11000000
LD A,C
JR NZ,M2

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 30 .C ══════════════════

Приложение 4
────────────

Здесь приведены тексты процедур для работы с pcx-файлами,
содержащими черно-белые изображения с 256 градациями яркости.
Кратко расскажу о формате таких файлов - это необходимо для
понимания логики работы процедур.

╔════════════════════════╤════════════╗
║ Часть файла │ Длина ║
╠════════════════════════╪════════════╣
║ Заголовок │ 128 ║
╟────────────────────────┼────────────╢
║ Упакованная информация │ переменная ║
║ о пикселах изображения │ ║
╟────────────────────────┼────────────╢
║ Тип палитры │ 1 ║
╟────────────────────────┼────────────╢
║ Палитра │ 768 ║
╚════════════════════════╧════════════╝
Табл. 2

_Заголовок файла_ содержит информацию о размерах
изображения, количестве цветов и т.п. В процедуре чтения
(READ_PCX) заголовок игнорируется, так как эти данные
предполагаются уже известными. В процедуре записи (MAKE_PCX)
заголовок записывается всегда один и тот же.
_Информация о пикселах изображения_ хранится в упакованном
виде. При упаковке последовательность одинаковых байтов
заменяется на два байта: счетчик и оригинал. Чтобы можно было
распознавать байт-счетчик при распаковке, его старшие два бита
устанавливаются в 1, а длина последовательности (от 1 до 63)
хранится в младших шести битах. Если длина последовательности
больше 63, записывается несколько пар "счетчик-значение". Если
при упаковке попадается отдельный байт, старшие два бита
которого равны 1, то, чтобы при распаковке он не был перепутан с
байтом-счетчиком, перед ним ставится байт-счетчик с количеством
повторений, равным 1.
_Тип палитры_ - #0A, если значения байтов палитры находятся
в диапазоне 0-63, и #0C, если в диапазоне 0-255. В процедуре
чтения тип палитры игнорируется, а в процедуре записи -
записывается #0C.
_Палитра_ - в ней содержится информация о компонентах R, G,
B каждого из 256 цветов изображения. В процедуре чтения палитра
игнорируется, а в процедуре записи - записывается
последовательность байтов (0,0,0, 1,1,1,..., 255,255,255).

Исходя из вышесказанного, можно определить максимальную
длину файла: 2 байта на пиксел - это 256*192*2 = #18000, еще
#80 байтов - заголовок, 1 байт - тип палитры и #300 байтов -
сама палитра, итого #18381 байт. Но в TR-DOS длина файла не
может превышать #FF00 байтов. Поэтому в процедуре MAKE_PCX, если
длина файла получается больше #FF00, на диск записываются два
файла: первый длиной #FF00, а во втором - остаток.

Итак, тексты процедур.

1. READ_PCX - чтение изображения из pcx-файла в три банка
памяти.

Вход: имя и расширение файла указано в FILENAME, номера банков
памяти - в N_BANK.
Выход: A=1 - OK, A=0 - файл не найден.

;Структуры данных:

BUFER EQU #7000 ;256-байтный буфер (адрес кратен 256!)

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

ORG #6000

READ_PCX LD HL,BUFER
LD (GET_B_0),HL
LD HL,N_BANK
LD (PUT_B_1),HL
LD HL,#C000
LD (PUT_B_2),HL

;Ищем файл:

LD HL,FILENAME
LD C,#13
CALL #3D13

LD C,#0A
CALL #3D13

LD A,C
INC A
RET Z ;нет такого файла

DEC A
LD C,8 ;читаем
CALL #3D13 ;дескриптор

;Определяем трек/секторный адрес начала
;файла:

LD HL,(#5CEB)
LD (GET_B_1),HL

;Пропускаем первые 128 байтов файла -
;это заголовок.

LD B,128
M1 CALL GET_B
DJNZ M1

LD HL,#C000 ;кол-во pix.

;Главный цикл:

M3 CALL GET_B ;читаем байт

;Если это байт-счетчик, то два его
;старших бита установлены в 1, а в
;младших 6 битах - количество
;повторений.

LD B,1
LD C,A
CPL
AND %11000000
LD A,C
JR NZ,M2

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 31 .C ══════════════════

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

LD A,%00111111
AND C
LD B,A
CALL GET_B

M2 CALL PUT_B
DEC HL
DJNZ M2

LD A,H ;все #C000 пикселов?
OR L
JR NZ,M3

INC A ;A:=1
RET

;Имя файла:

FILENAME DB "picture pcx"

;Hомера используемых банков памяти:

N_BANK DB #10,#11,#13

;---------------------------------------
;Процедура GET_B - чтение байта из
;файла.

GET_B PUSH HL
PUSH BC

LD HL,0 ;адрес в буфере
GET_B_0 EQU $-2
LD A,L
AND A
JR NZ,GET_B_3

;Hадо прочитать сектор файла:

PUSH HL

LD B,1
LD DE,0
GET_B_1 EQU $-2
PUSH DE
LD HL,BUFER
LD C,5
CALL #3D13

;Увеличиваем трек/секторный адрес:

POP DE
INC E
BIT 4,E
JR Z,GET_B_2
LD E,0
INC D
GET_B_2 LD (GET_B_1),DE

POP HL

;Читаем байт из буфера:

GET_B_3 LD A,(HL)
INC L
LD (GET_B_0),HL

POP BC
POP HL
RET

;---------------------------------------
;Процедура PUT_B - помещение информации
;об очередном пикселе в память.

PUT_B PUSH AF
PUSH HL

PUSH AF
PUSH BC
LD HL,0
PUT_B_1 EQU $-2
LD A,(HL) ;N банка
LD BC,#7FFD
OUT (C),A
POP BC
POP AF

LD HL,0 ;адрес
PUT_B_2 EQU $-2
LD (HL),A

INC HL ;увелич. адреса
LD A,H
OR L
JR NZ,PUT_B_3

LD HL,(PUT_B_1) ;переход к
INC HL ;следующему
LD (PUT_B_1),HL ;банку
LD HL,#C000

PUT_B_3 LD (PUT_B_2),HL

POP HL
POP AF
RET

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 31 .C ══════════════════

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

LD A,%00111111
AND C
LD B,A
CALL GET_B

M2 CALL PUT_B
DEC HL
DJNZ M2

LD A,H ;все #C000 пикселов?
OR L
JR NZ,M3

INC A ;A:=1
RET

;Имя файла:

FILENAME DB "picture pcx"

;Номера используемых банков памяти:

N_BANK DB #10,#11,#13

;---------------------------------------
;Процедура GET_B - чтение байта из
;файла.

GET_B PUSH HL
PUSH BC

LD HL,0 ;адрес в буфере
GET_B_0 EQU $-2
LD A,L
AND A
JR NZ,GET_B_3

;Надо прочитать сектор файла:

PUSH HL

LD B,1
LD DE,0
GET_B_1 EQU $-2
PUSH DE
LD HL,BUFER
LD C,5
CALL #3D13

;Увеличиваем трек/секторный адрес:

POP DE
INC E
BIT 4,E
JR Z,GET_B_2
LD E,0
INC D
GET_B_2 LD (GET_B_1),DE

POP HL

;Читаем байт из буфера:

GET_B_3 LD A,(HL)
INC L
LD (GET_B_0),HL

POP BC
POP HL
RET

;---------------------------------------
;Процедура PUT_B - помещение информации
;об очередном пикселе в память.

PUT_B PUSH AF
PUSH HL

PUSH AF
PUSH BC
LD HL,0
PUT_B_1 EQU $-2
LD A,(HL) ;N банка
LD BC,#7FFD
OUT (C),A
POP BC
POP AF

LD HL,0 ;адрес
PUT_B_2 EQU $-2
LD (HL),A

INC HL ;увелич. адреса
LD A,H
OR L
JR NZ,PUT_B_3

LD HL,(PUT_B_1) ;переход к
INC HL ;следующему
LD (PUT_B_1),HL ;банку
LD HL,#C000

PUT_B_3 LD (PUT_B_2),HL

POP HL
POP AF
RET

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 32 .C ══════════════════

2. MAKE_PCX - запись изображения из трех банков памяти в
pcx-файл.

Вход: имя и расширение файла указано в NAME_EXT, номера банков
памяти - в N_BANK.
Выход: A=1 - OK, A=0 - записать файл не удалось (нет места на
диске или в каталоге диска).

;Структуры данных:

FILE_BUFER EQU #7000 ;буфер для записи
CAT_BUFER EQU #7100 ;каталог диска

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

ORG #6000

MAKE_PCX CALL INIT_GET

;Сначала преобразуем "вхолостую", чтобы
;узнать размер получаемого файла. В это
;время на бордюр выводятся цветные
;полосы - чтобы показать, что компьютер
;не завис.

LD D,0 ;DHL - длина
LD HL,#381
LD BC,1

MAKE_PCX_1 CALL GET_BYTE
JR C,MAKE_PCX_2
AND 7 ;Изменяем
OUT (254),A ;BORDER
ADD HL,BC
LD A,D
ADC A,0
LD D,A
JR MAKE_PCX_1

MAKE_PCX_2 LD (LEN_FILE),HL
LD A,D
LD (LEN_FILE+2),A

;Длина в байтах определена, теперь
;получаем длину в секторах:

LD BC,255
ADD HL,BC
LD A,D
ADC A,0
LD L,H
LD H,A ;HL - длина.
PUSH HL ;Запомнили ее...

;Читаем каталог:

LD HL,CAT_BUFER
LD DE,0
LD B,9
LD C,5
CALL #3D13

;Проверка свободного места в области
;файлов:

LD HL,(CAT_BUFER+#8E5)
POP DE ;длина файла
XOR A ;A:=0, флаг C сброшен
SBC HL,DE
RET C ;Hет места на диске!

;Коррекция свободного места:

LD (CAT_BUFER+#8E5),HL

;Теперь определяем, сколько будет
;файлов: один или два?

LD A,1 ;кол-во файлов
EX DE,HL
LD DE,255
SBC HL,DE ;флаг C сброшен!
JR C,MAKE_PCX_7 ;1 файл
INC A ;2 файла
MAKE_PCX_7 LD (FILES),A

;Проверка свободного места в области
;каталога:

LD HL,CAT_BUFER+#8E4
LD B,(HL)
ADD A,B
LD (HL),A ;коррекция
CP 128+1
LD A,0
RET NC ;Hет места!

;Определяем номер первого сектора файла:

LD HL,(CAT_BUFER+#8E1)
LD (DISK_ADR),HL

;Помещаем в каталог информацию о файле
;(или о двух файлах, если длина больше
;#FF00):

LD H,0
LD L,B
ADD HL,HL
ADD HL,HL
ADD HL,HL
ADD HL,HL ;*16
LD DE,CAT_BUFER
ADD HL,DE

EX DE,HL
LD HL,NAME_EXT ;Заголовок
LD BC,11
LDIR

LD A,(FILES)
CP 2
JR NZ,MAKE_PCX_A

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 32 .C ══════════════════

2. MAKE_PCX - запись изображения из трех банков памяти в
pcx-файл.

Вход: имя и расширение файла указано в NAME_EXT, номера банков
памяти - в N_BANK.
Выход: A=1 - OK, A=0 - записать файл не удалось (нет места на
диске или в каталоге диска).

;Структуры данных:

FILE_BUFER EQU #7000 ;буфер для записи
CAT_BUFER EQU #7100 ;каталог диска

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

ORG #6000

MAKE_PCX CALL INIT_GET

;Сначала преобразуем "вхолостую", чтобы
;узнать размер получаемого файла. В это
;время на бордюр выводятся цветные
;полосы - чтобы показать, что компьютер
;не завис.

LD D,0 ;DHL - длина
LD HL,#381
LD BC,1

MAKE_PCX_1 CALL GET_BYTE
JR C,MAKE_PCX_2
AND 7 ;Изменяем
OUT (254),A ;BORDER
ADD HL,BC
LD A,D
ADC A,0
LD D,A
JR MAKE_PCX_1

MAKE_PCX_2 LD (LEN_FILE),HL
LD A,D
LD (LEN_FILE+2),A

;Длина в байтах определена, теперь
;получаем длину в секторах:

LD BC,255
ADD HL,BC
LD A,D
ADC A,0
LD L,H
LD H,A ;HL - длина.
PUSH HL ;Запомнили ее...

;Читаем каталог:

LD HL,CAT_BUFER
LD DE,0
LD B,9
LD C,5
CALL #3D13

;Проверка свободного места в области
;файлов:

LD HL,(CAT_BUFER+#8E5)
POP DE ;длина файла
XOR A ;A:=0, флаг C сброшен
SBC HL,DE
RET C ;Нет места на диске!

;Коррекция свободного места:

LD (CAT_BUFER+#8E5),HL

;Теперь определяем, сколько будет
;файлов: один или два?

LD A,1 ;кол-во файлов
EX DE,HL
LD DE,255
SBC HL,DE ;флаг C сброшен!
JR C,MAKE_PCX_7 ;1 файл
INC A ;2 файла
MAKE_PCX_7 LD (FILES),A

;Проверка свободного места в области
;каталога:

LD HL,CAT_BUFER+#8E4
LD B,(HL)
ADD A,B
LD (HL),A ;коррекция
CP 128+1
LD A,0
RET NC ;Нет места!

;Определяем номер первого сектора файла:

LD HL,(CAT_BUFER+#8E1)
LD (DISK_ADR),HL

;Помещаем в каталог информацию о файле
;(или о двух файлах, если длина больше
;#FF00):

LD H,0
LD L,B
ADD HL,HL
ADD HL,HL
ADD HL,HL
ADD HL,HL ;*16
LD DE,CAT_BUFER
ADD HL,DE

EX DE,HL
LD HL,NAME_EXT ;Заголовок
LD BC,11
LDIR

LD A,(FILES)
CP 2
JR NZ,MAKE_PCX_A

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 33 .C ══════════════════

;Записывается два файла.
;Первый файл:

EX DE,HL
LD (HL),0 ;Длина:
INC HL
LD (HL),#FF ;#FF00 байтов,
INC HL
LD (HL),#FF ;#FF секторов.
INC HL

LD A,#FF ;Адрес на
CALL NEW_TS ;диске.

;Второй файл:

EX DE,HL
LD HL,NAME_EXT ;Заголовок.
LD BC,8
LDIR
LD A,"0" ;Первый символ
LD (DE),A ;расширения - "0"
INC DE
INC HL
LDI
LDI

EX DE,HL ;Длина:
LD A,(LEN_FILE) ;младший
LD (HL),A ;байт...
INC HL

PUSH HL
LD HL,(LEN_FILE+1)
DEC H
INC HL ;-#FF00
LD A,L ;и старший
POP HL ;байт.

LD (HL),A

PUSH HL
DEC HL
LD L,(HL)
LD H,A
INC H
DEC HL
LD A,H ;Длина в секторах.
POP HL

INC HL
LD (HL),A

INC HL
CALL NEW_TS
JR MAKE_PCX_9

;Записывается один файл:

MAKE_PCX_A EX DE,HL
LD DE,(LEN_FILE)
LD (HL),E ;Длина
INC HL ;в байтах.
LD (HL),D
INC HL

INC D ;Длина
DEC DE ;в секторах.
LD (HL),D
INC HL

LD A,D
CALL NEW_TS

;Записываем каталог:

MAKE_PCX_9 LD HL,CAT_BUFER
LD DE,0
LD B,9
LD C,6
CALL #3D13

;Записываем содержимое файла:

CALL OPEN_FILE

;Заголовок (128 байтов):

LD HL,HEADER
LD B,128

MAKE_PCX_6 LD A,(HL)
CALL PUT_BYTE
INC HL
DJNZ MAKE_PCX_6

;Данные о пикселах:

CALL INIT_GET

MAKE_PCX_3 CALL GET_BYTE
JR C,MAKE_PCX_4
CALL PUT_BYTE
JR MAKE_PCX_3

;Тип палитры:

MAKE_PCX_4 LD A,#0C
CALL PUT_BYTE

;Палитра (#300 байтов):

XOR A
MAKE_PCX_5 CALL PUT_BYTE
CALL PUT_BYTE
CALL PUT_BYTE
INC A
JR NZ,MAKE_PCX_5

;Закрываем файл:

CALL CLOSE_FILE
LD A,1
RET

NAME_EXT DB "picture pcx" ;имя
LEN_FILE DS 3 ;длина pcx-файла
FILES DS 1 ;сколько будет файлов

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 02 Oct 2002
Hello, All!

═══════════════════ 34 .C ══════════════════

;---------------------------------------
;Процедура NEW_TS помещает по указанному
;в HL адресу сведения о первом
;свободном треке/секторе и обновляет
;эти сведения в системном секторе.
;
;Вход: A - длина файла в секторах.
;Выход: HL:=HL+2.

NEW_TS LD DE,(CAT_BUFER+#8E1)
LD (HL),E
INC HL ;Адрес на
LD (HL),D ;диске.
INC HL

;Увеличиваем номера первых незанятых
;трека/сектора:

LD B,A ;Длина в секторах
AND 15
ADD A,E
BIT 4,A
RES 4,A
LD E,A
JR Z,NEW_TS_1
INC D
NEW_TS_1 LD A,B
RRA
RRA
RRA
RRA
AND 15
ADD A,D
LD D,A
LD (CAT_BUFER+#8E1),DE

RET

;---------------------------------------
;Процедура GET_BYTE возвращает в
;аккумуляторе очередной байт упакованных
;данных о пикселах. Если флаг C
;установлен - значит, данные кончились.
;
;BC,DE,HL не изменяются.

STATUS DS 1 ;N состояния проц.
BYTE_B DS 1 ;запомненный байт

;Инициализация:

INIT_GET LD A,1
LD (STATUS),A
CALL INIT_PIX
RET

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

GET_BYTE PUSH BC
PUSH DE
PUSH HL

LD A,(STATUS)
CP 2
JR Z,GET_BYTE_2

;1-е состояние:

LD C,1 ;счетчик одинаковых
CALL GET_PIX
JR C,GET_EXIT

LD (BYTE_B),A

GET_B_3 LD A,C ;уже есть 63
CP 63 ;одинаковых байта?
JR Z,GET_B_1

CALL GET_PIX ;берем еще байт
JR C,GET_B_4

LD HL,BYTE_B ;сравниваем...
CP (HL)
JR NZ,GET_B_4

INC C
JR GET_B_3

GET_B_4 CALL PREV_PIX ;сдвиг назад

GET_B_2 LD A,(BYTE_B);байт выглядит
AND %11000000 ;как
CP %11000000 ;байт-счетчик?
JR Z,GET_B_1

LD A,C ;одно
DEC A ;повторение?
JR NZ,GET_B_1

LD A,(BYTE_B)
AND A ;сбрас. флаг C
JR GET_EXIT

GET_B_1 LD A,2
LD (STATUS),A
LD A,C
OR %11000000 ;сбрас. флаг C
JR GET_EXIT

;2-е состояние:

GET_BYTE_2 LD A,1
LD (STATUS),A
LD A,(BYTE_B)
AND A ;сбрас. флаг C

GET_EXIT POP HL
POP DE
POP BC
RET

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 03 Oct 2002
Hello, All!

═══════════════════ 35 .C ══════════════════

;---------------------------------------
;Процедура GET_PIX последовательно
;возвращает данные о пикселах. Если
;флаг C установлен - значит, данные
;кончились.
;
;BC,DE,HL не изменяются.

GET_ADR DS 2 ;адрес в банке памяти
GET_BANK DS 1 ;номер банка в таблице

;Инициализация:

INIT_PIX LD HL,#C000-1
LD (GET_ADR),HL
XOR A
LD (GET_BANK),A
RET

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

GET_PIX PUSH HL

LD HL,(GET_ADR)
INC HL
LD (GET_ADR),HL
LD A,H
OR L
JR NZ,GET_PIX_1

LD HL,#C000
LD (GET_ADR),HL
LD A,(GET_BANK)
INC A
LD (GET_BANK),A
CP 3
JR NZ,GET_PIX_1

POP HL
SCF
RET

GET_PIX_1 PUSH BC
PUSH DE
LD BC,#7FFD
LD HL,N_BANK
LD A,(GET_BANK)
LD D,0
LD E,A
ADD HL,DE
LD A,(HL)
OUT (C),A
LD HL,(GET_ADR)
LD A,(HL)
LD D,#10
OUT (C),D
POP DE
POP BC
POP HL

AND A ;сбрас. флаг C
RET

;Сдвиг на пиксел назад:

PREV_PIX PUSH HL

LD HL,(GET_ADR)
DEC HL
LD A,H
CP #BF
JR NZ,PREV_PIX_1
LD A,L
CP #FF
JR NZ,PREV_PIX_1

LD HL,#FFFF
LD A,(GET_BANK)
DEC A
LD (GET_BANK),A

PREV_PIX_1 LD (GET_ADR),HL

POP HL
RET

;Банки данных с информацией о пикселах:

N_BANK DB #10,#11,#13

;---------------------------------------
;Процедуры для побайтной записи
;содержимого файла с буферизацией.

IN_BUF DS 1 ;кол-во байтов в
;буфере
DISK_ADR DS 2 ;адрес сектора

;OPEN_FILE - открыть файл.

OPEN_FILE XOR A
LD (IN_BUF),A
RET

;PUT_BYTE - записать байт в файл.
;Вход: A - записываемое значение.
;Выход: регистры не изменены.

PUT_BYTE PUSH AF
PUSH BC
PUSH DE
PUSH HL

LD B,A
LD A,(IN_BUF)
LD HL,FILE_BUFER
LD D,0
LD E,A
ADD HL,DE
LD (HL),B

INC A
LD (IN_BUF),A

;Если буфер заполнился - записываем его:

CALL Z,CLOSE_FILE

POP HL
POP DE
POP BC
POP AF
RET

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 03 Oct 2002
Hello, All!

═══════════════════ 36 .C ══════════════════

;CLOSE_FILE - закрыть файл.
;Содержимое буфера записывается на диск.

CLOSE_FILE LD DE,(DISK_ADR)
PUSH DE
LD B,1
LD C,6
LD HL,FILE_BUFER
CALL #3D13
POP DE

INC E
LD A,E
AND 15
LD E,A
JR NZ,CLOSE_EXIT
INC D
CLOSE_EXIT LD (DISK_ADR),DE

RET

;---------------------------------------
;128-байтный заголовок:

HEADER

db #0A,#05,#01,#08,#00,#00,#00,#00
db #FF,#00,#BF,#00,#00,#01,#C0,#00
db #60,#61,#62,#63,#64,#65,#66,#67
db #68,#69,#6A,#6B,#6C,#6D,#6E,#6F
db #70,#71,#72,#73,#74,#75,#76,#77
db #78,#79,#7A,#7B,#3F,#00,#00,#00
db #50,#D6,#B0,#01,#14,#01,#00,#00
db #C5,#B9,#F7,#BF,#C4,#30,#92,#81
db #01,#01,#00,#01,#01,#00,#00,#00
db #00,#00,#00,#00,#00,#00,#00,#00
db #00,#00,#00,#00,#00,#00,#00,#00
db #00,#00,#00,#00,#00,#00,#00,#00
db #00,#00,#00,#00,#00,#00,#00,#00
db #00,#00,#00,#00,#00,#00,#00,#00
db #00,#00,#00,#00,#00,#00,#00,#00
db #00,#00,#00,#00,#00,#00,#00,#00

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 03 Oct 2002
Hello, All!

═══════════════════ 37 .C ══════════════════

3. SCR2BANK - преобразование изображения в спектрумовском
формате в черно-белое изображение с 256 градациями яркости,
расположенное в трех банках памяти. С помощью предыдущей
процедуры полученное изображение можно записать в pcx-файл.

Вход: исходное изображение загружено с #4000, номера банков
памяти - в N_BANK, яркости градаций - в BRIGHT_0 и
BRIGHT_1.

ORG #8000

SCR2BANK LD HL,N_BANK
LD (PUT_B_1),HL
LD HL,#C000
LD (PUT_B_2),HL

LD DE,0
MAIN CALL GET_PIX

LD B,A
AND 7 ;индикация
OUT (254),A ;на бордюре
LD A,B

CALL PUT_B
INC D
JR NZ,MAIN
INC E
LD A,E
CP 192
JR NZ,MAIN

RET

;Hомера используемых банков памяти:

N_BANK DB #10,#11,#13

;---------------------------------------
;Процедура GET_PIX возвращает яркость
;указанного пиксела изображения.
;
;Вход: DE - координаты пиксела.
;Выход: A - яркость (0..255).

GET_PIX PUSH DE

;Определяем INK, PAPER, BRIGHT:

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
LD A,(HL)
RRA
RRA
AND %00010000
LD (BRIGHT),A

LD A,(HL)
AND 7
LD (INK),A

LD A,(HL)
RRA
RRA

RRA
AND 7
LD (PAPER),A

POP DE
PUSH DE

CALL BYTE
AND (HL)
LD A,0
PAPER EQU $-1
JR Z,GET_PIX_1
LD A,0
INK EQU $-1
GET_PIX_1 ADD A,A
ADD A,0
BRIGHT EQU $-1
LD E,A
LD D,0
LD HL,BRIGHT_0
ADD HL,DE
LD E,(HL)
INC HL
LD D,(HL)
LD HL,#80
ADD HL,DE
LD A,H

POP DE
RET

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

;---------------------------------------
;Яркости градаций в формате 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

;---------------------------------------
;Процедура PUT_B - запись в память
;информации об очередном пикселе.
;
;Вход: A - значение пиксела.

PUT_B PUSH AF ;Устанавливаем
LD A,(0) ;нужный
PUT_B_1 EQU $-2 ;банк
LD BC,#7FFD ;памяти.
OUT (C),A
POP AF

LD HL,0 ;Записываем.
PUT_B_2 EQU $-2
LD (HL),A

INC HL ;Увеличиваем
LD A,H ;адрес.
OR L
JR NZ,PUT_B_3

LD HL,(PUT_B_1) ;Переход к
INC HL ;следующему
LD (PUT_B_1),HL ;банку.
LD HL,#C000

PUT_B_3 LD (PUT_B_2),HL
RET

════════════════════════════════════════════════

С уважением, Иван Рощин.

от: Ivan Roshin
кому: All
дата: 03 Oct 2002
Hello, All!

═══════════════════ 37 .C ══════════════════

3. SCR2BANK - преобразование изображения в спектрумовском
формате в черно-белое изображение с 256 градациями яркости,
расположенное в трех банках памяти. С помощью предыдущей
процедуры полученное изображение можно записать в pcx-файл.

Вход: исходное изображение загружено с #4000, номера банков
памяти - в N_BANK, яркости градаций - в BRIGHT_0 и
BRIGHT_1.

ORG #8000

SCR2BANK LD HL,N_BANK
LD (PUT_B_1),HL
LD HL,#C000
LD (PUT_B_2),HL

LD DE,0
MAIN CALL GET_PIX

LD B,A
AND 7 ;индикация
OUT (254),A ;на бордюре
LD A,B

CALL PUT_B
INC D
JR NZ,MAIN
INC E
LD A,E
CP 192
JR NZ,MAIN

RET

;Номера используемых банков памяти:

N_BANK DB #10,#11,#13

;---------------------------------------
;Процедура GET_PIX возвращает яркость
;указанного пиксела изображения.
;
;Вход: DE - координаты пиксела.
;Выход: A - яркость (0..255).

GET_PIX PUSH DE

;Определяем INK, PAPER, BRIGHT:

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
LD A,(HL)
RRA
RRA
AND %00010000
LD (BRIGHT),A

LD A,(HL)
AND 7
LD (INK),A

LD A,(HL)
RRA
RRA
RRA
AND 7
LD (PAPER),A

POP DE
PUSH DE

CALL BYTE
AND (HL)
LD A,0
PAPER EQU $-1
JR Z,GET_PIX_1
LD A,0
INK EQU $-1
GET_PIX_1 ADD A,A
ADD A,0
BRIGHT EQU $-1
LD E,A
LD D,0
LD HL,BRIGHT_0
ADD HL,DE
LD E,(HL)
INC HL
LD D,(HL)
LD HL,#80
ADD HL,DE
LD A,H

POP DE
RET

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

;---------------------------------------
;Яркости градаций в формате 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

;---------------------------------------
;Процедура PUT_B - запись в память
;информации об очередном пикселе.
;
;Вход: A - значение пиксела.

PUT_B PUSH AF ;Устанавливаем
LD A,(0) ;нужный
PUT_B_1 EQU $-2 ;банк
LD BC,#7FFD ;памяти.
OUT (C),A
POP AF

LD HL,0 ;Записываем.
PUT_B_2 EQU $-2
LD (HL),A

INC HL ;Увеличиваем
LD A,H ;адрес.
OR L
JR NZ,PUT_B_3

LD HL,(PUT_B_1) ;Переход к
INC HL ;следующему
LD (PUT_B_1),HL ;банку.
LD HL,#C000

PUT_B_3 LD (PUT_B_2),HL
RET

════════════════════════════════════════════════

С уважением, Иван Рощин.




Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
В гостях у сказки - Сказка о рыбаке и рыбке (в компьютерном изложении).
Doors kick start - новая ОС для Спектрума - DoorsAQUA 7.1, быть или не быть? Breeze разьясняет.
Непроверен.сведенья - Новая рубрика ! Слухи, касающиеся Speccy. Пятого номера журнал "ZX-Format" НЕ ждите.
С миру по биту - письма читателей, Спектрум умер или еще нет?
Profi Club - Музыкальные редакторы в CP/M: ADJ, Synthmaster v1.3, Music.com.

В этот день...   20 апреля