Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

[ Миронченко ] Императивное и объектно-ориентированное програмирование на Turbo Pascal и Delphi

.pdf
Скачиваний:
68
Добавлен:
25.04.2014
Размер:
3.16 Mб
Скачать

351

Глава 18: Графика

С точки зрения пользователя, рисовать можно прямо на форме, за исключением компонентов, которые на ней расставлены. Но для того, чтобы не создавать путаницы, графическими возможностями заведует класс TCanvas. У формы есть объект этого класса. В состав класса TCanvas входят, помимо всего прочего, функции рисования простых геометрических фигур и три свойства: Pen типа TPen (перо), Brush типа

TBrush (кисть), Font типа TFont (шрифт).

Перо отвечает за тип и цвет выводимых на канву линий, кисть – за заливку сплошных областей, а шрифт – за тип и размер шрифта, которым будет выводиться на канву текст.

18.1. Цвет кисти и пера

Любой цвет можно задать в виде наложения синего, красного и зеленого цветов. Задавая разные интенсивности этих цветов, мы будем получать различные результирующие цвета. В Delphi цвет определяется так (его объявление находится в модуле Graphic):

type

TColor = -$7FFFFFFF-1..$7FFFFFFF;

Т.е. цвет задается 4-байтовым целым числом.

Старший байт – это номер палитры. Чтобы использовать стандартную палитру, надо устанавливать значение старшего байта равным 00.

Следующие 3 байта задают интенсивность синего, зеленого и красного цвета соответственно. Например:

$00FF0000 – насыщенный синий цвет $0000FF00 – насыщенный зеленый цвет $000000FF – насыщенный красный цвет

Многие распространенные цвета можно задавать также с помощью встроенных констант, например:

clWhite, clRed – константы, задающие белый и красный цвета соответственно.

Другие константы вы можете посмотреть в справочной системе (наберите TColor

type)

У классов TBrush, TPen есть свойство, отвечающее за цвет заливки областей и цвета выводимых линий соответственно:

property Color:TColor;

18.2. Рисование многоугольников

Следующая процедура рисует прямоугольник текущим пером, и закрашивает его текущей кистью.

procedure Rectangle(X1, Y1, X2, Y2: Integer); overload; procedure Rectangle(const Rect: TRect); overload;

Тип TRect определяется так:

352

type

TPoint = packed record

X:Longint;

Y:Longint;

end;

TRect = packed record case Integer of

0:(Left, Top, Right, Bottom: Integer);

1:(TopLeft, BottomRight: TPoint);

end;

Ключевое слово packed влияет на способ хранения структурированных типов (записей, массивов, классов и т.д.). Можете набрать слово packed в справочной системе, чтобы получить больше информации.

Чтобы нарисовать незакрашенный прямоугольник, можно использовать процедуру

procedure FrameRect(const Rect: TRect);

Но имейте в виду, что граница прямоугольника рисуется в этой процедуре цветом кисти.

Чтобы рисовать пером, надо использовать процедуру Polygon, которая рисует многоугольник по заданному массиву вершин (ребро – это линия между соседними вершинами).

procedure Polygon(Points: array of TPoint);

18.3. Рисование линий

procedure MoveTo(X, Y: Integer);

Устанавливает новую позицию пера. procedure LineTo(X, Y: Integer);

Рисует линию от текущего положения пера до точки (х, у). При этом новая позиция пера становится равной (х, у).

На самом деле в LineTo рисуется не линия, а отрезок, начало которого хранится в свойстве PenPos (позиция пера) канвы.

Кроме линий и прямоугольников в классе TCanvas есть еще довольно много графических примитивов. Вы можете прочитать о них, посмотрев методы класса TCanvas в справочной системе.

Пример 1: Построение правильного многоугольника.

Создайте новый проект, и на форму поместите простую кнопку (Button1). По нажатию этой кнопки на канве будет рисоваться правильный многоугольник.

procedure VielEck(n:integer;x,y:integer;a:integer;fi0:real;C:TCanvas); var

i:integer;

x1,x2,y1,y2:integer;

fi:real; //угол, на который надо повернуть вершину многоуг. begin

