XX 图形程式设计基本观念

 

XX-0 前言

 

由於Windows是一个图形界面的作业系统,所以传统的Windows程式设计方法在处理有关图形设计时,多半是遵循自着Windows诞生以来的方法,透过所谓的GDI Graphics Device Interface)来实作图形的绘制。GDI式的绘图法对於早期曾经在Dos下自行撰写绘图模组的程式设计师来说,虽然节省了不少 <重新发明轮子> 的麻烦,但是相对地也带来另外一种程式设计的思维模式转变,相信曾经经历过这段过程的人都馀悸犹存。

 

对於完全没有任何程式经验的初学者来说,庞大复杂的GDI绘图系统更是一个难以跨越的学习障疑。

 

Windows中难道没有一个简易可行的绘图方法吗?有的,本章要为你介绍的C++ Builder绘图系统,就提供了一个高度抽象化的绘图模组,让你可以用非常直观的方式来实作出Windows下的绘图功能。

 

XX-01 C++ Builder的神奇画布 (Canvas)

 

C++ Builder中提供了一种称为Canvas的性质 Property),在仔细观察後你会发现,几乎所有的视觉化元件都包含这个性质,它就是C++ Builder为程式设计师所提供的神奇画布。

 

Canvas包含了许多和绘图有关的性质,如PenBrushPixelsFont等;另外也包含了各种绘图的函式,如LineToRectangleMoveToPolygon等等。在本单元中,我会一一为你介绍这些性质及工具的用法,相信在领略了C++ Builder的直觉式绘图法後,你再也不会为它所困扰。

 

对於CanvasPenBrushC++ Builder的关键字,往後我会采取直接使用原文而不翻译的方式,这是因为原文非常简洁,译成中文反而饶舌,有画蛇添足之虞。当然,在某些时候,使用中文可以使文意较为流畅时,我还不会择其善者而用之。

 

XX-02 C++ Builder的直觉绘图法

 

在进入主题前,我先以几个范例来说明C++ Builder绘图法的优点。如果你曾经尝试过传统Windows的绘图法,大概已经感受了它的不友善及繁杂,这时你也许会想:只是画条线而已嘛,为什麽还要搞什麽DCGDI Object等等又 又长的叙述。

 

你说得没错!虽然庞大的GDI绘图系统具备通天彻地的完整Windows绘图功能,可是也许大部份的人要求的并不多,只是希望可以在Windows下画出自已心中理想的图而已。

 

以下我以几个绘图范例来说明C++ Builder的绘图法。

 

简易绘图范例XX-01

 

范例一的输出

XX-01

 

这个程式很简单,只是重复地画出几个图形,如此就组成了一个美丽且和谐的图形。我们来看看其关键性的绘图部份:

其中Ellipse为一个画椭圆的绘图函式。

 

void __fastcall Ellipse(int X1, int Y1, int X2, int Y2);

函式说明:

利用Ellipse可在Canvas上画出圆形或椭圆形. 其环绕矩形的左上角为座标为 (X1, Y1) 而右下角坐标为 (X2, Y2). 若是该矩形为正方形,则所绘之图形为圆形。.

当此椭圆形绘制时,使用Pen的值画出其外框,而使用Brush值填满其内部。

: Windows 95X1+X2,Y1+Y2X1+X2+Y1+Y2的值皆不可超过 32768

 

void __fastcall TForm1::FormPaint(TObject *Sender)

{

double AL,x1,y1,x2,y2;

int L=120;

for (int i=0; i<48; i++)

{

AL = i*M_PI/24;

x1 = L*cos(AL);

y1 = L*sin(AL);

x2 = x1+320;

y2 = -y1+240;

Canvas->Ellipse(x2-90,y2-90,x2+90,y2+90);

}

}

 

怎麽样?简单吧!这个程式只是做一些简单的数学运算,然後以视窗中心为准,画出48个图形而已。我在这个程式中没有用到任何的软体元件,只是以表格 Form)做为显示的基台,由於表格中亦包含了Canvas性质,因此表示我们可以在上绘图。同时为了绘图的方便,我将表格的背景色调为黑色,并将适当地调整画笔的颜色及视窗的大小:

 

void __fastcall TForm1::FormCreate(TObject *Sender)

{

Width = 640;

Height = 480;

Canvas->Pen->Color = clTeal;

Canvas->Brush->Style = bsClear;

}

 

