#21 Графика. Продолжение.

Вступление

Здравствуйте!
По прежнему призываю посетить наш форум -
http://www.borda.ru/index.pl?ibp7 - он ведь создавался специально для обсуждения вопросов программирования на Паскале (и не только для этого :). А вообще мы сегодня вернёмся к давно отложенной теме графике.

Теория

Итак мы уже немного (или уже много?) поработали с библиотекой bgi. И смогли оценить её недостатки и достоинства. Сегодня мы напишем собственную графическую библиотеку... Конечно она будет обладать меньшим набором функций и процедур, нежели BGI, но это будет НАША библиотека, и этим она будет хороша. Но сначал как всегда немного теории....

Как вы знаете, существуют графический и текстовый режимы. Сегодня нас в первую очередь интересует именно графический режим.

В графическом режиме мы имеем возможность управлять цветом каждой точки экрана независимо от других. Основными характеристиками режима являются его разрешение и количество бит на пиксель. Разрешение экрана - это высота и ширина экрана в точках (пикселях). Не надо путать размер экрана с его разрешением!!! Размер (например 17 дюймов) - это физическая величина, которую мы изменить не можем. Разрешение (например 800 на 600 пикселей) - это логическая величина, которую можно изменить програмно. Каждая логическая точка (пиксель) состоит из множества физических точек экрана. Пиксель (Pixel) - это сокращение от Picture Element - элемент изображения. Маркировка разрешения (800х600) говорит нам о количестве пикселей в строках (по-горизонтали) - 800 и количестве пикселей в столбцах (по вертикали) - 600.

Количество бит видеопамяти, отводимое на пиксель определяет возможное число состояний пикселя. Например его цвет, яркость, мерцание. В самом простом случае - 1 бит на пиксель - возможны только два случая: пиксель или подсвечен или нет. Можно сказать иначе: пиксел чёрный или белый. При этом не стоит называть такой режим чёрно-белым, т.к. это название подразумевает наличие оттенков этих цветов, т.е. получается большее количество чем 2. Четыре цвета (2 бита на пиксель) в своё время доставляли немало удовольствия любителям цветных игрушек. 16 цветов (вот мы добрались и до bgi) - 4 бита на пиксель - были вполне достаточны для многих игр и графических программ. Адаптеры с 256 цветами (8 бит на пиксель) в своё время произвели революцию в мире графики. Сейчас в основном используются режимы High Color (15 бит - 32768 цветов или 16 бит - 65536 цветов) и True Color (24 бит на пиксель - 16,7 миллионов цветов). Так же популярен режим с 32 битами на пиксель, но для цветопередачи по прежнему используются только 24 бита.

Давайте рассмотрим логическую организацию видео памяти. В зависимости от количества бит на пиксель она может быть различной.

В случае одного или двух бит на пиксель каждая ячейка памяти (байт) соответствует восьми или четырём соседним пикселям. Такой способ отображения называется линейным - последовательности пикселей соответствует линейная последовательность бит видеопамяти.

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

При режимах 8, 16 и 24 бит на пиксель вернулись к линейной организации памяти. При этом каждому пикселю соответствует уже байт, слово или три байта. При этом цвет задаётся непосредственно через двоичные коды, которые соответствуют уровням интенсивности 3-х основных цветов: красный, зелёный, синий (для краткости их обозначают RGB - RedGreenBlue). Форматы байт выглядят следующим образом (младший бит справа, U - для формирования цвета бит не используется):

  • 15 бит на пексель: URRR RRGG GGGB BBBB
  • 16 бит на пексель: RRRR RGGG GGGB BBBB
  • 24 битa на пексель: RRRR RRRRR GGGG GGGG BBBB BBBB
Объём видеопамяти, требуемый для хранения образа экрана, определяется как произведение разрешения на количество бит на пиксель. Например для режима 800х600 256 цветов требуется 469 Кбайт (800*600*8 = 3840000 бит). Ниже представлена таблица объёмов памяти, необходимой для одной страницы основных видеорежимов.
Бит на пиксель Количество цветов 640x480 800x600 1024x768 1280x1024
4 16 150 Кбайт 235 Кбайт 384 Кбайт 640 Кбайт
8 256 300 Кбайт 469 Кбайт 768 Кбайт 1,25 Мбайт
15 32,768 600 Кбайт 938 Кбайт 1,5 Мбайт 2,5 Мбайт
16 65,536 600 Кбайт 938 Кбайт 1,5 Мбайт 2,5 Мбайт
24 16,777,216 900 Кбайт 1,37 Мбайт 2,25 Мбайт 3,75 Мбайт
Думаю для всех очевидно, что мы не будем писать программу для работы с режимом 1280x1024 при 24 битах :)

