До этого наши программы работали в текстовом режиме. Кроме него существует ещё и графический. В текстовом режиме вам доступны только 256 символов псевдографики (как бы графики), которые вы можете отобразить на экране. Этих символов достаточно для простых графических работ, например: нарисовать таблицу, написать карточную игру, оболочку (типа Norton Commander), многоконный тестовый редактор (взять даже BP) и для много другого. Графический же режим характеризуется возможностью задавать цвет любой точки экрана. И соответственно мы можем рисовать сложные геометрические фигуры. Для работы с графикой в комплект поставки BP входит модуль graph, в котором содержатся основные функции и процедуры. Так же для работы нужны специальные графические драйвера, которые тоже входят в BP и находятся в папке BP\BGI. Кстати модуль graph называется библиотекой BGI (Borland Graphic Interface). Прежде чем начать работу с графикой нам надо перейти в графический режим, т.к. наша программа запускается по умолчанию в текстовом. И соответственно перейти назад в текстовый после завершения работы. Поэтому каркас программы превращается в нечто более сложное, чем было до этого:
uses Graph;
var
grDriver: Integer;
grMode: Integer;
Errcode: Integer;
begin
grDriver := Detect;
InitGraph(grDriver, grMode,'');
Errcode := GraphResult;
if Errcode <> grOk then
begin
Writeln('Graphics error:');
Writeln(GraphErrorMsg(Errcode));
Writeln('Program aborted...');
Halt(1)
end
..... do something ...
Readln;
CloseGraph
end.
ну что неплохое начало :) Эта программа "ничего" не делает. Просто переходт в графический режим, ждёт нажатия клавиши и выходит назад в текстовый. Теперь давайте разберём, что же мы тут использовали:
procedure InitGraph(var GraphDriver:Integer; var GraphMode: Integer; PathToDriver: string);процедра инициализации графики. Т.е. по просту говоря для перехода в графический режим. GraphDriver - графический драйвер, которым мы хотим пользоваться. Мы задаём этому параметру значение Detect - авто определение(т.е. программа сама определит наилучший режим). Для современной техники это 640x480x16 :)
GraphMode определяет графический режим, т.к. у нас стоит автоопределение он определится сам (это и будет режим 640х480 16 цветов).
PathToDriver - путь к файлу драйвера. Если путь не задаётся, как это сделали мы, то драйвер ищётся в текущем каталоге программы. Если использовать автоопределение, то нам нужен файл egavga.bgi из папки bp\bgi. На всякий случай, вдруг кто не знает скажу кое-что об обозначениях что значит 640х480х16 - то точная характеристика графического режима: (число точек по горизонтали)х(число точек по вертикали)х(количество цветов). Т.е. 640 точек по горизонтали, 480 по вертикали и 16 цветов. В интернете сейчас можно найти версии файлов *.bgi для работы в более лучших режимах. Однако я всё же буду рассказывать про стандартную поставку, т.к. углубляться в графику мы пока особо не будем. Всему своё время. Следующая функция возвращает нам резльтат предыдущей функции или процедуры, если возникла ошибка:
function GraphResult: Integer;Если возникла ошибка (например при инициализации графики программа не нашла файл с драйвером), то возвращаемое значение не равно grOk. Расшифровку ошибки можно получить вызвав:
function GraphErrorMsg(Errorcode: Integer): string;возвращает строку, содержащую содержание ошибки, заданной переменной Errorcode. Не помню писал ли я про halt, так что если повторюсь, то ничего:
procedure Halt ( Exitcode: Word );останавливает выполнеие программы и передаёт управление операционной системе. Если параметр Exitcode = 1 значит это завершение программы с ошибкой. Ну и последняя процедура:
procedure CloseGraph;переходит назад в текстовый режим. Обратите внимание на вызов ReadLn перед вызовом CloseGraph. Зачем мы это делаем? Если опустить этот вызов, то после выполнения программы сразу произойдёт выход в текстовый режим и соответственно экран очистится. Что бы посмотреть результаты выполнения программы мы вынужденны ожидать нажатия клавиши. Если вы думаете, что это сложно, то вы ошибаетесь, что бы создать простое пустое окошко в программе для Windows надо написать раза в три-четыре больше :) Этот код будем считать каркасом наших будующих графических программ. Теперь самое время поговорить о координатах. Всё дело в том, что в компьютерах координатные оси направленны по другому. Для примера: мы пользуемся декартовой системой координат ( © by Рене Декарт :), центром которой является точка (0,0) опять же извиняюсь за убогость, но картинку не приаттачить:
^ y
|
|
|
--------+------->
| x
|
|
В компьютерах же используется несколько другая система
+-----------------------> | x | | | | | | V y
Как видите в ней отсутствуют отрицательные числа и ось ординат (y) направленна вниз. Вызвано это следующей причиной. Разрешение экрана определяют в пикселях. Пиксель (сокращение от Picture Element - элемент картинки) - это минимальная логическая точка (или если хотите единица) экрана. Т.е. разрешению экрана 800х600 означает, что по оси Х у нас максимально 800 пикселей, а по оси Y - 600. Один пиксель может состоять из множества физических точек (т.е. точек подсветкой которых можно управлять аппаратно). Так вот левому верхнему углу экрана соответствеут пиксель с координатами (0,0), а правому нижнему в зависимости от разрешения. Если оно 800х600 тогда (799, 599). Пиксель - это всегда целое положительное число. У вас не может быть 1/3 пикселя или пиксель с координатой (-5, -5). Так как мы не можем посветить на экране 1/3 пикселя и мы не можем высветить пиксель (-5, -5) т.к. это выпадает за границу экрана. Кстати количесто цветов на экране определяется количеством бит видеопамяти, отводимой под 1 пиксель. Так если под один пиксель отводится 1 байт (8 бит) тогда это режим с 256 цветами (напомню, что столько может быть различных комбинаций бит в байте). Теперь рассмотрим самую важную процедуру в графике - подсветка пикселя:
procedure PutPixel(X, Y: Integer; color: Word);соответственно х, у - координаты. Color - цвет. Кстати о цветах. Для первых 16 цветов введены специальные константы. Ниже приводится таблица соответствия цветов и номеров (для текстовой версии к сожалению графа пример останется незаполненной, т.к. нет такой возможности):
| Номер цвета | Название константы | Русское название (для текстовой версии) | Примерчик |
| 0 | BLACK | чёрный | |
| 1 | BLUE | синий | |
| 2 | GREEN | зелёный | |
| 3 | CYAN | голубой | |
| 4 | RED | красный | |
| 5 | MAGENTA | фиолетовый | |
| 6 | BROWN | коричневый | |
| 7 | liGHTGRAY | светло серый | |
| 8 | DARKGRAY | тёмно серый | |
| 9 | liGHTBLUE | светло синий | |
| 10 | liGHTGREEN | светло зелёный | |
| 11 | liGHTCYAN | светло голубой | |
| 12 | liGHtrED | розовый | |
| 13 | liGHTMAGENTA | светло фиолетовый | |
| 14 | YELLOW | жёлтый | |
| 15 | WHITE | белый |
поэтому putpixel (10, 10, 15) и putpixel (10, 10, WHITE) произведёт одинаковый эффект. Таким образом мы можем рисовать любые фигуры, используя посветку точек на экране. Однако в BGI есть специальные процедуры для рисования графических примитивов - линий, окружностей, прямоугольников, вывода текста, работы с изображениями и многим другим. К сожалению в формат выпусков не влезет описать все возможности BGI, да многое и не понадобится. Поэтому проведу обзор самых нужных функций и процедур.
- function GetPixel(X,Y: Integer): Word;
- возвращает цвет точки с координатами х,у
- procedure line(x1, y1, x2, y2: Integer);
- рисует линию от точки (x1,y1) к точке (x2, y2). Цвет линии устанавливается с помощью следующей процедуры:
- procedure SetColor(Color: Word);
- устанавливает текущий цвет для рисования графических объектов. По умолчанию цвет белый.
- procedure SetBkColor(ColorNum: Word);
- устанавливает цвет фона, по умолчанию цвет фона чёрный. Цвет фона - это цвет экрана на котром мы рисуем. Что-то типа цвета рабочего стола в Windows :)
- procedure Circle(X,Y: Integer; Radius: Word);
- рисует окружность с центром в точке (x,y) и радиусом Radius. Цвет окружности задаётся с помощью SetColor.
- procedure Ellipse(X, Y: Integer; StAngle, EndAngle: Word; XRadius, YRadius: Word);
- рисует элипс с центром в точке (x,y), полу-осями xradius, yradius, от угла stangle до угла endangle. Угол считается против часовой стрелки (т.е. 00 - 3 часа, 900 - 12 часов). Цвет задаётся с помощью SetColor.
- procedure Bar(x1, y1, x2, y2: Integer);
- рисует закрашенный прямоугольник. левый верхний угол (х1,у1) - правый нижний (х2, у2). Стиль кисти и цвет закраски определяется следующей процедурой:
- procedure SetFillStyle(Pattern: Word; Color: Word);
- устанавливает текущий стиль кисти (pattern) и её цвет (color). Цвет, который задаётся setcolor и setfillstyle не совпадают и применяются к разным графическим объектам. Первый к графическим примитивам, второй к закрашенным объектам. Существует несколько предопределённых стилей, которые приведены в нижеследующей таблице:
что бы лучше понять напишите программу, которая бы в цикле выводила прямоугольники и меняла стиль, тогда вы поймёте что к чему.Констнанта Численное значение Описание EMPTY_FILL 0 Закрашивает цветом фона
(несмотря на цвет, который вы указали в setfillstyle)SoliD_FILL 1 Равномерная закраска liNE_FILL 2 Закрашивает --- LTSLASH_FILL 3 Закрашивает /// SLASH_FILL 4 Закрашивает ///, толстые линии BKSLASH_FILL 5 Закрашивает \\\, толстые линии LTBKSLASH_FILL 6 Закрашивает \\\ HATCH_FILL 7 В "клеточку" XHATCH_FILL 8 В клеточку под углом INTERLEAVE_FILL 9 Чередование WIDE_DOT_FILL 10 Широко расположенные точки CLOSE_DOT_FILL 11 Близко расположенные точки USER_FILL 12 Стиль определять вам! - procedure FillEllipse(X, Y: Integer; XRadius, YRadius: Word)
- рисует закращенный элипс с центром (х,у) и полу-осями xradius, yradius. Стиль и цвет определяются Setfillstyle
- procedure ClearDevice;
- очищает экран (заполняет его цветом фона).
- procedure OutTextXY(X,Y: Integer; TextString: string);
- выводит строку textstring, начиная с точки (х,у). В графическом режиме для вывода нужно обязательно использовать эту процедуру.
- procedure GetImage(x1, y1, x2, y2: Integer; var BitMap);
- копирует картинку в память. Картинка определяется координатами (x1, y1) - левый верхний угол; (x2, y2) - правый нижний угол. BitMap - это область куда мы должны копировать картинку. Размер области памяти складывается из количества необходимого под картинку + 6 байт. Два первых слова этих байт хранят ширину и высоту картинки, третье слово зарезервировано и зачем оно нужно это охраняемая тайна компании Borland :)
- procedure PutImage(X, Y: Integer; var BitMap; BitBlt: Word);
- соответственно обратная процедура. Рисует изображение из BitMap на экран, начиная с координат (х,у). BitBlt - определяет способ вывода картинки. Ниже даны его возможные значения:
Константа Численное значение Смысл NormalPut 0 Копирует изображение на экран (цвет каждого пикселя сохраняется) XORPut 1 Результат получается как XOR, применённый к точке на экране и точке изображения OrPut 2 как OR, применённый к точке на экране и точке изображения AndPut 3 как AND, применённый к точке на экране и точке изображения NotPut 4 Цвет каждого пикселя инвертируется (заменяется на противоположный) - function ImageSize(x1, y1, x2, y2: Integer): Word;
- эта функция используется, что бы вычислить размер памяти BitMap в процедуре GetImage, т.е. размер необходимый под сохранение области + 3 слова.
- function GetMaxX: Integer; / function GetMaxY Integer;
- возвращает максимальное значение Х / Y координаты.
- procedure FloodFill(X, Y: Integer; Border: Word);
- закрашивает область, которая содержит точку (x,y) и ограничена кривой с цветом Border. Стиль и цвет определяются Setfillstyle
program demo;
uses Crt, Graph;
const
r = 20;
StartX = 100;
StartY = 50;
var
grDriver, grMode, Errcode : integer;
MaxX, MaxY : word;
Saucer : pointer;
X, Y : integer;
ulx, uly : word;
lrx, lry : word;
Size : word;
I : word;
procedure MoveSaucer(var X, Y : integer; Width, Height : integer);
var
Step : integer;
begin
Step := Random(2*r);
if Odd(Step) then
Step := -Step;
X := X + Step;
Step := Random(r);
if Odd(Step) then
Step := -Step;
Y := Y + Step;
if X > MaxX then
X := MaxX
else
if (X < 0) then
X := 0;
if Y > MaxY then
Y := 1
else
if Y < 0 then
Y := 0
end;
begin
grDriver := Detect;
InitGraph(grDriver, grMode,'');
Errcode := GraphResult;
if Errcode <> grOk then
begin
writeLn ('Graphics error:', GraphErrorMsg(Errcode));
halt (1)
end;
ClearDevice;
MaxX := getmaxx;
MaxY := getmaxy;
{ рисуем НЛО }
Ellipse(StartX, StartY, 0, 360, r, (r div 3)+2);
Ellipse(StartX, StartY-4, 190, 357, r, r div 3);
line(StartX+7, StartY-6, StartX+10, StartY-12);
Circle(StartX+10, StartY-12, 2);
line(StartX-7, StartY-6, StartX-10, StartY-12);
Circle(StartX-10, StartY-12, 2);
SetFillStyle(SolidFill, WHITE);
FloodFill(StartX+1, StartY+4, GetColor);
{ вычисляем границы прямоугольника в который вмещается НЛО }
ulx := StartX-(r+1);
uly := StartY-14;
lrx := StartX+(r+1);
lry := StartY+(r div 3)+3;
Size := ImageSize(ulx, uly, lrx, lry);
GetMem(Saucer, Size);
GetImage(ulx, uly, lrx, lry, Saucer^);
PutImage(ulx, uly, Saucer^, XORput);
{ рисуем звёздное небо :) }
for I := 1 to 1000 do
PutPixel(Random(MaxX), Random(MaxY), random (WHITE));
X := MaxX div 2;
Y := MaxY div 2;
repeat
PutImage(X, Y, Saucer^, XORput);
Delay (10000);
PutImage(X, Y, Saucer^, XORput);
MoveSaucer(X, Y, lrx - ulx + 1, lry - uly + 1);
until Keypressed;
FreeMem(Saucer, size);
ReadLn;
closegraph
end.
Сначала одна неизвестная до этого момента функция
function Odd(X: Longint): Boolean;проверяет является ли число нечётным. и возвращает true в случае успеха. Так вот думаю вы ужу понаблюдали за НЛО, теперь разберём как это происходит. Процедура MoveSaucer не осуществляет ничего сверх естественного и просто изменяет координаты НЛО, которые хранятся в глобальных переменных Х,Y. На ней подробно останавливаться думаю не стоит. Так же я не буду комментировать процесс рисования объекта, так как это просто последовательный вызов графических функций безо всяких алгоритмов. Я бы хотел обратить ваше внимание на следующие строки:
Size := ImageSize(ulx, uly, lrx, lry); - вычисляем память, которая нам нужна для хранения картинки НЛОтут придётся вспомнить логические операции (как неужели вы их забыли !!!). Если забыли, тогда вернитесь к выпуску "#0E А сила - она, брат, в правде". Так вот что будет, когда мы число xor'им само с собой (a xor a) ??? Правильно 0. То же самое происходит и когда мы вызываем PutImage(ulx, uly, Saucer^, XORput). Ведь в Saucer у нас находится изображение НЛО, когда же мы накладываем изображение само на себя используя операцию xor, то получается 0, т.е. изображение цвет всех точек которого равен 0. А так как цвет фона у нас чёрный (0) то получается эффект стирания, а не перерисовки. Продолжим:
GetMem(Saucer, Size); - выделяем эту память
GetImage(ulx, uly, lrx, lry, Saucer^); - захватываем изображение НЛО в буффер Saucer.
PutImage(ulx, uly, Saucer^, XORput); - стираем НЛО
repeatвроде бы ничего сложного, но задумайтесь на минутку почему остаются звёзды ? Давайте поставим задержку (delay) совсем большой и присмотримся к НЛО .... через него просвечивают звёзды! (что бы лучше это рассмотреть увеличте количество звёзд ). Всё из-за того что способ применённый здесь не является правильным. Хотя согласитесь рассмотреть эти маленькие точки тяжело и искажения не очень заметны. Теперь рассмотрим внимательнее исходник. Когда мы рисуем НЛО мы опять применяем операцию xor - поэтому если под НЛО находится звёздочка, то она просвечивает, причём с искажением цвета. Однако когда мы стираем НЛО мы вновь применяем xor и НЛО стирается, а звездочке возвращается свой "первозданный" цвет. Давайте обратимся к цифрам. Пускай у нас будет звезда жёлтого (14) цвета.
PutImage(X, Y, Saucer^, XORput); - рисуем НЛО
Delay (10000); - пауза
PutImage(X, Y, Saucer^, XORput); - стираем
MoveSaucer(X, Y, lrx - ulx + 1, lry - uly + 1); - двигаем НЛО
until Keypressed;
14 = 1110bДанную анимацию можно применять для взаимно обратных цветов, т.е. по цветам с одинаковым отступом от концов палитры (палитра - набор цветов, каждому цвету соответствует номер в палитре, помните таблицу констант?). Такими цветами и являются: чёрный-белый (0 -15), синий-жёлтый (1-14) и т.д. Про правильную анимацию я раскажу как нибудь в другой раз.
1111b xor 1110b = 0001b - синий цвет. это в момент рисования.
0001b xor 1111b = 1110b - жёлтый. в момент стирания. Как говорят математики ЧТД.