x1:=x+round(a*cos(fi0));

353

y1:=y+round(a*sin(fi0));

fi:=fi0+2*Pi/n;

C.MoveTo(x1,y1); //переходим в точку, с которой будут чертиться линии

for i:=0 to n-1 do //чертим все линии begin

x2:=x+round(a*cos(fi)); //координаты следующей вершины y2:=y+round(a*sin(fi));

C.LineTo(x2,y2); //чертит линию от текущего положения до точки

(х2,у2) fi:=fi+2*Pi/n; end;

end;

procedure TForm1.Button1Click(Sender: TObject); begin

with Canvas do begin

Pen.Color:=clWhite;//устанавливаем белый цвет пера Brush.Color:=clWhite;//устанавливаем белый цвет кисти Rectangle(clipRect);//рисуем белый прямоугольник на весь экран Pen.Color:=clRed; //устанавливаем красный цвет пера

VielEck(4,300,300,150,0,Form1.Canvas);

end;

end;

По-немецки многоугольник – das Polygon, но мне кажется, что гораздо красивее было бы назвать его das Vieleck (по аналогии с Viereck – четырехугольник; само слово viel означает много). Поэтому я назвал процедуру VielEck. Ее параметры – количество углов, центр окружности, описанной около многоугольника, радиус описанной окружности и угол относительно положительного направления оси ОХ, на который надо повернуть многоугольник и канва, на которой сам Vieleck будет рисоваться.

Сначала рассмотрим обработчик нажатия кнопки. Работа ведется со свойством Canvas (которое принадлежит классу TForm).

Чтобы очистить экран закрашиваем весь фон белым цветом.

ClipRect, который передается в процедуру рисования прямоугольника – это специальное свойство канвы:

property

Содержит прямоугольник, который должен быть

ClipRect:TRect;

перерисован.

Когда форма только создается, ClipRect содержит размеры всей канвы.

Если вы еще не запустили программу, то сделайте это. Вы увидите, что кнопка находится поверх канвы, и на ней ничего не рисуется. Теперь измените размеры экрана, перетащите окно так, чтобы оно одним краем вылезло за границы экрана и верните в исходное положение или сверните окно, а затем восстановите его. Вы увидите, что изображение на канве не перерисовывается само по себе, в отличие от кнопок и других компонентов.

354

18.4.Рисуем шашечную доску

Уформы есть специальное событие OnPaint, которое возникает тогда, когда надо перерисовать форму. Если написать обработчик этого события, то мы сможем выполнять специальные действия при перерисовке изображения канвы.

При изменении размеров формы возникает событие OnResize. В следующем примере мы будем рисовать шашечную доску (иногда ее называют шахматной, но исторически раньше появились шашки, поэтому правильнее писать «шашечная доска»). Мы сделаем так, чтобы она перерисовывалась, когда это необходимо и, кроме того, чтобы ее размеры менялись в зависимости от размеров формы.

Пример 2: Шашечная доска.

1 : unit BrettU;

2 :

3 : interface

4 :

5 : uses Graphics,Types,Forms,Math;

6 :

7 : type

8 : Brett = class {das Brett - доска} 9 : private

10:Langex:byte;{количество клеток по горизонтали}

11:Langey:byte;{количество клеток по вертикали}

12:Seite:byte;{длина стороны клетки (в пикселях)}

13:x,y:integer;{координаты верхнего левого угла доски}

14:farbe1:TColor;{цвет "белых" клеток}

15:farbe2:TColor;{цвет "черных" клеток}

16:ErwSeite:integer; //желаемая ширина клетки

17:public

18:procedure FestSt(aLangex,aLangey,aSeite:byte; ax,ay:integer; afarbe1,afarbe2:TColor);

19:constructor Bilde(aLangex,aLangey,aSeite:byte; ax,ay:integer; afarbe1,afarbe2:TColor);

20:procedure Male(C:TCanvas);

21:procedure StellFormat(Frm:TForm);

22:end;

23:

24: implementation 25:

26:constructor Brett.Bilde(aLangex,aLangey,aSeite:byte; ax,ay:integer; afarbe1,afarbe2:TColor);

27:begin

