Из журнала Deja Vu #07, Кемерово, 1999



(C) Max/Cyberax Software/BDA
__________________________________________

                DITHERING
                ---------

 ДИЗЕРИНГ, КАК ОН ЕСТЬ (И КАК ЕГО НЕТ :-)

 +-   -  - ----====<++>====---- -  -   -+
    По мотивам произведений Д.Роджерса


   В Deja Vu #06 DANIEL предложил называть
такой метод вывода графики 1=4*4. Знакомые
писишники говорят, что по научному все это
называется словом DITHERING. Т.к. писишни-
ки никогда не врут:-), остановимся на вто-
ром варианте.
   Не подумайте только, что  раз  уж  речь
зашла о выводе  графики, значит  я  сейчас
начну  строчить  всякие там процедуры типа
"вывод картинки 64*48 за  3  фрейма". Нет!
После FunTop'a это, в общем-то, пройденный
этап (но это только в общем...).
   Речь пойдет о генерации текстур. Приме-
нений этих текстур можно придумать доволь-
но много: заливка граней в 3D-grafix, кон-
вертация картинок 4- ...  256-color в чер-
но-белые, полутоновая печать, "увеличение"
количества цветов Спектрума (то, что и бы-
ло на FT'98) и т.п.

             *      *      *

   Здесь я  буду  рассматривать  получение
текстур размером N*N пикселов, где  N=2^Z,
Z=1, 2, 3 ... Принципа  получения  текстур
других  размеров  я  не  знаю, но  в конце
статьи все же приведу парочку.
------------------------------------------

   Для начала  немного базовой информации,
необходимой для понимания статьи:

 1)  Для умножения матрицы на число, необ-
     ходимо все ее  элементы  умножить  на
     это число.
 2)  Сумма двух матриц - матрица, элементы
     которой получены сложением соответст-
     вующих элементов первых двух матриц.

(Конечно, все это очевидно, но...)
------------------------------------------

   Текстуры размером N*N мы будем получать
из матриц таких же размеров. Каждая после-
дующая текстура будет отличаться от преды-
дущей добавочным включенным пикселом.
   Очевидно, что начав с пустой текстуры и
на каждом шаге включая по пикселу (пока не
заполним ее всю), мы всего получим (N^2)+1
градаций яркости (текстур). Начнем с 2*2:

                                +-  -+
                                |0  2|
Базовая матрица имеет вид: D2 = |    |
                                |3  1|
                                +-  -+

Теперь из матрицы  будем пошагово получать
спрайты текстур:

Шаг1)  Пустой спрайт 2*2 пиксела.

Шаг2)  Включаем в спрайте пиксел, которому
       в матрице D2 соответствует число 0.
       (координаты 0,0 от лев. верх. угла)

Шаг3)  Включаем  еще один пиксел,  имеющий
       те же координаты в спрайте,  что  и
       число 1 в матрице D2.
....

Шаг5)  Включаем пиксел с номером 3.


   Шагу номер 5 соответствует текстура ма-
ксимальной  яркости (спрайт  закрашен пол-
ностью).
   В результате, на каждом шаге мы получи-
ли по текстуре. Вот все пять градаций:
   Картинки,  выведенные  текстурами  2*2,