基本上以上的调整都可以用二种方式来达成,一种是在属性编辑器,另一种则是在程式执行时动态改变,上面的程式码就是在改变视窗的宽度及高度,并设定画笔及笔刷,我将画笔颜色设为clTeal并将笔刷型式设为bsClear,以避免填色的动作,画笔及笔刷的动作在後续单元中会详细介绍,在此你只要大略了解其动作即可。

 

程式输出时,其背景为黑色是因为我在属性编辑器设定了黑色的背景值

 

简易绘图范例XX-02

 

范例二的输出

 

XX-02

 

这个范例使用了画线的函式来画出图形,同样地我们来看看其关键的函式LineToMoveTo

 

void __fastcall LineTo(int X, int Y);

函式说明

LineTo函式由画笔位置画一条至 (X,Y)点的直线,此直线不包含(X,Y)点,同时将画笔位置移至 (X,Y)。此函式通常必须配合MoveTo来移动画笔位置。

此直线使用Pen来绘制。

 

 

void __fastcall MoveTo(int X, int Y);

函式说明

MoveTo是用以在呼叫LineTo前设定画笔位址值。呼叫此函式和直接设定PenPos属性的效果相同。

 

 

void __fastcall TForm1::FormPaint(TObject *Sender)

{

double A,x1,y1,x2,y2;

int D=150,E=50;

double L,M,O,P;

for (int i=0; i<240; i++)

{

A = i*M_PI/120;

L = D+D/3*(1+cos(12*A)/2)*cos(A);

x1 = 240+1.25*L*cos(A);

M = E+E/3*(1+sin(12*A)/2)*cos(A);

x2 = 240+1.25*M*cos(A);

O = D+D/3*(1+cos(10*A)/2)*sin(A);

y1 = 240-O*sin(A);

P = E+E/2*(1+cos(15*A)/2)*sin(A);

y2 = 240-P*sin(A);

Canvas->MoveTo(x1,y1);

Canvas->LineTo(x2,y2);

}

}

 

以上的程式列表就是范例XX-2的关键部份,也就是实际负责绘图的FormPaint部份,其中用了许多的叁角函数如sin,cos,M_PI等运算,我在此就不另加说明了,之所用使用了这些函数只是为了在介绍这些简单的函式时,利用这些简单函式所产生的美丽图形,加深你的印象及学习效果罢了。电脑绘图之所以迷人也在於此,它可以利用电脑强大的运算及绘图能力,很容易地将一些复杂的图显示出来。

 

除了以上两个范例之外,我再为你示范两个同样使用简单的画线函式的绘图范例,不过细节我就不再详述了,另外因为它的关键部份程式很短,为了避免你来回读取档案交互参考的困扰,我还是把它列出来,你可以尝试着修改其中的参数,说不定可以产生更为美观的图形呢!

 

范例叁的输出

XX-03

 

void __fastcall TForm1::FormPaint(TObject *Sender)

{

double A,x1,y1,x2,y2;

int D=100;

double E;

for (int i=0; i<720; i++)

{

A = i*M_PI/360;

E = D*(1+sin(4*A));

x1 = 320+E*cos(A);

x2 = 320+E*cos(A+M_PI/5);

y1 = 240+E*sin(A);

y2 = 240+E*sin(A+M_PI/5);

Canvas->MoveTo(x1,y1);

Canvas->LineTo(x2,y2);

}

}

 

范例四的输出

 

XX-04

 

void __fastcall TForm1::FormPaint(TObject *Sender)

{

double A,x1,y1,x2,y2;

int D=80;

double E,F;

for (int i=0; i<960; i++)

{

A = i*M_PI/480;

E = D*(1+cos(20*A)/4);

F = E*(1+sin(4*A));

x1 = 320+F*cos(A);

x2 = 320+F*cos(A+M_PI/5);

y1 = 240-F*sin(A);

y2 = 240-F*sin(A+M_PI/5);

Canvas->MoveTo(x1,y1);

Canvas->LineTo(x2,y2);

}

}

 

以上四个范例程式是我先为你准备的开胃小菜,主要用以说明C++Builder Canvas绘图基本观念,同时也让你明了:利用几个简单的基本函式也可以做出美丽的电脑绘图。

 

XX-03 TCanvasHandle性质

 

在平时,你不太会有机会用到TCanvasHandle性质,它其实就是在Windows SDK绘图函式中都必须要用到的DC (Device Context)值。在Windows系统中,所有的绘图动作都必须透过DC来达成,