Как известно различные графические адаптеры позволяют использовать различные разреения и количества цветов. Давайте рассмотрим все адаптеры (ну или подчти все) от самых первых - MDA до наиболее популярных сегодня SVGA.

  • MDA (Monochrime Display Adapter) - монохромный адаптер, применявшийся на первых PC, режим - только текстовый.
  • HGC (Hercules Graphic Controller) - тот же MDA, но с графическим режимом.
  • CGA (Color Graphic Adapter) - цветной графический адаптер - первая графическая система на ПК, режимы - текстовый и графический.
  • EGA (Enhanced Graphic Adapter) - расширенный графический адаптер. Режимы текстовый и графический, в своё время был очень популярен.
  • PGA (Professional Graphic Adapter) - професиональный графический адаптер. Имеет процессор для трехмерной графики, однако высокая цена помешала его распространению.
  • VGA (Video Graphic Array) - Режимы: текст, графика. Поддерживает режимы MDA, CGA, EGA и дополнонительно режимы с высоким разрешением (640x480). Обеспечивает одновременно 256 цветов на экране из палитры 262144 цветов.
  • SVGA (Super VGA) - видеоадаптеры превосходящие VGA по разрешению и количеству цветов
Ниже приведены основные параметры адаптеров:
Разрешние Режим (Gr/TXT) Количество цветов
MDA
720x350 TXT 4
HGC
720x350 GR 4
CGA
320x200 TXT 16
640x200 TXT 16
160x200 GR 16
320x200 GR 4
640x200 GR 2
EGA
320x350 TXT 16
640x350 TXT 16
720x350 TXT 4
320x200 GR 16
640x200 GR 16
640x350 GR 4
640x350 GR 16
PGA
320x200 TXT 16
640x200 TXT 16
320x200 GR 4
640x200 GR 2
640x480 GR 256
VGA
360x400 TXT 16
720x400 TXT 16
720x400 TXT 16
320x200 GR 4
640x200 GR 2
320x200 GR 16
640x200 GR 16
60x350 GR 4
640x350 GR 16
640x480 GR 2
640x480 GR 16
320x200 GR 256
О режимах SVGA не имеет смысла писать, т.к их очень много и думаю всем вам они хорошо известны.

Ну вот так шаг, за шагом мы подобрались к началу нашей библиотеки. С чего начинается работа графической программы? Правильно с установки видео режима! Нам надо написать свою процедуру для инициализации графики и возврата в текстовый режим.

Хотя две процедуры писать нам не придётся. Т.к. возврат в текстовый режим сводится к простой установке нового режима. Итак за установку режима отвечает 0 функцая прерывания 10h (это прерывание BIOS). На входе: AL = текущий режим. Сразу скажу, что текстовому режиму (вернее тестовому режиму с "разрешением" 80х25) соответствует значение 3.

Теперь пришло время донести до вас одну печальную новость. Наша библиотека будет поодерживать только один режим, а именно 320х200х256. Почему я сделал такой выбор?

  • Во-первых это режим с линейной адресацией, что уже само по себе не плохо.
  • Во-вторых "весь экран влезает" в один сегмент. Так для режимов с более высоким разрешением мы не можем впихнуть всю видеопамять в один сегмент. Поэтому была придумана специальная технология: вся видеопамять разбивалась на банки (сегменты). И с помощью вызова специальной функции память распределялась так, что текущий банк имел начало A000h (т.е. по просту говоря надо было переключать банки между собой).
  • На самом деле в этом режиме не 256 цветов, а гораздо больше. Но одновременно мы можем работать только с 256.
Так вот этот режим имеет номер 13h. Т.е. мы можем завести константы:
const
  TextMode = $3;
  GrMode = $13;
Отвечающие соответственно за режимы. И написать процедуру для установки нужного режима:
procedure SetMode (mode : byte);assembler;
asm
  xor ah, ah
  mov al, mode
  int 10h
