Pers.narod.ru. Обучение. Учебник по Паскалю. Глава 25

25. Модуль graph и создание графики на Паскале

 

Для работы с графикой из программы на Паскале в папке, откуда она запускается, должен присутствовать файл 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.

Рейтинг@Mail.ru
вверх гостевая; E-mail
Hosted by uCoz