28:FestSt(aLangex,aLangey,aSeite,ax,ay,afarbe1,afarbe2);

29:end;

30:

31:procedure Brett.FestSt(aLangex,aLangey,aSeite:byte; ax,ay:integer; afarbe1,afarbe2:TColor);

32:begin

33:Langex:=aLangex;

34:Langey:=aLangey;

35:Seite:=aSeite;

355

36:x:=ax;

37:y:=ay;

38:farbe1:=afarbe1;

39:farbe2:=afarbe2;

40:ErwSeite:=Seite;

41:end;

42:

43:procedure Brett.StellFormat(Frm:TForm);

44:var

45:EinzugBr,EinzugH:integer;{отступ от краев (по ширине и

высоте)}

46:R:TRect;

47:begin

48:EinzugBr:=15;

49:EinZugH:=15;

50:{Вычисляем текущие координаты клиентской части формы}

51:R.Left:=Frm.Left;

52:R.Top:=Frm.Top;

53:R.Right:=Frm.Left+Frm.ClientWidth;

54:R.Bottom:=Frm.Top+Frm.clientHeight;

55:

56:{Длина и ширина поля}

57:Seite:=min(min(ErwSeite,(R.Right-R.Left-2*EinzugBr) div LangeX), (R.Bottom-R.Top-2*EinzugH) div LangeY);

59:x:=(R.Right-R.Left - Seite*LangeX) div 2;

60:y:=(R.Bottom-R.Top - Seite*LangeY) div 2;

62:end;

64:procedure Brett.Male(C:TCanvas);

65:var

66:Pfrb,Bfrb:TColor;

67:i,j:byte;

68:R:TRect;

69:begin

70:Pfrb:=C.Pen.Color;

71:Bfrb:=C.Brush.Color;

72:C.Brush.Color:=farbe1;

73:C.Pen.Color:=farbe2;

74:{заполняем всю доску "белым" цветом}

75:R.Left:=x;

76:R.Right:=x+Langex*Seite;

77:R.Top:=y;

78:R.Bottom:=y+Langey*Seite;

79:C.Rectangle(R);

80:

81:C.Brush.Color:=farbe2;

82:{Заполняем нужные клетки "черным" цветом}

83:for i:=1 to (Langex div 2) do

84:for j:=1 to (Langey div 2) do

356

85: C.Rectangle(x+(2*i-1)*Seite,y+(2*j- 2)*Seite,x+2*i*Seite,y+(2*j-1) *Seite);

86:

87:for i:=1 to (Langex div 2) do

88:for j:=1 to (Langey div 2) do

89:C.Rectangle(x+(2*i-2)*Seite,y+(2*j-1)*Seite,x+(2*i- 1)*Seite, y+(2*j)*Seite);

91:C.FrameRect(R);

92:C.Pen.Color:=Pfrb;

93:C.Brush.Color:=Bfrb;

94:end;

95:END.

Впроцедуре Male используется FrameRect (см. «рисование прямоугольников»). Сам алгоритм рисования можете разобрать самостоятельно. StellFormat («установи формат») устанавливает размеры доски относительно формы. У доски есть желаемая ширина клетки. Если доска с такой шириной клетки может поместиться на канву, то она рисуется именно с таким размером по центру канвы. Если вся доска не влезает на канву, то размер выбирается так, чтобы она влезла и еще оставалось место до края канвы (EinzugBr – отступ по горизонтали, EinzugH – отступ по вертикали). Теперь рассмотрим основной модуль.

1 : unit haupt;

2 :

3 : interface

4 :

5 : uses

6 : Windows, Messages, SysUtils, Variants, Classes, Graphics,

7 : Controls, Forms,Dialogs,BrettU;

8 :

9 : type

10:TForm1 = class(TForm)

11:procedure FormCreate(Sender: TObject);

12:procedure FormPaint(Sender: TObject);

13:procedure FormResize(Sender: TObject);

14:private

15:B:Brett;

16:public

17:{ Public declarations }

18:end;

19:

20:var

21:Form1: TForm1;

23:implementation

25:{$R *.dfm}

27:procedure TForm1.FormCreate(Sender: TObject);