end;
Итак теперь мы в графическом режиме. Теперь надо написать процедуру для подсветки точки на экране. Самый простой метод - напрямую в видеопамять. Для каждого режима надо писать отдельную процедуру, но раз мы договорились, что режим у нас один, то и процедуру мы напишем одну. Смещение относительно сегмента видеопамяти считается по следубщей формуле: offset = y*320+x. Конечно такую процедуру просто реализовать на Паскале (вот вам кстати очередное Д/З - реализуйте процедуру подсветки пикселя на Паскале:). НО графическая библиотека должна быть быстрой - поэтому такую процедуру нужно реализовывать на ассемблере, т.к. скорость при подсветке точек играет большое значени. Ниже приведена процедура для подсветки пикселя с комментариями:
procedure PutPixel (x, y: integer; color : byte);assembler;
asm
  mov ax, y	--	AX = y
  mov bx, ax	--	BX = AX = y
  shl bx, 6	--	BX = BX * 64
  shl ax, 8	--	AX = AX * 256 (т.к.  y * 320 = y * 64+ y * 256)
  add bx, ax	--	BX = BX + AX (т.е. BX = y * 320)
  add bx, x	--	BX = BX + x (т.е. BX = y * 320 + x)

  mov ax, 0A000h
  mov es, ax			--	ES = 0A000h
  mov al, color			--	AL = color
  mov byte ptr[es:bx], al	--	пишем точку по адрессу ES:BX (т.е. 0A000:BX)
end;
Для работы этой процедуры надо в опциях компилятора выбрать инструкции для 286 процессора. И соответственно нам нужна функция, которая возвратит нам цвет точки. Вот её реализация так же на ассемблере:
function GetPixel (x, y: integer) : byte;assembler;
asm
  mov ax, y
  mov bx, ax
  shl bx, 6
  shl ax, 8
  add bx, ax
  add bx, x

  mov ax, 0A000h
  mov es, ax
  mov al, es:[bx]	 --	 Записываем в AL значение по адрессу ES:BX (т.е. 0A000:BX)
end;
Теперь самое время поговорить о палитре. Как говорилось выше, каждый цвет кодируется тремя составляющими - RGB. На каждую компоненту отводится по 6 бит, т.е. она может принимать значения от 0 до 63. За утановку палитры отвечают порты 03C8h и 03C9h. Алгоритм установки цвета такой: пишем в порт 03C8h номер цвета, а затем в порт 03C9h по порядку записываем RGB составляющие. На примере это выглядит так:
procedure SetPallet  (c, r, g, b : byte);assembler;
asm
  mov dx, 03C8h
  mov al, c
  out dx, al
  inc dx
  mov al, r
  out dx, al
  mov al, g
  out dx, al
  mov al, b
  out dx, al
end;
Для чтения же необходимо сначала записать в порт 03C7h номер цвета, а потом из порта 03C9h по порядку считать RGB составляющие.

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

Программа

Сегодня у нас довольно интересная программа - алгоритм огня. Итак каким образом формируются огонь в различных играх? Реально нам нужно написать алгоритм для затухания огня. Присмотритесь на все существующие огни - они "просто " затухают снизу вверх. Т.е. нижняя линия у нас всегда горит. Дальше линия горит меньше, дальше ещё меньше .... и так пока не "выгорят" все точки на линии.

Самым простым вариантом затухания является усреднение значения каждой точки с её окружением. Т.е. пусть у нас есть точка цвета А с координатами (х,у). Для получения её нового цвета В мы должны взять цвет 8 соседних точек и точки А, поделив это на 9. Т.е. мы берём цвета:
x - 1, y - 1 x + 0, y - 1 x + 1, y - 1
x - 1, y + 0 x + 0, y + 0 x + 1, y + 0
x - 1, y + 1 x + 0, y + 1 x + 1, y + 1
Тем самым, т.к. вверху у нас более тёмные линии мы и получим затемнение. Однако, что б не получилось наоборот некоторые точки снизу так же придётся затемнить.

Кроме того нам надо постоянно "толкать" изменённую линию вверх. А снизу создавать новую. При этом не полностью заполненную.

Наилучшим вариантом будет хранение всего огня в двух мерном массиве. Полный текст программы приведён ниже:

program fireStorm;

uses CRT;

const
  TextMode = $3;
  GrMode = $13;

  FireH = 100;
  FireW = 100;

var
  Fire : array [1..FireH, 1..FireW] of byte;

procedure SetMode (mode: byte);assembler;
asm
  xor ah, ah
  mov al, mode
  int 10h
end;

procedure PutPixel (x, y: integer; color: byte);assembler;
asm
  mov ax, y
  mov bx, ax
  shl bx, 6
  shl ax, 8
  add bx, ax
  add bx, x

  mov ax, 0A000h
  mov es, ax
  mov al, color
  mov byte ptr [es:bx], al
end;

