Pers.narod.ru. Обучение. Учебник по Паскалю. Глава 25 |
Для работы с графикой из программы на Паскале в папке, откуда она запускается, должен присутствовать файл egavga.bgi. Он представляет собой графический драйвер, предназначенный для управления видеопамятью в режимах поддержки мониторов типов EGA и VGA. Разумеется, современные мониторы давно "переросли" эти два исторически распространенных класса дисплеев. Однако, на любом современном компьютере поддержка видеорежимов EGA и VGA по-прежнему возможна, если не напрямую, то через специальную программу-эмулятор (см. конец главы).
В поставку Паскаля могут входить и другие файлы с расширением *.bgi, отвечающие за работу с мониторами различных типов.
Кроме того, при компиляции программы, имеющей графический вывод, должен быть доступен модуль graph.tpu, содержащий подпрограммы отрисовки графических объектов.
Библиотека graph.tpu подключается стандартным способом с помощью директивы uses в разделе описаний программ:
uses graph;
В графическом режиме, который в современных операционных системах типа Windows является основным, экран представляет собой матрицу точек (пикселов), причем имеется возможность высветить любой пиксел любым цветом. Координаты каждого пиксела определяются парой целых чисел:
· координата x -- номер пиксела в строке. Нумерация выполняется слева направо, начиная с 0;
· координата y -- номер строки пикселов. Нумерация строк производится сверху вниз, начиная с 0.
Таким образом, координаты левого верхнего угла экрана равны (0, 0).
Любой объект, высвечиваемый на экране, является совокупностью отдельных пикселов. Количество воспроизводимых пикселов по горизонтали и вертикали зависит от типа монитора и установленного графического режима. Каждый монитор может использовать множество режимов, отличающихся количеством поддерживаемых цветов и разрешением графического экрана в пикселах.
Классический Паскаль поддерживает монитор CGA, имеющий разрешение до 3203200 пикселов, монитор EGA с разрешением 6403350, монитор VGA с разрешением до 6403480. Работу с более современными и мощными графическими устройствами, относящимися к классу superVGA, Паскаль непосредственно не поддерживает, хотя существуют созданные независимыми разработчиками графические драйверы этих режимов.
Графический режим работы экрана кроме количества пикселов характеризуется определенной палитрой -- набором видимых цветов. Каждая палитра состоит из 4 цветов для монитора CGA или 16 цветов для EGA и VGA.
Установка графического режима осуществляется путем обращения к процедуре initgraph:
initgraph(var gd:integer, var gm:integer,
pt:string);
Целочисленные переменные gd и gm задают тип графического драйвера и режим его работы, строковая переменная pt -- путь к файлу *.bgi. Например, при выборе основного для Паскаля видеорежима VGA с разрешением 6403480 пикселов и поддержкой 16 цветов подойдет следующий код:
uses graph;
var gd,gm,error: integer;
begin
gd:=VGA; {адаптер VGA}
gm:=VGAHi; {режим 640*480пикс.*16 цветов}
initgraph(gd,gm,'');
error:=graphresult;
if error <> grOk then begin
write ('Ошибка графики: ',
grapherrormsg(error));
readln; halt;
end;
line (0,0,getmaxx,getmaxy);
readln; closegraph;
end.
Так как путь к файлу egavga.bgi указан пустым, предполагается, что он находится в текущей папке. После перехода в графический режим процедурой line рисуется линия из левого верхнего в правый нижний угол экрана, затем, после нажатия Enter, графический режим закрывается и происходит выход из программы.
Для автоматического выбора максимально возможного режима переменной gd необходимо присвоить значение detect, при этом переменные gm и pt не определяются, если в текущем каталоге, в котором находится система Турбо Паскаль, имеются файлы *.bgi. Пример:
uses graph; var gd,gm: integer;
begin
gd:=detect; initgraph(gd,gm,''); ...
Рассмотрим основные стандартные процедуры и функции модуля graph.
closegraph;
- процедура без параметров, завершает работу в графическом режиме. Следует выполнять эту процедуру перед завершением любой графической программы на Паскале.
cleardevice;
- процедура без параметров, очищает экран. При переходе в графический режим экран очищается автоматически, так что перед началом вывода эта операция не требуется.
function getmaxx:integer;
- функция возвращает максимальную координату пиксела по оси x.
function getmaxy:integer;
- функция возвращает максимальную координату пиксела по оси y.
setcolor(color:word);
- процедура устанавливает цвет рисования линий, точек и текста (аналог "пера" в программах для рисования). Цвета кодируются так же, как в текстовом режиме (см. табл. 24.1).
setfillstyle (style:word, color:word);
- процедура устанавливает цвет заполнения областей экрана (параметр color) и способ наложения цвета (параметр style). Является аналогом "кисти" в программах для рисования. Параметр color принимает значения, указанные в табл. 24.1, параметр style -- значения от 1 до 11. При style=1 происходит сплошное заполнение цветом, другие стили позволяют создать различные штриховки. Здесь и далее вместо цифр можно использовать символические имена стилей, узнать о них можно в справочной системе.
Приведем примеры.
setfillstyle (linefill,GREEN);
{установили заполнение зелеными линиями}
setfillstyle (solidfill,RED);
{ установили сплошную заливку красным}
Следующая процедура определяет стиль рисования линий:
setlinestyle (linestyle:word, pattern:word,
thickness:word);
Параметр linestyle (стиль линии) принимает значения от 0 до 4, значение 0 соответствует сплошной линии, параметр pattern при использовании готовых стилей со значением linestyle от 0 до 3 игнорируется, толщина линии thickness указывается значением 1 или 3 (в пикселах). Например, оператор setlinestyle (0,0,1); устанавливает стиль сплошной тонкой линии, а setlinestyle (1,0,3); -- толстую пунктирную линию. Для цифровых значений linestyle и thickness в библиотеке также определены символические имена, при значении linestyle=4 можно определить собственный стиль, задав его параметром pattern с помощью битовой маски.
Перейдем к стандартным подпрограммам, связанным с отображением на экране основных графических примитивов.
putpixel(x,y:integer,color:word);
- процедура высвечивает на экране пиксел с координатами (x, y) цветом color;
function getpixel (x,y:integer):word;
- функция вернет код цвета пиксела с координатами (x, y).
line(x1,y1,x2,y2:integer);
- процедура рисует текущим цветом прямую линию с экранными координатами начала (x1, y1), и конца (x2, y2).
moveto(x,y:integer);
- процедура устанавливает текущую позицию рисования (пера, графического курсора) в точку с экранными координатами (x, y).
lineto(x,y:integer);
- процедура проводит прямую линию из текущей позиции пера в точку с экранными координатами (x, y). Линия проводится текущим цветом пера.
linerel(dx,dy:integer);
- процедура проводит прямую линию из текущей позиции в точку с приращением координат от текущих на dx и dy, приращения могут быть как положительными так и отрицательными. Таким образом, процедура linerel позволяет указывать, в отличие от line и lineto, не абсолютные, а относительные координаты точки, куда нужно провести линию.
rectangle(x1,y1,x2,y2:integer);
- процедура рисует прямоугольник с координатами левого верхнего угла (x1, y1) и правого нижнего угла (x2, y2). Цвет прямоугольника, как и других незакрашенных фигур, определяется установкой, сделанной процедурой setcolor.
bar(x1,y1,x2,y2);
- процедура рисует закрашенный прямоугольник с координатами углов (x1, y1) и (x2, y2). Цвет и стиль заливки определяются процедурой setfillstyle.
bar3d (x1, y1, x2, y2, depth :integer;
top:boolean);
- процедура рисует трехмерный параллелепипед. Параметр depth определяет глубину фигуры по оси x, top указывает, рисовать ли верхнюю грань:
bar3d (50,50,100,100,20,true);
Следующая процедура рисует многоугольник или ломаную линию:
drawpoly (numpoint:integer;
var polypoints);
Аналогичная процедура fillpoly создает закрашенный цветом заливки многоугольник. Покажем работу процедуры на примере:
var poly: array [1..10] of integer;
poly[1]:=20; poly[2]:=20;
poly[3]:=60; poly[4]:=30;
poly[5]:=60; poly[6]:=60;
poly[7]:=40; poly[8]:=80;
poly[9]:=20; poly[10]:=20;
drawpoly (5,poly);
Элементы с нечетными номерами массива poly задают x-координаты точек, а с четными -- y-координаты. Таким образом, в данном случае нарисован пятиугольник.
floodfill (x,y,bordercolor:integer);
- мощная процедура, позволяющая закрасить любую замкнутую область, которой принадлежит точка (x, y) и которая ограничена по краям цветом bordercolor.
circle(x,y:integer,r:word);
- несложная процедура рисует окружность с центром в точке с координатами (x, y) и радиусом r.
arc(x,y:integer,sa,ea,r:word);
- процедура рисует дугу окружности с центром в точке с координатами (x, y), радиусом r, начальным углом sa и конечным углом ea. Углы sa и ea измеряются в градусах и отсчитываются против часовой стрелки от оси абсцисс.
Существуют также процедуры для рисования эллипсов и секторов.
Для вывода текста на графический экран имеются 2 основные функции.
outtextxy(x,y:integer,text:string);
- процедура выводит текст на экран, начиная с точки с координатами (x, y). Здесь text -- константа или переменная строкового типа, содержащая нужное сообщение. Текст выводится установленным цветом рисования линий. Заметим, что применение стандартных процедур write и writeln для вывода текста в графическом режиме нежелательно, так как они не позиционируют текст по пикселам и не учитывают установок цвета и фона графического экрана.
outtext(text:string);
- процедура выводит текст, заданный параметром, на экран, начиная с текущей позиции графического курсора.
Для краткости мы не рассматриваем методы привязки текста к позициям на экране.
В библиотеке graph нет процедур для вывода численных данных. Для этого необходимо сначала преобразовать число в строку с помощью процедуры str, а затем посредством операции '+' подключить строку к сообщению, выводимому процедурой outtextxy. Например:
max:=34.56; {Число}
str(max:6:2,smax);
{Преобразование числа max в строку smax}
outtextxy(400,40,'Максимум=' + smax);
{Вывод строки smax с комментарием}
Узнать ширину и высоту строки в пикселах можно с помощью стандартных функций function textwidth (s: string):word; и function textheight (s: string):word; соответственно.
Существуют также процедуры для управления внешними графическими шрифтами, хранящимися в файлах *.chr.
Приведем примеры программ, реализующих типовые графические построения.
1. Реализация процедуры, выводящей строку текста в центр прямоугольного окна на экране.
procedure centerstring
(x1,y1,x2,y2,color :integer; str: string);
var cx,cy:integer;
begin
setcolor (color); {Устанавливаем цвет}
rectangle (x1,y1,x2,y2); {Рамка}
rectangle (x1+2,y1+2,x2-2,y2-2);
cx:=(x1+ x2) div 2; {Координаты}
cy:=(y1+ y2) div 2; {центра}
settextJustify (centertext,centertext);
{Выравнивание текста по центру}
outtextxy (cx,cy,str); {Вывод строки}
end;
...
{ Обращение к данной процедуре: }
centerstring (100, 100, 200, 200, yELLOW,
'Hello!');
2. В следующем примере мы нарисуем на экране как "линейный" объект (домик с переменным числом окон и этажей), так и "радиальный" (солнце с лучами), для которого нужен пересчет из декартовых координат в полярные. Схема, поясняющая принцип перевода из декартовых координат в полярные, приведена на рис. 25.1.
Рис. 25.1. Пересчет из декартовых координат в полярные
program SunHouse;
uses graph,crt;
var Driver, Mode: integer;
i,j,u,N,K,x2,y2:integer;
rad:real; sunx,suny:integer;
begin
{Не проверяем правильность ввода}
writeln ('Сколько этажей?'); read (N);
writeln ('Сколько окон на этаж?');
read (K);
Driver := VGA; Mode:=VGAHi;
initgraph(Driver, Mode,'');
{Домик}
setcolor (15);
rectangle (20, getmaxy-20-70*n,
20+k*50+(k+1)*20, getmaxy-20);
{Крыша}
moveto (20,getmaxy-20-70*n);
lineto(10,getmaxy-20-70*n);
lineto (20,getmaxy-40-70*n);
lineto (20+k*50+(k+1)*20,getmaxy-40-70*n);
lineto (30+k*50+(k+1)*20,getmaxy-20-70*n);
lineto (20+k*50+(k+1)*20,getmaxy-20-70*n);
{Линии между этажами}
for i:=1 To N Do
line (20, getmaxy-20-70*i,
20+k*50+(k+1)*20, getmaxy-20-70*i);
setfillstyle (solidfill, YELLOW);
{Окна на каждом этаже}
for i:=1 To N Do {Цикл по этажам}
for j:=1 To K Do begin {Цикл по окнам}
bar(20+(j-1)*70+20,getmaxy-20-(i-1)*70-
60,20+(j-1)*70+70, getmaxy-20-(i-1)*70-10);
end;
sunx:=getmaxx-50; suny:=50;
{Центр солнца - координаты на экране}
FillEllipse (sunx, suny, 30, 30);
{Рисуем контур солнца}
setcolor (YELLOW);
{Рисуем лучи}
u:=0;
while u<=360 Do begin
{угол u меняем от 0 до 360 градусов}
rad:=u*pi/180;
{перевод в радианы для функций sin,cos }
x2:=round(sunx+50*cos(rad));
y2:=round(suny+50*sin(rad));
{перевод из полярных координат в декартовы}
line (sunx,suny,x2,y2);
u:=u+12; {шаг по углу=12 градусов}
end;
repeat until keypressed;
closegraph;
end.
3. Этот пример реализует программу построения графика функции f(x), заданной отдельной подпрограммой, в границах [a, b] изменения аргумента x.
Схема пересчета значений (x, f(x)) при в экранные координаты приведена на рис. 25.2. Пересчет выполняется в 2 этапа.
Узнав с помощью процедур getmaxx, getmaxy размеры графического экрана и определив значение xstep -- шаг по x, соответствующий одному пикселу на экране, мы сможем обеспечить масштабирование графика по оси X. Для масштабирования по оси Y на первом этапе пересчета требуется также определить максимальное и минимальное значения f(x) на интервале [a, b] при изменении x с шагом xstep.
Второй этап связан с непосредственным пересчетом значений (x, f(x)) в экранные координаты (cx, cy). Для решения этой задачи воспользуемся формулой, согласно которой значение x, принадлежащее интервалу [a, b], можно линейно преобразовать в значение y, принадлежащее интервалу [c, d]: . Эта формула позволит получить коэффициенты преобразования величин (x, f(x)) к экранным координатам. Дополнительно придется учесть то, что экранная ось Y проведена сверху вниз.
Рис. 25.2. Пересчет из декартовых координат в экранные
program graphOfFun;
uses graph,crt;
function f(x:real):real;
{ Функция, график которой строим }
begin
f:=sin(x)+cos(x);
end;
function getreal(s:string):real;
var f:real; {Ввод числа с контролем ошибок}
begin
repeat
write (s);
{$I-}readln(f);{$I+}
if IoResult=0 then break
else writeln
('Ошибка! Введено не число');
until false;
getreal:=f;
end;
procedure Init;
{Инициализация графического режима }
{VGA 640*480 пикселов, 16 цветов}
var driver,mode,error:integer;
begin
driver:=VGA; mode:=VGAHi;
initgraph(driver,mode,'');
error:=graphresult;
if error<>0 then begin
{Не ноль означает ошибку!}
writeln;
write ('Не могу инициализировать ',
'графику! Ошибка ',grapherrormsg(error));
halt(1)
end;
end;
var a,b: real; { Границы изменения x }
xmax,ymax: integer; { Размеры графического
экрана по длине и высоте }
xstep:real; { Шаг по x }
x,fx:real;
fmax,fmin:real;
cx,cy:integer; { Экранные координаты }
oldx,oldy:integer;{В этих переменных будем
запоминать координаты последней точки,
чтобы соединить ее с текущей }
xcoef,ycoef:real; {Коэффициенты пересчета к
экранным координатам }
ss:string;
begin
clrscr;
repeat
a:=getreal ('Левая граница по x=');
b:=getreal ('Правая граница по x=');
if a>b then write('Ошибка!Левая граница',
' должна быть меньше правой');
until a<b;
Init; { Инициализировать графику }
xmax:=getmaxx; ymax:=getmaxy;
{ размеры графического экрана }
xstep:=(b-a)/(xmax-19);
{ шаг по x, соответствующий 1 пикселу.}
x:=a; fmax:=f(a); fmin:=fmax;
while x<=b do begin
fx:=f(x);
if fx>fmax then fmax:=fx
else if fx<fmin then fmin:=fx;
x:=x+xstep;
end;
xcoef:=(xmax-19)/(b-a);
ycoef:=(ymax-19)/(fmax-fmin);
{ обрамление графика: }
setfillstyle (solidfill,CYAN);
bar (10,10,xmax-10,ymax-10);
setcolor (YELLOW);
rectangle (9,9,xmax-9,ymax-9);
str (a:8:3,ss); outtextxy (2,ymax-8, ss);
str (b:8:3,ss); outtextxy
(xmax-66,ymax-8, ss); {Границы по x }
settextstyle (DefaultFont,VertDir,1);
{ Границы по y выводим вертикально }
str(fmax:8:3,ss); outtextxy(9,2, ss);
str(fmin:8:3,ss);outtextxy(9,ymax-66, ss);
setcolor (White);{цвет рисования графика}
x:=a;
while x<=b do begin
fx:=f(x);
cx:=10+round((x-a)*xcoef);
cy:=ymax-10-round((fx-fmin)*ycoef);
putpixel (cx,cy,LightRED);
if x>a then line (oldx,oldy,cx,cy);
{ Соединяем две последние точки }
oldx:=cx; oldy:=cy;
{ Запоминаем текущую точку }
x:=x+xstep;
end;
repeat until keyPressed;
closegraph;
end.
Недостаток этой программы -- отсутствие пропорционального масштабирования по осям x и y. Подумайте, как ее можно улучшить. Листинг 12 из Приложения 4 представляет более объемную графическую программу, реализующую несложную компьютерную игру. Функция Draw этого листинга может также служить примером обработки 16-цветного изображения в формате BMP из программы на Паскале.
Решение задач, связанных с выводом графики на экран, часто требует сохранения участка экранного изображения в оперативной памяти с последующим восстановлением прежней картинки. Для решения этой проблемы в библиотеке graph предусмотрен набор соответствующих функций.
В первую очередь требуется оценить объем памяти, требуемый для сохранения участка экрана. Стандартная функция библиотеки graph, имеющая вид imagesize (x1,y1,x2,y2:integer):word, где x1, y1 -- экранные координаты верхнего левого, а x2, y2 -- правого нижнего угла, возвращает число байт памяти, необходимых для сохранения заданной прямоугольной области. Эта функция может определить объем памяти до 64 Кб включительно, так как тип возвращаемого значения -- беззнаковое целое типа word. Если количество требуемой памяти больше либо равно 64 Кб, возвращается значение ошибки -11 (grError). Разумеется, вызов функции предполагает, что монитор находится в графическом режиме.
После того, как требуемое количество байт определено, программа должна позаботиться о выделении участка оперативной памяти, предназначенного для сохранения изображения. Это легко сделать, используя системную процедуру getmem (var p:pointer; size:word), где объем памяти size ранее определен функцией imagesize, а переменная p представляет собой указатель. Ранее незнакомый нам тип данных "указатель" служит для косвенного вызова одних переменных через другие. Фактически, переменная-указатель хранит адрес другой типизированной переменной и может обратиться к ней, используя синтаксис p^, где p -- имя указателя. Применение указателей позволяет создавать динамические переменные, способные в разные моменты времени адресовать различные участки оперативной памяти, в которых хранятся данные. Самый большой блок памяти, который может выделить getmem, также равен 64 Кб. Освободить ранее занятую память можно процедурой Freemem (var p:pointer; size:word).
Наконец, третий шаг -- сохранить участок экрана, используя только что сформированный в оперативной памяти буфер. Для этой цели достаточно использовать процедуру Getimage (x1,y1,x2,y2:integer; var p). Здесь параметры x1,y1,x2,y2 имеют тот же смысл, что для функции imagesize, а нетипизированный параметр-указатель p получен процедурой getmem.
Теперь требуемый участок экрана сохранен в памяти и может быть занят новым изображением. После того, как изображение выполнило свои функции и нужно восстановить прежний фрагмент экрана, достаточно вызвать процедуру putimage (x,y:integer; var p; mode:word), где x,y -- экранные координаты левого верхнего угла восстанавливаемой области, p -- указатель на сохраненную память, а переменная mode определяет, какая двоичная операция будет использована при выводе изображения на экран. Для неизмененного восстановления изображения следует передавать в качестве mode значение NormalPut, другие возможные значения параметра -- copyPut, XORPut, ORPut, ANDput и NOTPut. Все описанные функции использованы в листинге, приведенном ниже. Его изучение поможет вам в написании аналогичных программ, поддерживающих движение по экрану графических объектов.
uses graph,crt;
var Gd, Gm : integer;
P : pointer;
size : word;
x,y,width,height: integer;
ch:char;
changed:boolean;
begin
{Инициализация графики}
Gd:=VGA; Gm:=VGAHi;
initgraph(Gd, Gm, '');
if graphresult <> grOk then halt(1);
{Отрисовка фона}
setfillstyle(xHatchFill, CYAN);
bar(0, 0, getmaxx, getmaxy);
{Параметры активного окна}
x:=getmaxx div 2;
y:=getmaxy div 2;
width:=40;
height:=30;
{Выделение памяти для сохранения
фона под окном}
size:=imagesize(x,y,x+width-1,y+height-1);
getmem(P, size);
getimage(x, y, x+width-1, y+height-1, P^);
{Первая отрисовка активного окна}
setfillstyle(solidfill, RED);
bar (x,y,x+width-1,y+height-1);
{Признак изменения положения окна}
changed:=false;
repeat {Цикл движения объекта}
ch:= readkey; {Читаем код клавиши}
if ch=#0 then begin
{Если это расширенный код...}
ch:= readkey; {то читаем второй байт}
case ch of
{Реагируем только на 4 клавиши:}
#72: if y>0 then changed:=true;
{стрелка вверх}
#80: if y+height<getmaxy then
changed:=true; {стрелка вниз}
#75: if x>0 then changed:=true;
{стрелка влево}
#77: if y+width<getmaxx then
changed:=true; {стрелка вправо}
end;
if changed=true then begin
{если флаг реакции выставлен}
PutImage(x, y, P^, NormalPut);
{восстанавливаем экран под окном}
case ch of
{и меняем нужную координату окна}
#72: dec(y);
#80: inc(y);
#75: dec(x);
#77: inc(x);
end;
getimage(x,y,x+width-1,y+height-1,P^);
{сохраняем экран под новым положением окна}
bar (x,y,x+width-1,y+height-1);
{и перерисовываем окно}
changed:=false;
{сбросить флаг изменения}
end;
end;
until ch=#27; {...пока не нажата Esc}
Freemem (p,size); {освобождаем память}
closegraph; {и закрываем графику}
end.
Говоря о написании графических программ применительно к Паскалю, нельзя обойти стороной вопрос о поддержке DOS-графики современными компьютерами. К сожалению, многие современные платформы не поддерживают графические видеорежимы DOS. Помочь может эмулятор DOS-машины, такой как свободно распространяемая программа DOSBox. Скачав DOSBox по адресу http://dosbox.sourceforge.net и установив ее, мы можем запускать приложения DOS в любом видеорежиме с поддержкой (эмуляцией) многочисленных устаревших программных и аппаратных решений.
Желательно также установить оболочку эмулятора, позволяющую создавать для DOS-приложений настраиваемые ярлыки. Оболочка DOSShell доступна по адресу http://www.loonies.narod.ru/dosshell.htm, а узнать об эмуляторах DOS больше Вы можете по адресам http://ru.wikipedia.org/wiki/DOSBox и http://gh.gameslife.ru/text/dosbox.htm.
гостевая; E-mail |