28:begin

357

29:B:=Brett.Bilde(40,30,15,100,100,clYellow,clBlack);

30:end;

31:

32:procedure TForm1.FormPaint(Sender: TObject);

33:begin

34:B.Male(Canvas);

35:end;

36:

37:procedure TForm1.FormResize(Sender: TObject);

38:begin

39:with Canvas do

40:begin

41:Pen.Color:=Brush.Color;

42:Rectangle(ClipRect);

43:end;

44:B.StellFormat(Form1);

45:B.Male(Canvas);

46:end;

47:end.

Сами видите, что я добавил к форме одно закрытое поле – доску.

При создании формы возникает событие OnCreate, которое обрабатывается методом FormCreate, в которой создается объект В. Сразу после этого форма должна быть перерисована, поэтому возникает событие OnPaint, которое обрабатывается с помощью процедуры FormPaint. FormResize, обработчик события OnResize, вызывается после изменения размеров формы. В нем закрашивается все содержимое формы, устанавливаются новые параметры доски и рисуется ее обновленный вариант.

18.5. Фракталы

Термин «фрактал» ввел Бенуа Мандельброт. Но первые фрактальные множества появились задолго до работ Мандельброта. На рисунке 18.1 вы видите множество Кантора, которое строится на базе простого отрезка (0-я итерация). На первом шаге из отрезка выбрасывается средняя треть. Затем из двух оставшихся третей также выбрасываются центральные трети и т.д. до бесконечности. То, что осталось от отрезка после бесконечного количества выбрасываний и есть множество Кантора ( K ).

Рис. 18.1. Множество Кантора (0, 1, 2, 4-я итерации)

Что в нем такого необычного, спросите вы? Но оказывается, что несмотря на то, что суммарная длина выброшенных отрезков равна длине исходного отрезка, количество точек множества Кантора (т.е. его мощность) равно количеству точек исходного отрезка (это означает, что можно найти функцию, которая каждой точке K поставит в соответствие некоторую точку отрезка, причем разным точкам K

358

соответствуют различные точки отрезка и для любой точки x отрезка существует точка из K , которая отображается в точку x )!

Другим интересным примером является кривая Коха17, с которой вы познакомились в начале главы «Рекурсия».

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

E

0

 

 

E1

 

 

 

 

 

 

 

 

 

 

E

2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 18.2. Пыль Кантора (0-я, 1-я и 2-я итерации)

Еще одним монстром является кривая Пеано, которая полностью заполняет собой квадрат (т.е. проходит через каждую его точку). На рисунке вы видите первые 3 итерации построения этой кривой. Сама же кривая получится после бесконечного числа итераций.

Рис. 18.3. Кривая Пеано

Обычно под фракталом понимают множество дробной размерности. Но для того, чтобы это понятие обрело смысл, надо определить, что такое размерность и каким образом она умудряется быть дробной.

Начнем мы с простейшего объекта – квадрата. Пусть на дан квадрат Q со стороной 3 см. Какое минимальное количество ( N ) квадратов со стороной δ см надо, чтобы покрыть квадрат Q ? Пусть δ = 1 . Тогда N = 9 .

δ= 1 N = 9 4 = 9δ −2

2

17 В различных книгах имя этого ученого переводилось так: Гельг фон Кох, Хельге фон Кох и даже Хельга фон Кох (Хельга – женское имя).

 

 

359

δ =

1

N = 9 4k = 9δ −2

2k

 

 

 

 

Значит можно найти сколь угодно малое число δ такое, что минимальное

количество квадратов со стороной δ , которыми можно покрыть исходный квадрат Q ,

будет равняться 9δ −2 (9 – площадь квадрата, 2 – размерность плоскости).

Давайте теперь покрывать куб со стороной 3 см кубиками со стороной δ . По

аналогии с предыдущим случаем получим:

 

δ =

1

N = 27 8k = 27δ −3

(27 – объем куба, а 3 – размерность пространства)

 

 

2k

 

 

 

 

Назовем несущим пространством пространство, элементами которого мы

покрываем множество, размерность которого мы хотим выяснить.

 

 

 

В двух разобранных примерах мы проверили, что покрытии множества