смотрятся гораздо лучше,чем 4*4 - выше ра-
зрешение (но меньше "цветов"...). Пример -
дема  Higher State (часть с Credits'ами).

   Перейдем к текстурам 4*4. Для  них мат-
рица выглядит так:

              +-          -+
              | 0  8  2 10 |
              |            |
              |12  4 14  6 |
         D4 = |            |
              | 3 11  1  9 |
              |            |
              |15  7 13  5 |
              +-          -+

   Возникает вопрос: откуда  она  взялась?
Все очень просто - она получилась из базо-
вой матрицы 2*2 по следующей формуле:

           +-                  -+
           |4*D2+0*U2 4*D2+2*U2 |
      D4 = |                    |
           |4*D2+3*U2 4*D2+1*U2 |
           +-                  -+
                           +- -+
                           |1 1|
        Здесь матрица U2 = |   |
                           |1 1|
                           +- -+

   Поясняю  более  подробно. Берем  первый
элемент матрицы D4: 4*D2+0*U2  или  просто
4*D2. Проводим вычисления:

         +- -+   +-     -+   +-   -+
         |0 2|   |4*0 4*2|   | 0  8|
     4 * |   | = |       | = |     |
         |3 1|   |4*3 4*1|   |12  4|
        |+- -+|  +-     -+  |+-   -+|
        +-----+             +-------+
       матрица D2            четверть
                            матрицы D4

   Получили четыре левых верхних  элемента
матрицы D4. Аналогично  получаются осталь-
ные три четверти. Покажу  для правой верх-
ней (4*D2+2*U2):

   +- -+     +- -+   +-      -+   +-   -+
   |0 2|     |1 1|   |0+2  8+2|   |2  10|
 4*|   | + 2*|   | = |        | = |     |
   |3 1|     |1 1|   |12+2 4+2|   |14  6|
  |+- -+|   |+- -+|  +-      -+  |+-   -+|
  +-----+   +-----+              +-------+
    D2        U2                  правая
                                  верхняя
                                 четверть
                                  м-цы D4

   Теперь из D4 можно получить набор текс-
тур 4*4. Делается это точно также, как и в
случае 2*2 (всего 17 шагов).
   Привожу результат:
   Думаю, вы уже  поняли общую  закономер-
ность получения  текстур  dithering'а N*N.
Она выражается рекуррентным соотношением:

     +-                                 -+
     |4*D(N/2)+0*U(N/2) 4*D(N/2)+2*U(N/2)|
D(N)=|                                   |
     |4*D(N/2)+3*U(N/2) 4*D(N/2)+1*U(N/2)|
     +-                                 -+

                +-       -+
                |1 1 ... 1|
                |         |
                |1 1 ... 1|
            U = |         |
                |.........|
                |         |
                |1 1 ... 1|
                +-       -+

   Это выражение связывает матрицу порядка
N с предыдущей матрицей N/2. То  есть, для
получения матрицы 8*8 необходимо будет ис-
пользовать полученную ранее D4.
   Of coz, я  могу и дальше издеваться над
читателями, получая  текстуры  более высо-
ких порядков, но  меня самого уже  достало
написание настолько нудной  статьи. Посему
буду постепенно закругляться (в смысле по-
ловину уже написал...).

             *      *      *

   Общие замечания:

   Во-первых, почему именно такой  способ?
Вообще для размера N*N пикселов существует
2^(N*N) различных спрайтов. Но  далеко  не
все из них подходят для  дизеринга. Наибо-
лее  оптимальных  конфигураций не так уж и
много. К  тому же тот факт, что две сосед-
ние (по яркости) текстуры отличаются всего
одним  пикселом, обеспечивает  минимальное
мельтешение точек (например, когда большая
часть  экрана плавно  меняет яркость путем
смены текстур).
   Во-вторых, можно запросто повернуть все
текстуры на 90  градусов. От  этого ничего
не изменится.
   В-третьих, на мой взгляд, юзать тексту-
ры размером больше чем 16*16 не имеет осо-
бого смысла.
   И в-четвертых, как  уже говорилось, для
размера N*N получается N*N+1 текстура. Ес-
ли Вы собираетесь применять их для  вывода
картинок (плазмы или чего-то еще), то  Вам
необходимо  выбрать  N*N  штук. Почему? Да
просто потому, что под цвет точки задейст-
вуется N бит  => всего будет N^2 различных
кодов  цвета. Какую  текстуру  выбросить -
Ваше дело...

             *      *      *

   Теперь о том,  куда можно текстуры при-
собачить. Ну ее, эту плазму... Лучше  рас-
скажу, как картинки конвертить!

   Вообще я знаю четыре метода конвертации
(спасибо "Алгоритмическим основам...").
   Суть первого  -  уменьшение  разрешения
картинки  с как бы сохранением  количества
цветов.
   Пример: пусть есть 16-ти колорное изоб-
ражение, скажем 256*192 пиксела. Разбиваем
его на квадратики  по 4  пиксела, затем  в
каждой из полученных клеток усредняем цве-
та 16-ти входящих в нее пикселов. Получаем
картинку (256/4)*(192/4)=64*48, причем,все
еще 16-цветную.
   Дальше все ясно: заменяем каждый пиксел
текстурой с соответствующим номером.
   Результат - ч/б изображение 256*192.

   Второй метод интереснее. Рассмотрим его
на той же картинке.
   Берем первую точку изображения. Ее код-
от 0 до 15. Заменяем  ее на пиксел из тек-
стуры с тем же кодом (номером), причем  из
текстуры берем пиксел с координатами:

        X=(A MOD 4) и Y=(B MOD 4)

 A и B - координаты исходной точки в цвет-
ном изображении.
   Повторив такой фокус для всех точек 16-
-цветной картинки, получим  скрин  256*192
ч/б, без потери разрешения.
   Можно даже не получать текстур, а рабо-
тать сразу с матрицей. Вот  алгоритм, сду-
тый, как вы сами понимаете, с книжки:
------------------------------------------

;Xmin, Xmax, Ymin, Ymax - пределы растра.
;N - размер матрицы (N*N - число цветов).
;Mod - функция, возвращающая остаток от
;целочисленного деления 1-го аргумента на
;2-й.

for Y=Ymin to Ymax
for X=Xmin to Xmax
    I=(X mod N)
    J=(Y mod N)
if I(X,Y)<D(I,J)  then
   Пиксел(X,Y)=Черный
else
   Пиксел(X,Y)=Белый
endif
Display Пиксел(X,Y); изображаем пиксел.
next X
next Y
finish
------------------------------------------

   Здесь массив I(X,Y)  -  исходный растр,
массив D(I,J) - наша матрица.
   Координаты I,J в матрице - от 0 до N-1.

   Третий  метод  тривиален: если  яркость
точки больше некоторого  порогового значе-
ния - пиксел белый, иначе - черный.

   Четвертый  метод я вообще  описывать не
собираюсь - кому надо, могут посмотреть...

   Да, чуть не  забыл! Конвертить-то  надо
не просто цветные картинки, а те,у которых
меньшему коду  цвета  точки  соответствует
меньшая яркость этой самой точки.
   Т.к. коды цвета определяются через R, G
и B компоненты, то  возникает задача опре-
деления яркости точки по трем байтам RGB.
   Но это выходит за рамки статьи...

             *      *      *

   Идем дальше. Поскольку  статья все-таки
по кодингу, привожу  здесь листинг генери-
ловки текстур 8*8:

        ORG   #6000
        ENT
;Written by Max/CBX/BDA, 27.10.98.
;      XAS Assembler v7.447.
;---------------------------------
DIT_GEN LD    E,64
DIT_GN1 LD    HL,MATRIX
        LD    D,D_TAB
        LD    B,8
DIT_GN2 LD    C,1
        LD    A,64
        SUB   E
DIT_GN3 CP    (HL)
        INC   HL
        RL    C
        JP    NC,DIT_GN3
        LD    A,C
        LD    (DE),A
        INC   D
        DJNZ  DIT_GN2
        DEC   E
        JP    P,DIT_GN1
        RET

D_TAB   EQU   #80; 2K size.

MATRIX  DB    1,33,9,41,3,35,11,43
        DB    49,17,57,25,51,19,59,27
        DB    13,45,5,37,15,47,7,39
        DB    61,29,53,21,63,31,55,23
        DB    4,36,12,44,2,34,10,42
        DB    52,20,60,28,50,18,58,26
        DB    16,48,8,40,14,46,6,38
        DB    64,32,56,24,62,30,54,22

Пример использования:

        LD    H,#80
        LD    L,XX
;L -номер текстуры от 0 до 64.
        LD    A,(HL); берем 1-й байт.
        INC   H
        ...
        LD    A,(HL); 2-й байт.
        INC H
        ...
;и т.д. всего 8 раз.

   Конкретное применение - скажем, заливка
граней в 3D-графике.

             *      *      *

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

+-------------+--------------+-----------+
|1-st, bright1|2-nd, bright0 | result br.|
+-------------+--------------+-----------+
|      0      |      0       |     0     |
+-------------+--------------+-----------+
|      0      |      1       |     1     |
+-------------+--------------+-----------+
|      1      |      0       |     2     |
+-------------+--------------+-----------+
|      1      |      1       |     3     |
+-------------+--------------+-----------+

   Первая и вторая колонки показывают сос-
тояния  пикселa  на 1-ом (яркость вкл.)  и
2-ом (яркость  выкл.)  битпланах. Третья -
визуальную яркость  пиксела при flashing'e
битпланов.
   Если теперь (фактически при  двух битах
на пиксел)  еще  и использовать  текстуры,
скажем 2*2, то  при  снижении   разрешения
всего в два раза, получаем  13 уровней яр-
кости для квадрата 2 на 2 пиксела!
   Из рисунка очевиден  принцип  получения
двубитплановых текстур из обычных.

        ---====< The END >====---