举例来说标准的SDK画线函式应该是这样的

 

BOOL LineTo(

HDC hdc, // device context handle

int nXEnd, // x-coordinate of line's ending point

int nYEnd // y-coordinate of line's ending point

);

因此在C++Builder中,使用Casvas->LineTo(x,y) 来绘图和呼叫标准SDK函式的LineTo((HDC)Canvas->Handle,x,y) 是一样的。

 

当然我不建议你在C++Builder中使用SDK语法来画图,但是我还是希望你对它们两者之间的关系有一些了解,因为C++BuilderVCL虽然在Canvas中已经把大部份的绘图函式实作出来,以物件的方式提供你使用,不过若是你要使用到Canvas未提供的绘图函式时,你就可以利用Canvas->Handle来做为传入SDK函式的参数。

 

XX-04 TCanvasTPen 性质

 

注:SDK加油站。

SDK中使用Pen的方式是利用SelectObject函式来达成。它传入两个参数,一个是HDC值,它就是Canvas->Handle值,另一个则是HGDIOBJ值,它是一些绘图工具的通称,以Pen而言,它就是HPEN值,同时也是Pen->Handle值。

 

HGDIOBJ SelectObject(

HDC hdc, // handle of device context

HGDIOBJ hgdiobj // handle of object

);

 

 

现在开始,我要为你一一介绍在TCanvas所使用的绘图工具。首先为你介绍的是TPen性质。在往下进行之前,我先简单说明C++ Builder的命名惯例,在C++Builder中,对於资料型别或是物件类别一般是以大写T 为启始字元,而对於物件本身则以不包含大写T的名称为名,如TCanvasTPen为类别名,而CanvasPen则是实际的物件,在本书中我会依情况交互使用之。

 

TPen是你在Canvas画线所使用的,因此所有和线条有关的绘图函式都会受TPen影响,如LineToEllipsePolygonPolyLineRectangle等函式都使用来画线,基本上我们可以将这些绘图工具归类为向量式的绘图工具,所有的向量式绘图工具都使用Pen来进行画线的动作。

 

你可以利用Canvas.Pen来存取Pen ,藉此修改Pen的性质,这些性质包含ColorWidthStyle,以及 Mode。你可以修改上述性质来达到改变线条颜色及样式的目的。

 

XX-04-01 Color性质

 

Color性质可以定出笔的颜色。在C++ Builder中提供了许多颜色的预定常数,这些预设颜色都以clcl代表color)为启始字元命名。例如clRed代表红色,clBlue代表蓝色,clGreen代表绿色等等。另外C++ Builder也将Windows的基本颜色以常数定义,如clWindowclMenu分别代表WindowMenu的颜色。以下我列出部份C++ Builder定义的颜色常数,你不必完全记住它,只要有些印象即可,若是无法记住,可以直接使用线上辅助说明,不过记住一些常用的常数会加快你程式写作的效率。

 

颜色常数说明

意义

clBlack 黑色

clMaroon 茶色

clGreen 绿色

clOlive 橄榄绿

clNavy 海蓝色

clPurple 紫色

clTeal 青紫色

clGray 灰色

clSilver 银色

clRed 红色

clLime 灰绿色

clBlue 蓝色

clFuchsia

clAqua 淡绿青色

clWhite 白色

clBackground Window背景色

clActiveCaption 活动视窗的标题色

clInactiveCaption 非活动视窗的标题色

clMenu Menu的颜色

clWindow Windows的背景色

clWindowFrame Window Frame的颜色

clMenuText Menu文字的颜色

clWindowText Window内文字的颜色

clCaptionText 标题文字的颜色

clActiveBorder 活动视窗的边界颜色

clInactiveBorder 非活动视窗的边界颜色

clAppWorkSpace 视窗工作区域的颜色

clHighlight 高亮度Windows颜色

clHightlightText 被选取文字的颜

clBtnFace Button颜色

clBtnShadow Button影子颜色

clGrayText 灰色文字色

clBtnText Button内文字颜

clBtnHighlight Button高亮度颜色

 

以下的程式会将画笔的颜色设为蓝色

Canvas->Pen->Color=clBlue;

 

另外我们也可以利用以下的叙述来改变颜色

 

Canvas->Pen->Color = TColor(RGB(192,192,192));

 