некоторыми квадратами несущего пространства кол-во необходимых

квадратов N

будет равно

 

 

 

 

 

N = s

(1)

Причем в разобранных примерах s - и есть то, что мы интуитивно считаем размерностью объекта, причем она совпадает с размерностью самого пространства.

Выделим s из формулы (1): s =

ln N

.

 

 

−(ln c + ln δ )

Теперь заметим, что формула (1) в двух разобранных примерах выполняется точно лишь при удачном выборе числа δ . Вообще говоря, не при каждом числе δ эта формула будет выполняться. Но этого и не надо. Главное, чтобы она была верна при δ → 0 , т.е. когда мы будем покрывать очень малыми объектами. Поэтому введем такое

определение размерности:

 

Размерностью множества

Q назовем величину s , вычисляющуюся по формуле

s = lim

ln N

, где N = N (δ )

- минимальное количество квадратов с длиной стороны

 

δ →0 −(ln δ )

 

не больше δ , которыми можно покрыть множество Q .

Вообще говоря, покрывать можно не обязательно квадратами. Можно покрывать, например, кругами, размерность от этого не изменится, хотя коэффициент c в формуле

(1) будет иной.

Чтобы доказать это достаточно заметить, что в круг любого радиуса можно вписать квадрат и около этого круга можно описать квадрат:

d

d 2

Рис 18.4.

360

Введем обозначения:

NQ (x) - минимальное количество квадратов с длиной стороны не больше x , которыми надо покрыть заданное множество.

NK (x) - минимальное количество кругов диаметром не больше

x , которыми надо

покрыть заданное множество.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Очевидно, что:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

NQ (δ ) ≤ NK (δ 2) ≤ NQ (δ 2)

 

 

 

 

 

 

 

 

 

 

При 0 < δ < 1 получим:

ln

(NQ (δ ))

ln (NK (δ 2))

 

ln (NQ

(δ 2))

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

− ln δ

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

− ln δ

 

 

− ln δ

 

 

 

 

 

 

 

 

 

 

Заметим, что lim

ln (NQ (δ 2))

ln (NQ (δ 2))

ln (NQ (δ 2))

 

 

ln (NQ

(δ ))

.

 

 

 

 

= lim

 

 

 

 

= lim

 

 

 

= lim

 

 

 

 

 

 

− ln δ

 

 

 

 

 

 

 

 

− ln(δ )

 

 

δ →0

 

δ →0 − ln(δ 2) + ln

2 δ →0

− ln(δ 2)

 

 

δ →0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ln (NK (δ 2))

 

 

 

ln ( N

 

(δ ))

.

 

Точно так же получим следующее соотношение: lim

 

 

= lim

 

K

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

δ →0

− ln δ

 

δ →0

− ln(δ )

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Значит lim

ln (NQ (δ ))

= lim

ln ( NK (δ ))

, т.е. размерность не зависит от того, считаем ли мы

− ln(δ )

− ln(δ )

δ →0

δ →0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

кругами диаметра δ

или квадратами со стороной δ .

 

 

 

 

 

 

 

 

 

 

 

 

Давайте теперь воспользуемся таким определением для того, чтобы подсчитать размерность снежинки Коха (см. главу «Рекурсия»). Покрывать снежинку для удобства будем кругами диаметра δ . Пусть сторона исходного треугольника будет равна 1 ед.

Если δ =

1

, то количество кругов равно 3 4

(на рис. 18.5 показано, как

 

3

 

 

 

 

покрывается одна сторона снежинки). Ясно, что если

δ =

1

, то количество кругов

3k

 

 

 

 

 

равно 3 4k .

Теперь давайте считать размерность:

s = lim

ln N

= lim

ln(3 4k )

 

 

δ →0 −(ln δ )

k →∞ −(ln 3k )

= lim

ln(3) + k ln 4

=

ln 4

= log

 

4 .

k ln 3

ln 3

3

k →∞

 

 

 

 

 

 

 

 

 

 

 

 

Выходит, что если ввести размерность по формуле (2), то действительно существуют множества дробных размерностей.

Рис. 18.5. Покрытие снежинки Коха кругами