учебник по паскалю. Программа 5 Алгоритм 5 Свойства алгоритма 6 Формы записи алгоритма 6
Скачать 2.21 Mb.
|
25. Модуль graph и создание графики на ПаскалеДля работы с графикой из программы на Паскале в папке, откуда она запускается, должен присутствовать файл egavga.bgi. Он представляет собой графический драйвер, предназначенный для управления видеопамятью в режимах поддержки мониторов типов EGA и VGA. Разумеется, современные мониторы давно "переросли" эти два исторически распространенных класса дисплеев. Однако, на любом современном компьютере поддержка видеорежимов EGA и VGA по-прежнему возможна, если не напрямую, то через специальную программу-эмулятор (см. конец главы). В поставку Паскаля могут входить и другие файлы с расширением *.bgi, отвечающие за работу с мониторами различных типов. Кроме того, при компиляции программы, имеющей графический вывод, должен быть доступен модуль graph.tpu, содержащий подпрограммы отрисовки графических объектов. Библиотека graph.tpu подключается стандартным способом с помощью директивы uses в разделе описаний программ: uses graph; В графическом режиме, который в современных операционных системах типа Windows является основным, экран представляет собой матрицу точек (пикселов), причем имеется возможность высветить любой пиксел любым цветом. Координаты каждого пиксела определяются парой целых чисел:
Таким образом, координаты левого верхнего угла экрана равны (0, 0). Любой объект, высвечиваемый на экране, является совокупностью отдельных пикселов. Количество воспроизводимых пикселов по горизонтали и вертикали зависит от типа монитора и установленного графического режима. Каждый монитор может использовать множество режимов, отличающихся количеством поддерживаемых цветов и разрешением графического экрана в пикселах. Классический Паскаль поддерживает монитор CGA, имеющий разрешение до 320200 пикселов, монитор EGA с разрешением 640350, монитор VGA с разрешением до 640480. Работу с более современными и мощными графическими устройствами, относящимися к классу superVGA, Паскаль непосредственно не поддерживает, хотя существуют созданные независимыми разработчиками графические драйверы этих режимов. Графический режим работы экрана кроме количества пикселов характеризуется определенной палитрой -- набором видимых цветов. Каждая палитра состоит из 4 цветов для монитора CGA или 16 цветов для EGA и VGA. Установка графического режима осуществляется путем обращения к процедуре initgraph: initgraph(var gd:integer, var gm:integer, pt:string); Целочисленные переменные gd и gm задают тип графического драйвера и режим его работы, строковая переменная pt -- путь к файлу *.bgi. Например, при выборе основного для Паскаля видеорежима VGA с разрешением 640480 пикселов и поддержкой 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 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 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 changed:=true; {стрелка вниз} #75: if x>0 then changed:=true; {стрелка влево} #77: if y+width 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. |