VC.NET的GDI+编程入门教程之图形
基于直线的图形
一、等边图形
(一)长方形和正方形
长方形是由四条边组成的具有四个直角的几何图形,为了绘制一个长方形,可以定义围成长方形的矩形值,或定义它的位置和尺寸。为了画一个矩形围成的长方形,可以使用Graphics::DrawRectangle()方法。
public: void DrawRectangle(Pen *pen, Rectangle rect); |
类似的长方形可以按照如下说明:
图一、长方形说明图示 |
定义过一个矩形变量后,可以将它传递给上述的方法,例子代码如下:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Red); Rectangle Rect(20, 20, 248, 162); e->Graphics->DrawRectangle(penCurrent, Rect); } |
需要注意的是,也可以在方法的括号内定义画笔或矩形对象。
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { e->Graphics->DrawRectangle(new Pen(Color::Red), Rectangle(20, 20, 248, 162)); } |
这将产生如下效果图:
图二、绘制的长方形效果图 |
一定要记住,矩形对象的第三个参数代表的是矩形的宽度,第四个参数代表的矩形的高度,这对于那些使用过GDI编程的人来说是容易混淆的
一点。GDI+定义的矩形对象与GDI定义的矩形对象是有区别的。实际上,为了定义所要画的长方形的位置和尺寸,Graphics类提供了如下版本的
DrawRectangle()方法:
public: void DrawRectangle(Pen *pen, int x, int y, int width, int height); public: void DrawRectangle(Pen *pen, float x, float y, float width, float height); |
这次,长方形对象用一个定位点和它的宽度、高度来表示。这可以用如下的Windows坐标系统进行说明。
图三、Windows坐标系统 |
在此基础上,上述的长方形可以按照如下方法进行绘制:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { e->Graphics->DrawRectangle(new Pen(Color::Red), 20, 20, 248, 162); } |
正方形是四个边都相等的长方形,是长方形的特例。
(二)一系列的长方形
DrawRectangle()方法用于绘制一个长方形,如果打算绘制很多的矩形的话,你可以向前一步地,用Graphics::DrawRectangles()方法,它有两个版本,语法如下:
public: void DrawRectangles(Pen *pen, Rectangle rects[]); public: void DrawRectangles(Pen *pen, RectangleF rects[]); |
这个方法需要一个Rectangle 或 RectangleF数组。它根据数组的不同的成员值绘制不同的长方形。下面是一个例子代码:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Red); Rectangle Rect[] = { Rectangle(20, 20, 120, 20), Rectangle(20, 50, 120, 30), Rectangle(20, 90, 120, 40), Rectangle(20, 140, 120, 60) }; e->Graphics->DrawRectangles(penCurrent, Rect); } |
上述代码产生如下的效果:
图四、一系列长方形效果图 |
二、线条
(一)一条直线
直线连接了两个点,这意味着直线有起点和终点。
图五、直线示意图 |
起点和终点是截然不同的两个点,正是基于这一点,直线也可以用两个点(Ponit)来表示,或者用笛卡尔坐标系中的四个坐标数值表示。Graphics提供了以下重载的DrawLine()方法来绘制一条直线,:
public: void DrawLine(Pen *pen, Point pt1, Point pt2); public: void DrawLine(Pen *pen, PointF pt1, PointF pt2); public: void DrawLine(Pen *pen, int x1, int y1, int x2, int y2); public: void DrawLine(Pen *pen, float x1, float y1, float x2, float y2); |
如果直线用自然数表示,它的起点可以用pt1表示,终点用点pt2表示,如果直线用实数绘制,它在PointF
pt1处开始,在PointF pt2处结束。或者,可以用坐标(x1, y1)来表示起点,用坐标(x2,
y2)表示终点。同样类型的直线可以用十进制数从点(x1, y1) 处到点 (x2, y2).处。
下面的代码画了三条直线:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Red); e->Graphics->DrawLine(penCurrent, 20, 20, 205, 20); penCurrent = new Pen(Color::Green); e->Graphics->DrawLine(penCurrent, 40, 40, 225, 40); penCurrent = new Pen(Color::Blue); e->Graphics->DrawLine(penCurrent, 30, 60, 215, 60); } |
图六:绘制三条直线 |
(二)一系列直线
上述的DrawLine()方法用来画一条直线,如果打算一次画一组直线的话,可以使用Graphics::DrawLines()方法,它重载了两个版本:
public: void DrawLines(Pen *pen, Point points[]); public: void DrawLines(Pen *pen, PointF points[]); |
为了使用这种方法,需要使用代表笛卡尔坐标的自然数定义Point数组,或只用实数定义PointF数组,例子代码如下:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Point Coordinates[] = { Point(20, 10), Point(205, 20), Point(40, 40), Point(225, 60), Point(30, 80), Point(215, 100) }; Pen *penCurrent = new Pen(Color::Red); e->Graphics->DrawLines(penCurrent, Coordinates); } |
这将产生如下的效果:
图七、系列直线效果图 |
三、多边形 多边形是如若干个直线互联所围成的图形,换句话说,多边形有多个直线定义,除了第一根直线外,所有直线的起点都是前一根直线的终点,最后一根直线的终点是第一根直线的起点。 为了画多边形,可以使用Graphics::Polygon()方法,它重载了两个版本:
使用这个方法时,首先声明一个Point 或 PointF类型的数组,并将它传递给函数的第二个参数。下面是一个例子的代码:
这个将产生如下效果:
|
基于圆的图形
一、椭圆与圆
连续弯曲的线条围成了椭圆。椭圆上的每一个点都相对于中心点来说都存在一个对称点,下图对它进行了说明:
图九、椭圆示意图 |
因为一个椭圆可以放入到一个矩形中,所以在GDI+编程中,椭圆用它的外接矩形来定义。为了画一个椭圆,可以使用Graphics::DrawEllipse()方法,这个方法有四个版本:
public: void DrawEllipse(Pen *pen, Rectangle rect); public: void DrawEllipse(Pen *pen, RectangleF rect); public: void DrawEllipse(Pen *pen, int x, int y, int width, int height); public: void DrawEllipse(Pen *pen, float x, float y, float width, float height); |
图十、函数参数示意图 |
这种方法参数的含义与Graphics::DrawRectangle()方法参数的含义是一样的。
这里是一个例子:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Red); e->Graphics->DrawEllipse(penCurrent, Rectangle(20, 20, 226, 144)); } |
图十一、代码运行效果图 |
二、饼图
饼图是用一个起始角度和终止角度定位的椭圆的一部分,示意图如下:
图十二、饼图示意图 |
为了画一个饼图,可以用Graphics::DrawPie()方法,它有下列的几个版本:
public: void DrawPie(Pen *pen, Rectangle rect,float startAngle,float sweepAngle); public: void DrawPie(Pen *pen, RectangleF rect, float startAngle,float sweepAngle); public: void DrawPie(Pen *pen,int x,int y,int width,int height, int startAngle, int sweepAngle); public: void DrawPie(Pen *pen, float x, float y, float width, float height, float startAngle, float sweepAngle); |
饼图是基于椭圆的,椭圆所外接的矩形将作为rect参数传递,矩形也可以用定位点和尺寸来定义。
对于所要绘制的椭圆的外接矩形中,可以设定一个起始角度,这个角度是按照顺时针方向从0度开始计算的(就象一个模拟钟一样)。这意味着90度在6点钟方向而不是在12点钟方向。这个开始的角度作为startAngle参数来传递。
定义过起始角度后,还要定义饼图所覆盖的角度,这也是按照顺时针计算的。这个值使用sweepAngle参数来传递。
下面有个例子:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Red); e->Graphics->DrawPie(penCurrent, 20, 20, 200, 100, 45, 255); } |
代码运行效果如下:
图十三、饼图效果 |
三、弧线
弧线是椭圆的一部分,这意味着弧线是一个非封闭的椭圆。尽管饼图是一个封闭的图形,但弧线不是。它仅仅是定义椭圆的边线部分。因为弧线必须与椭圆一致,它被定义为适应外接矩形,这可以用下图来说明:
图十四、弧线示意图 |
为了支持弧线,Graphics 类提供了DrawArc()方法,这个方法定义了四个版本:
public: void DrawArc(Pen *pen, Rectangle rect, float startAngle, float sweepAngle); public: void DrawArc(Pen *pen, RectangleF rect, float startAngle, float sweepAngle); public: void DrawArc(Pen *pen, int x, int y, int width, int height, int startAngle, int sweepAngle); public: void DrawArc(Pen *pen, float x, float y, float width, float height, float startAngle, float sweepAngle); |
含有弧线的椭圆必须在Rectangle或
RectangleF矩形内进行绘制,也可以用外接矩形的坐标、尺寸来定义椭圆,弧线必须与外接矩形相匹配外,还必须定义起始角度(起始点与X轴的顺时针
角度)。弧线还要定义从起始点顺时针所扫过的角度,这两个值与Graphics::Pie()方法中的值含义一样 ,下面是例子代码:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Red); e->Graphics->DrawArc(penCurrent, 20, 20, 200, 150, 225, 200); } |
图十五、程序效果图 |
基于曲线的图形
一、曲线
曲线是连接两点或多点的线条,如果仅仅涉及两个点,线条将把它们连接在一起,但这个线条不是直的。如果有三个点A、B、C,这条线将从A点开始,穿过B
点,结束于C点。如果多于三个点,这条线将起始与第一个点,依次穿过中间的若干点,结束于最后的点。曲线中的点不必排成一条直线,实际上,绘制曲线的整体
思想是用非直的线条连接不在一条直线上的点,下图的C 1、C2、C3可以说明这一点:
图十六、曲线示意图 |
曲线C1包括两个点,曲线C2包括3个点,曲线C3包括4个点。
两点间的部分称为线段。这也意味着曲线可以由包含
的线段的个数来进行区分。如果一个曲线仅仅由两个点构成,这意味着它只有一个线段,连接着第一、第二个点,如果一个曲线包括三个点,它有两个线段,第一个
线段跨越第一、第二个点,第二个线段跨越第二、第三个点。综上所述,曲线的线段数等于点数减去一。
GDI+中使用Graphics::DrawCurve()方法画曲线,在画一个曲线时,必须说明所涉及到的点数,这意味着首先要声明一个Point 或PointF数组,正是考虑到这一点,Graphics类定义的DrawCurve()的方法如下:
public: void DrawCurve(Pen *pen, Point points[]); public: void DrawCurve(Pen *pen, PointF points[]); |
这个版本的方法使用了Point 或 PointF值作为参数,数组的成员数由你规定,这里有一个例子使用四个点来绘制一个含有三个线段的曲线:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Blue); Point pt[] = { Point( 40, 42), Point(188, 246), Point(484, 192), Point(350, 48) }; e->Graphics->DrawCurve(penCurrent, pt); } |
它的效果图如下所示:
图十七、曲线效果图 |
正如你所见到的,当画一个曲线时,一个弯曲的线条穿越起始点和终点之间的中间点,为了使线条是非直的,编译器使用一个称为张力的值来弯曲线条,这个张力值可以通过定义弯曲系数的形式来修改,为了这么做,使用下列版本的DrawCurve()方法:
public: void DrawCurve(Pen *pen, Point points[], float tension); public: void DrawCurve(Pen *pen, PointF points[], float tension); |
弯曲程度由张力参数决定,它可以是大于等于0.00的十进制数,如果该值等于0.00,将画一个直线。下面是一个例子:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Blue); Point pt[] = { Point(40, 42), Point(188, 246),Point(484, 192), Point(350, 48) }; e->Graphics->DrawCurve(penCurrent, pt, 0.00F); } |
图十八、代码运行效果图 |
这意味着,如果想画一个真正的曲线,或者是不传递张力参数,使用这个方法的第一个版本,或者是传递的张力的参数值大于0.00。这里有个例子:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Blue); Point pt[] = { Point(40, 42), Point(188, 246),Point(484, 192), Point(350, 48) }; e->Graphics->DrawCurve(penCurrent, pt, 2.15F); } |
这将产生如下效果:
图十九、代码运行效果图 |
DrawCurve()方法的两个版本准许在开始的第一个点绘制曲线,下面的例子使用了5个点,产生了4个线段:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Blue); PointF pt[] = { PointF(20.00F, 322.00F), PointF(124, 24),PointF(214, 242), PointF(275, 28), PointF(380.00F, 322.00F) }; e->Graphics->DrawCurve(penCurrent, pt); } |
效果如图所示:
图二十、代码运行效果图 |
如果需要的话,可以随意在任意一个点开始曲线,为了支持这一点,Graphics类提供了以下版本的DrawCurve()方法:
public: void DrawCurve(Pen *pen, PointF[] points, int offset, int numberOfSegments); |
offset参数用来规定在开始绘制之前跳需要跃过的点数,首先需要决定的就是offset参数必须设置为0或者更高的数,如果将这个
参数设置为0,曲线将从第一个点开始绘制。如果这个参数设置为1,第一个点将不包含在曲线之内。这意味着曲线从第二个点开始绘制,以此类推。如果设置为
2,第一和第二个点都不在曲线内,曲线从第三个点开始。
通过offset参数规定曲线的起点后,然后就需要设定曲线的段数,这个段数必须要小于可用的段数,它等于所有段落数减去offset值。下面是一个例子:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Blue); PointF pt[] = { PointF(20.00F, 322.00F), PointF(124, 24),PointF(214, 242), PointF(275, 28), PointF(380.00F, 322.00F) }; e->Graphics->DrawCurve(penCurrent, pt, 1, 2); } |
效果图如下:
图二十一、代码运行效果图 |
再一次,编译器在绘制曲线时需要申请张力,如果愿意使用一个直线段或使用一个不同于默认值的张力的话,可以使用下列版本的Graphics::DrawCurve()方法:
public: void DrawCurve(Pen *pen, Point[] points, int offset, int numberOfSegments, float tension); public: void DrawCurve(Pen *pen, PointF[] points, int offset, int numberOfSegments, float tension); |
这次,你可以传递0值给张力,以此来得到直线,代码如下:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Blue); PointF pt[] = { PointF(20.00F, 322.00F), PointF(124, 24),PointF(214, 242), PointF(275, 28),PointF(380.00F, 322.00F) }; e->Graphics->DrawCurve(penCurrent, pt, 0, 4, 0); } |
效果如图:
图二十二、代码运行效果图 |
也可以向张力参数传递任何正值,这有个例子:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Blue); PointF pt[] = { PointF(20.00F, 322.00F), PointF(124, 24), PointF(214, 242), PointF(275, 28), PointF(380.00F, 322.00F) }; e->Graphics->DrawCurve(penCurrent, pt, 1, 3, 1.750F); } |
效果如图:
图二十三、代码运行效果图 |
二、贝赛尔曲线
贝赛尔曲线是用四个点(不必在一条直线上)绘制的连续曲线,它可以用下图来说明:
图二十四、贝赛尔曲线 |
为了绘制这个线条(使用四个点),编译器将从第一点到第四个点画一条曲线,但是它并不经过第二、第三个点,而只是通过弯曲曲线来使中间的侧边各自接近于第二、第三个点。例如,上述的贝赛尔曲线使用了如下的四个点进行绘制:
图二十五、贝赛尔曲线绘制说明图 |
为了绘制贝赛尔曲线,Graphics类提供了DrawBezier()方法,它重载了以下版本:
public: void DrawBezier(Pen *pen, Point pt1, Point pt2, Point pt3, Point pt4); public: void DrawBezier(Pen *pen, PointF pt1, PointF pt2, PointF pt3, PointF pt4); public: void DrawBezier(Pen *pen, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4); |
在此基础上,绘制贝赛尔曲线时可以使用四个Point 或PointF值,也可以使用四个点的坐标值。下面有一个例子:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Blue); Point pt1 = Point(20, 12), pt2 = Point(88, 246), pt3 = Point(364, 192), pt4 = Point(250, 48); e->Graphics->DrawBezier(penCurrent, pt1, pt2, pt3, pt4); } |
效果图如下:
图二十六、贝赛尔曲线效果图 |
三、一系列贝赛尔曲线
Graphics::DrawBezier()方法用来绘制一条贝赛尔曲线,如果想绘制一系列贝赛尔曲线,可以用Graphics::DrawBeziers()方法,它重载了两个版本:
public: void DrawBeziers(Pen *pen, Point points[]); public: void DrawBeziers(Pen *pen, PointF points[]); |
DrawBeziers()方法需要一个Point 或 PointF数组值。当仅仅处理四个点时,DrawBeziers()
方法与 DrawBezier()很相似。区别是DrawBezier()处理的是四个Point 或
PointF的值,DrawBeziers()处理的是Point 或
PointF数组值。使用DrawBeziers()方法可以绘制出与上面曲线一样的效果,代码如下:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Blue); Point pt[] = { Point(20, 12), Point(88, 246), Point(364, 192), Point(250, 48) }; e->Graphics->DrawBeziers(penCurrent, pt); } |
使用DrawBeziers()方法的一个典型特点是它允许使用7个Point或PointF值,这里有一个例子:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Blue); Point pt[] = { Point( 10, 5), Point(340, 60), Point(320, 148), Point(150, 120), Point(24, 220), Point(250, 150), Point(304, 240) }; e->Graphics->DrawBeziers(penCurrent, pt); } |
效果图如下:
图二十七、代码运行效果图 |
四、封闭曲线
使用
DrawLines()、DrawBezier()或
DrawBeziers()方法将得到一条或一系列有起点有终点的直线或曲线,GDI+也允许绘制一系列的线段,但是最后的线段的终点和第一条线段的起点
是连接在一起的,形成了一个封闭的图形。为了绘制这种图形,可以使用Graphics::DrawClosedCurve()方法,该方法重载了四个版
本,其中的两个版本语法如下:
public: void DrawClosedCurve(Pen *pen, Point points[]); public: void DrawClosedCurve(Pen *pen, PointF points[]); |
这两个版本非常容易使用,它们允许你提供4个Point 或PointF值的数组,在执行过程中,每一种版本都将绘制一条曲线,穿过每一个点,并连接终点和起点,这里有个例子:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { Pen *penCurrent = new Pen(Color::Blue); Point pt[] = { Point(40, 42), Point(188, 246), Point(484, 192), Point(350, 48) }; e->Graphics->DrawClosedCurve(penCurrent, pt); } |
代码运行效果如下图:
图二十八、代码运行效果图 |
上两个版本用来绘制线条,但对线条进行弯曲处理以使图形看起来平滑,如果需要的话,可以画直线来连接各点,而不必进行弯曲处理。基于这种假定,上面的形状将显示如下:
图二十九、直线连接的封闭图形 |
为了绘制这种类型的图形,需要使用ClosedCurve()方法,可以使用以下版本:
public: void DrawClosedCurve(Pen *pen, Point points[], float tension, FillMode fillmode); public: void DrawClosedCurve(Pen *pen, PointF points[], float tension, FillMode fillmode); |
这些版本可以规定张力大小及填充模式,张力系数使你可以定义曲线的实际形状,如果这个值是零的话,各个点将用直线进行互联。填充模式因数用来定义内部的
曲线如何来填充,这由FillMode枚举来进行控制,它定义在System::Drawing::Drawing2D命名空间中。枚举FillMode
有两个值,Alternate 和Winding,这里有一个例子:
private: System::Void Form1_Paint(System::Object * sender, System::Windows::Forms::PaintEventArgs * e) { using namespace System::Drawing::Drawing2D; Pen *penCurrent = new Pen(Color::Red); Point pt[] = { Point(40, 42), Point(188, 246), Point(484, 192), Point(350, 48) }; e->Graphics->DrawClosedCurve(penCurrent, pt, 0.75F, FillMode::Winding); } |
这将产生影响如下效果图:
图三十、代码运行效果图 |