procedure SetPallet  (c, r, g, b : byte);assembler;
asm
  mov dx, 03C8h
  mov al, c
  out dx, al
  inc dx
  mov al, r
  out dx, al
  mov al, g
  out dx, al
  mov al, b
  out dx, al
 end;

procedure FillFire;
var
 i, j : integer;
begin
  for i := 1 to FireH do
    for j := 1 to FireW do
     Fire[i,j] := 0
end;

procedure PutFire (x, y: integer);
var
 i, j : integer;
begin
  for i := 1 to FireH do
    for j := 1 to FireW do
      putpixel (j + x, i + y, Fire [i,j])
end;

procedure MoveFire;
var
 i, j : integer;
begin
  for i := 1 to FireH do
    for j := 1 to FireW do
      Fire[i,j] := Fire [i+1, j]
end;

procedure SpotFire;
var
 i, j, c : integer;
begin
  for i := 1 to FireH do
    for j := 1 to FireW do
    begin
       c := trunc (
            (Fire[i + 0, j + 0] + Fire[i + 1, j - 1] +
             Fire[i + 0, j + 1] + Fire[i - 1, j + 0] +
             Fire[i + 0, j - 1] + Fire[i - 1, j + 1] +
             Fire[i + 1, j + 0] + Fire[i - 1, j - 1] +
             Fire[i + 1, j + 1]) / 9);
       if c < 0 then
         Fire[i,j] := 0
       else if c > 255 then
         Fire [i,j] := 255
       else
         Fire [i,j] := c
    end;

    for i := 1 to (FireW div 3) do
       Fire [FireH, random (FireW)] := 255
end;

var
 i : integer;
begin
  SetMode (GrMode);
  randomize;

  for i := 0 to 255 do
     SetPallet (i, i, 0, 0);

  FillFire;

  while not keypressed do
  begin
    SpotFire;
    MoveFire;
    PutFire (110, 0);
    delay (10)
  end;

  SetMode (TextMode)
end.
Вообщем ничего сложного нет. Помимо процедур из нашей будующей библиотеки я использовал, следующие процедуры:
  • FillFire - заполняет массив Fire (в нём и хранится огонь :) нулями.
  • MoveFire - сдвигает массив Fire на одну строчку.
  • SpotFire - считает среднее значение для точки, а потом заполняет часть нижней линии горящими точками.
  • PutFire - отображает массив Fire на экране.
Обратите внимание, что я задал палитру из одних оттенков красного:
for i := 0 to 255 do
SetPallet (i, i, 0, 0);
Если не изменять палитру, то можно получить эффект "плазмы" с единственным недостаткаом - вверху будут отображаться 16 цветов. Связано это с тем, что в начале палитры идут 16 не переливающихся цветов, а уже только потом начинаются плавные переходы.

Конечно это самы простой вариант огня. Для получения более рельного изображения надо использовать более сложный алгоритм затухания.

Голосование

Моё повествование о Паскале подходит к концу. И поэтому надо думать: что дальше? Мои варианты:

  • Изучение Object Pascal - расширение Паскаля до объектно ориентированного языка. Именно на нём основан Delphi. Освоить его очень просто, тем более что осваивать будем на том же BP 7.
  • Изучение программирования под Windows. Не на Delphi - а именно на Паскале. Тут пойдет конкретное повествование об особенностях построения пользовательского интерфейса - вообщем как создавать окна, отдельные элементы и т.п. Можно было бы изучить и на BP 7, но лучше будет использовать какой-либо 32-х разрядный компилятор, так что возможно придётся что-то и скачать.
  • Писать про реализацию различных алгоритмов, не привязываясь к какой-либо ОС. Самые различные алгоритмы - понемногу (или многу) обо всём.
  • Или четвёртая дорога - одобрить мой план: сначала Object Pascal, потом Windows, потом немного о Delphi, а потом уже алгоритмы.
Если есть другие предложения - просьба не стесняться - пишите. Проголосовать и посмотреть как это сделали другие можно по этой ссылке: http://narod.yandex.ru/survey/?id=96923

На этой неделе уверенно вырвалось вперёд программирование на Object Pascal.

Это должен посетить каждый!

сегдня я дам только одну ссылку, не имеющую отношения к Паскалю, но очень мне понравившуюся :)

  • http://www.combats.ru - Бойцовский клуб - виртуальное государство воинов. Двольно интересная идея и реализация, да и грузится довольно быстро (т.к. Flash и Java отсутствуют !!!).

[Назад] [Содержание] [Дальше]
Hosted by uCoz