注:RGBWindows系统用以表示颜色的方式,它们分别代表红色(Red)、绿色(Green)及蓝色(Blue)。使用RGB时我们可以传入分别代表红、绿、蓝叁种颜色的强度值,此时它会传回Windows系统的对应值。例如RGB(255,0,0) 的传回值是代表红色,所以Tcolor(RGB(255,0,0)) 所代表的意义和clRed是相同的。你也可以利用ColorToRGB来将clRedC++ Builder定义的常数值传换成Windows系统所代表的RGB颜色。

 

XX-04-02 Style性质

 

Style性质是用来改变画笔的型式,在C++ Builder中定义了以下几种画笔型式。它们都以ps为启始字串 (ps代表Pen Style)

 

psClear 清除线

psDash Dash线

psDashDot DashDot交替线

psDashDotDot Dash和一堆Dot线

psSolid 实心线

 

XX-05

 

XX-04-03 Width性质

 

笔的Width顾名思义,就是指笔的粗细,我在前面几个程式都已使用过了,所以在此不再细述。

 

XX-04-04 Mode性质

 

PenMode性质是用以决定Pen如何画在Canvas上,下表列出我们可以使用的Mode,它们都以pm为启始字元,代表Pen Mode

 

Mode 点的颜色

pmBlack 恒为黑色

pmWhite 恒为白色.

pmNop 不变色。

pmNot 萤幕的反相色。

pmCopy 使用Color性质的颜色(内定值).

pmNotCopy 使用Color性质的反相色。

pmMergePenNot Color和萤幕反相的 Merge

pmMaskPenNot Color和萤幕反相的 Mask

pmMergeNotPen Color反相和萤幕的 Merge

pmMaskNotPen Color反相和萤幕的 Mask

pmMerge Color和萤幕的 Merge

pmNotMerge pmMerget的相反。

pmMask Color和萤幕的 Mask

pmNotMask pmMask的反相。

pmXor Color和萤幕的Xor运算。

pmNotXor pmXor的相反。

 

Pen->Mode的预设值是pmCopy,也就是说,它会用Color性质的颜色来画图。在大部份情况下,你会使用这个预设值。不过若你要再针对笔做细部控制时,你就必须对其他Mode有一些初步的了解。

 

另一个常用的Mode pmXor,它是用来让Pen->Color和萤幕颜色做XOR运算,XOR运算最重要的特徵是:和同一个值做两次XOR运算时,会恢复原来的值。

因此我们可以用XOR模式在同一位置画两次线,将原来的线擦掉。在本书的绘图范例中就使用pmXor来处理滑鼠的画线,用以将上次的线条清除掉。同时XOR运算也是一种最简单的编码演算法,不过这不在本书讨论范围内,因此我就不再多说了。

 

XX-05 TCanvasTBrush性质

 

TBrush可用来在Canvas的特定区域下着色。和TPen不同的是,TBrush可以用不同的颜色、样式、及图案来填满Canvas的特定区域,而TPen则是用来在Canvas上绘线。

 

XX-04-01 Style性质

 

TBrush共有bsSolid, bsClear, bsHorizontal, bsVertical, bsFDiagonal, bsBDiagonal, bsCross, bsDiagCross等几种 Style,你可以由图XX-06看出这几种不同的笔刷型式有何不同。

 

XX-06

 

XX-05 TCanvasPixels性质

 

Pixel性质,是一个二维的颜色阵列,它让你可以直接存取Canvas内的任何一点。你可以利用和一般阵列相同的存取方法来取得Pixel内的值。此阵列的最大索引值是X,Y的值。

 

除非必要,否则不要以Pixel来绘图,它是最慢的绘图方法。

 

XX-06 TCanvasFont性质

 

Font性质是用来控制画在Canvas上的文字所使用的字形。你可以利用改变 ColorNameSizeStyle的方式来分别改变字形的颜色、使用的字形名称、字形大小及字形的样式。

 

XX-06-1 TFontColorNameSize性质。

 

TFontColor性质和其他元件的Color性质相同。

Name性质表示使用字形的名称。你可以用以下的方式来设定字形名称。

Canvas->Font->Name=”标楷体“;

Size性质表示字形的大小。

 

XX-06-02 TFontStyle性质。

 

Style性质用以表示字型的样式。它包含以下几种样式:

 

fsBold 粗体

fsItalic 斜体

fsUnderline 底线

fsStrikeOut 穿越文字的水平直线。

 

<<< 以下待续 >>>