Теория
До этого наши программы работали в текстовом режиме. Кроме него существует ещё и графический. В текстовом режиме вам доступны только 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
это далеко не полный список процедур и фунций из библиотеки BGI. Что бы понять как они работают лучше всего, что бы вы попробовали вызвать их поочереди и посмотреть что будет. Кстати хороший пример работы этих и других функций и процедур находится в программе BP\EXAMPLES\DOS\BGI\bgidemo.pas. Сейчас я как раз разберу пример от туда. Это пример с НЛО :) запустите и посмотрите, так как надо объяснять пользуясь картинкой. Если НЛО летает медленно, тогда в самом конце измените задержку (например на delay (1000) - в 10 раз быстрее) так как на моём P4 за НЛО не уследить :) этот параметр в примере равен кстати 70 :)
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); - вычисляем память, которая нам нужна для хранения картинки НЛО
GetMem(Saucer, Size); - выделяем эту память
GetImage(ulx, uly, lrx, lry, Saucer^); - захватываем изображение НЛО в буффер Saucer.
PutImage(ulx, uly, Saucer^, XORput); - стираем НЛО
тут придётся вспомнить логические операции (как неужели вы их забыли !!!). Если забыли, тогда вернитесь к выпуску "#0E А сила - она, брат, в правде". Так вот что будет, когда мы число xor'им само с собой (a xor a) ??? Правильно 0. То же самое происходит и когда мы вызываем PutImage(ulx, uly, Saucer^, XORput). Ведь в Saucer у нас находится изображение НЛО, когда же мы накладываем изображение само на себя используя операцию xor, то получается 0, т.е. изображение цвет всех точек которого равен 0. А так как цвет фона у нас чёрный (0) то получается эффект стирания, а не перерисовки. Продолжим:
repeat
PutImage(X, Y, Saucer^, XORput); - рисуем НЛО
Delay (10000); - пауза
PutImage(X, Y, Saucer^, XORput); - стираем
MoveSaucer(X, Y, lrx - ulx + 1, lry - uly + 1); - двигаем НЛО
until KeyPressed;
вроде бы ничего сложного, но задумайтесь на минутку почему остаются звёзды ? Давайте поставим задержку (delay) совсем большой и присмотримся к НЛО .... через него просвечивают звёзды! (что бы лучше это рассмотреть увеличте количество звёзд ). Всё из-за того что способ применённый здесь не является правильным. Хотя согласитесь рассмотреть эти маленькие точки тяжело и искажения не очень заметны.
Теперь рассмотрим внимательнее исходник. Когда мы рисуем НЛО мы опять применяем операцию xor - поэтому если под НЛО находится звёздочка, то она просвечивает, причём с искажением цвета. Однако когда мы стираем НЛО мы вновь применяем xor и НЛО стирается, а звездочке возвращается свой "первозданный" цвет. Давайте обратимся к цифрам. Пускай у нас будет звезда жёлтого (14) цвета.
14 = 1110b
1111b xor 1110b = 0001b - синий цвет. это в момент рисования.
0001b xor 1111b = 1110b - жёлтый. в момент стирания. Как говорят математики ЧТД.
Данную анимацию можно применять для взаимно обратных цветов, т.е. по цветам с одинаковым отступом от концов палитры (палитра - набор цветов, каждому цвету соответствует номер в палитре, помните таблицу констант?). Такими цветами и являются: чёрный-белый (0 -15), синий-жёлтый (1-14) и т.д. Про правильную анимацию я раскажу как нибудь в другой раз.
|