## 1.6 图形类 Graphics
与 GDI 的 MFC 类 CDC 类似,GDI+的绘图功能主要由图形类 Graphics 承担。
图形类 Graphics 是 GDI+的核心,它提供绘制图形、图像和文本的各种方法(似 GDI 中的 CDC 类),还可以存储显示设备和被画项目的属性(到图元文件)。Graphics 类及其方 法都被定义在头文件 Gdiplusgraphics.h 中。
![image](https://box.kancloud.cn/2016-04-18_57144a7e15a2e.jpg)
![image](https://box.kancloud.cn/2016-04-18_57144a7e3300b.jpg)
图 14-12 颜色枚举常量
### 1.6.1 构造函数
Graphics 类的构造函数有如下 4 种:
```
Graphics(Image* image); // 用于绘制图像
Graphics(HDC hdc); // 用于在当前窗口中绘图
Graphics(HDC hdc, HANDLE hdevice); // 用于在制定设备上绘制图形
Graphics(HWND hwnd, BOOL icm = FALSE); // 用于在指定窗口中绘图
```
其中,最常用的是第二种——在当前视图窗口中绘图的图形类构造函数。
注意,该构造函数的输入参数,是设备上下文的句柄,而不是 CDC 类对象的指针。一 般可以由 CDC 对象得到(因 CDC 类含有公用数据成员 HDC m_hDC;):
+ 在 OnDraw 函数中,利用输入参数 CDC *pDC,就可直接得到 DC 句柄。例如:
```
Graphics graph(pDC->m_hDC);
```
+ 在视图类的其他函数中,可先利用 GetDC 函数得到 CDC 指针,然后再利用它去获 取 DC 的句柄。例如:
```
Graphics graph(GetDC()->m_hDC);
```
也可以使用 Graphics 类的另一个构造函数 `Graphics(HWND hwnd, BOOL icm = FALSE);`,利用视图类的窗口句柄成员来构造 Graphics 对象。例如:
```
Graphics graph(this->m_hWnd);
```
### 1.6.2 状态枚举 status
在图形类 Graphics 中,封装了各种绘图方法。每种绘图方法被调用后,都会返回一种 叫做 status 的枚举值,反映该方法是否被正确执行,0 表示正确,其他大于 0 的值为错误代 码(GdiplusTypes.h):
```
typedef enum { // 状态枚举(含 22 个枚举值)
Ok = 0,
GenericError = 1,
InvalidParameter = 2,
OutOfMemory = 3,
...
PropertyNotSupported = 20,
ProfileNotFound = 21
} Status;
```
GDI+的绘图功能被封装在图形类 Graphics 中,下面介绍其中的常用绘图方法。先讲绘 制线型图的方法,再讲绘制填充图的方法,最后讲绘制文字的方法。
### 1.6.3 画线型图的方法
GDI+中绘制线型图形的方法与 GDI 的类似,也包括绘直线、矩形、椭圆和多边形等, 但是 GDI+增加了浮点版本和若干新功能。GDI+的画线函数都是 Graphics 类的方法,而且所 有方法的名称都是以 Draw 开头。
(1)画直线[折线]DrawLine[s]
在 GDI+中定义了 6 种绘制直线和折线的方法,前三个为整数版,后三个为对应的浮点 数版:
```
Status DrawLine(const Pen* pen, INT x1, INT y1, INT x2, INT y2);
Status DrawLine(const Pen* pen, const Point& pt1, const Point& pt2);
Status DrawLines(const Pen* pen, const Point* points, INT count);
Status DrawLine(const Pen* pen, REAL x1, REAL y1, REAL x2, REAL y2);
Status DrawLine(const Pen* pen, const PointF& pt1, const PointF& pt2);
Status DrawLines(const Pen* pen, const PointF* points, INT count);
```
其中:
+ DrawLine——画直线(4 个重载),参数 pen 为画直线所用的笔、(x1, y1)和 pt1 为 直线的起点、(x2, y2)和 pt2 为直线的终点。GDI 的相应函数为 MoveTo 和 LineTo。
+ DrawLines——画折线(一串相互连接的直线段)(2 个重载),参数 points 为点数 组、count 为数组中点的数目。GDI 的相应函数为 Polyline。
(2)画矩形[组] DrawRectangle[s]
在 GDI+中也定义了 6 种绘制矩形和矩形组的方法,也是前三个为整数版,后三个为对 应的浮点数版:
```
Status DrawRectangle(const Pen* pen, const Rect& rect);
Status DrawRectangle(const Pen* pen, INT x, INT y, INT width, INT height);
Status DrawRectangles(const Pen* pen, const Rect* rects, INT count);
Status DrawRectangle(const Pen* pen, const RectF& rect);
Status DrawRectangle(const Pen* pen, REAL x, REAL y, REAL width, REAL height);
Status DrawRectangles(const Pen* pen, const RectF* rects, INT count);
```
其中:
+ DrawRectangle——画单个矩形(4 个重载),参数 pen 为画矩形所用的笔、rect 为 矩形区域、(x, y)为矩形的左上角、(width, height)为矩形的大小(宽,高)。与 GDI 的对应函数 BOOL Rectangle( int x1, int y1, int x2, int y2);的区别主要是 GDI+的第 2 个和第 4 个画矩形方法的后两个输入参数,不再是 GDI 中的矩形右下角的坐标, 而改成矩形的宽和高了。
+ DrawRectangles——画多个矩形(2 个重载),参数 rects 为矩形数组、count 为数组 中矩形的数目。GDI 中没有同时绘制一个矩形数组的函数。
(3)[椭]圆 DrawEllipse
GDI+中有 4 个重载的绘制椭圆的方法,如果输入参数所确定的外接矩形的宽高相等, 则画圆。也是前两个为整数版,后两个为对应的浮点数版:
```
Status DrawEllipse(const Pen* pen, const Rect& rect);
Status DrawEllipse(const Pen* pen, INT x, INT y, INT width, INT height) Status DrawEllipse(const Pen* pen, const RectF& rect);
Status DrawEllipse(const Pen* pen, REAL x, REAL y, REAL width, REAL height);
```
这些方法的功能,与 GDI 中的函数:
```
BOOL Ellipse( int x1, int y1, int x2, int y2 );
```
图 14-13 画弧方法的输入参数
类似,但是同样要注意 GDI+的 DrawEllipse 方法与 GDI 的 Ellipse 函数的主要区别(与画矩 形的方法与函数类似),是上面的以坐标为参数的第 2、4 个 GDI+画椭圆方法的后两个输入 参数,也是矩形的宽高而不再是矩形的右下角坐标了。
(4)画[椭]圆弧 DrawArc
GDI+中也有 4 个重载的绘制椭圆弧的方法,如果输入参数所确定的外接矩形的宽高相 等,则画圆弧。也是前两个为整数版,后两个为对应的浮点数版。
![image](https://box.kancloud.cn/2016-04-18_57144a7e4bcac.jpg)
```
Status DrawArc(const Pen* pen, INT x, INT y, INT width, INT height, REAL startAngle, REAL sweepAngle);
Status DrawArc(const Pen* pen, const Rect& rect, REAL startAngle, REAL sweepAngle);
Status DrawArc(const Pen* pen, REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle);
Status DrawArc(const Pen* pen, const RectF& rect, REAL startAngle, REAL sweepAngle);
```
注意,角度的单位是度(不是弧度,C++的三角函数 采用的是弧度单位),而且都必须是实数。零度角为 x 轴 方向,顺时针方向为正(这与数学上反时针方向为正刚好相反),参见图 14-13。
(5)画多边形 DrawPolygon
GDI+中有 2 个重载的绘制多边形的方法,前一个为整数版,后一个为对应的浮点数版:
```
Status DrawPolygon(const Pen* pen, const Point* points, INT count);
Status DrawPolygon(const Pen* pen, const PointF* points, INT count);
```
其中,各参数的含义同画折线方法 DrawLines 的,只是 DrawPolygon 方法会将点数组中的起点和终点连接起来,形成一个封闭的多边形区域。 该方法的功能与 GDI 的 Polygon 函数相同:
```
BOOL Polygon( LPPOINT lpPoints, int nCount );
```
注意:GDI+中没有提供与 GDI 函数 RoundRect(圆角矩形)和 Chord(弓弦)具有类 似功能的绘图方法,但可以利用矩形+椭圆和弧+直线等方法来自己实现。
### 1.6.4 画填充图的方法
在 GDI 中,任何画封闭区域的性状图绘制函数(如矩形、圆角矩形、[椭]圆、弓弦和多 边形等),都可以画填充图,因为它们总是在用当前笔画指定边框的同时,也用当前刷子填 充内部区域。
而 GDI+的画线方法就没有这个功能,因为在 GDI+是无状态的,没有当前笔和刷的概 念。为了完成与这些 GDI 函数类似的功能,在 GDI+中,你得分两步来做:先用填充方法填 充区域内部,再用画线方法绘制边框。
在 GDI+中画填充图,不需像 GDI 那样得先将刷子选入 DC,而是与 GDI+画线状图的 方法类似,将刷子作为画填充图方法的第一个输入参数。注意,GDI+中的画填充图的方法 都以 Fill 开头。
(1) 画填充矩形[组]FillRectangle[s]
GDI+中有 6 个重载的绘制填充矩形[组]的方法,前 3 个为整数版,后 3 个为对应的浮点 数版:
```
Status FillRectangle(const Brush* brush, const Rect& rect);
Status FillRectangle(const Brush* brush, INT x, INT y, INT width, INT height);
Status FillRectangles(const Brush* brush, const Rect* rects, INT count);
Status FillRectangle(const Brush* brush, const RectF& rect);
Status FillRectangle(const Brush* brush, REAL x, REAL y, REAL width, REAL height);
Status FillRectangles(const Brush* brush, const RectF* rects, INT count);
```
用指定刷子 Brush,填充 rect 的内部区域,无边线,填充区域包括矩形的左边界和上边 界,但不包括矩形的右边界和下边界。功能与 GDI 的 FillRect 函数类似:
```
void FillRect( LPCRECT lpRect, CBrush* pBrush );
```
但是,GDI 中没有同时填充一个矩形数组的函数。不过 GDI 却有 GDI+中所没有的画填充圆 角矩形的函数 FillSolidRect。
(2) 画填充椭圆 FillEllipse
GDI+中有 4 个重载的绘制填充椭圆的方法,前 2 个为整数版,后 2 个为浮点数版:
```
Status FillEllipse(const Brush* brush, const Rect& rect);
Status FillEllipse(const Brush* brush, INT x, INT y, INT width, INT height);
Status FillEllipse(const Brush* brush, const RectF& rect);
Status FillEllipse(const Brush* brush, REAL x, REAL y, REAL width, REAL height);
```
GDI 中没有类似函数,但可以用(采用当前刷填充的)Ellipse 函数来代替。
(3) 画饼图 DrawPie
GDI+中有 4 个重载的绘制饼图的方法,前 2 个为整数版,后 2 个为浮点数版:
```
Status DrawPie(const Pen* pen, const Rect& rect, REAL startAngle, REAL sweepAngle);
Status DrawPie(const Pen* pen, INT x, INT y, INT width, INT height, REAL startAngle, REAL sweepAngle);
Status DrawPie(const Pen* pen, const RectF& rect, REAL startAngle, REAL sweepAngle);
Status DrawPie(const Pen* pen, REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle);
```
与 GDI 的下列函数类似,但是部分输入参数的含义有所不同:
```
BOOL Pie( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 );
BOOL Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd );
```
例如(参见图 14-14):
![image](https://box.kancloud.cn/2016-04-18_57144a7e5d0ef.jpg)
```
void DrawPies(Graphics &graph, const Color cols[], Point &O, int r, const float data[], int n) {
Rect rect(O.X - r, O.Y - r, 2 * r, 2 * r);
float startAngle = 0, sweepAngle;
for (int i = 0; i < n; i++) {
sweepAngle = data[i] * 360.0f;
graph.FillPie(&SolidBrush(cols[i]), rect, startAngle, sweepAngle);
startAngle += sweepAngle;
}
}
void CGdipDrawView::OnDraw(CDC* pDC) {
……
Graphics graph(pDC->m_hDC);
Color cols[] = {Color::Red, Color::Green, Color::Blue, Color::Aqua};
float data[] = {0.2f, 0.4f, 0.1f, 0.3f};
DrawPies(graph, cols, Point(200, 200), 100, data, 4);
……
}
```
(4) 画填充多边形 FillPolygon
GDI+中有 4 个重载的绘制填充多边形的方法,前 2 个为整数版,后 2 个为浮点数版:
```
Status FillPolygonconst Brush* brush, const Point* points, INT count);
Status FillPolygon(const Brush* brush, const Point* points, INT count, FillMode fillMode);
Status FillPolygon(const Brush* brush, const PointF* points, INT count);
Status FillPolygon(const Brush* brush, const PointF* points, INT count, FillMode fillMode);
```
其中,填充模式参数 FillMode,可取如下两个值之一(参见 8.5.3 中的 1.):
```
typedef enum {
FillModeAlternate, // 交替模式——按奇偶规则填充(默认模式)
FillModeWinding // 环绕模式——按非零环绕规则填充
} FillMode;
```
对简单图形,这两种模式的效果是一样的,但对复杂图形,特别是有穿插的图,结果可 能是不同的。例如(画五角星,参见图 14-15):
```
// 定义五角星顶点数组
const int n = 5;
Point p1(100, 0);
Point p2(195, 69);
Point p3(159, 181);
Point p4(41, 181); Point p5(5, 69);
Point ps0[n] = {p1, p2, p3, p4, p5};
Point ps[n] = {p1, p3, p5, p2, p4};
// 创建实心刷对象
SolidBrush redBrush(Color(128, 0, 0));
SolidBrush greenBrush(Color(0, 128, 0));
SolidBrush blueBrush(Color(0, 0, 128));
// 画五角星
Graphics graph(pDC->m_hDC);
graph.DrawPolygon(&Pen(Color::Red), ps0, n);
graph.DrawPolygon(&Pen(Color::Green), ps, n);
// 画填充五角星
graph.TranslateTransform(200, 0); // 右移 200 像素
graph.FillPolygon(&redBrush, ps0, n);
graph.TranslateTransform(200, 0);
graph.FillPolygon(&greenBrush, ps, n, FillModeAlternate);
graph.TranslateTransform(200, 0);
graph.FillPolygon(&blueBrush, ps, n, FillModeWinding);
```
![image](https://box.kancloud.cn/2016-04-18_57144a7e6df84.jpg)
多边形 交替/环绕模式 交替模式 环绕模式
图 14-15 填充多边形(五角星)
GDI 中也没有与画填充多边形类似的专门函数,但可以用(采用当前刷填充的)Polygon 来代替。
### 1.6.5 画曲线的方法
前面讲的各种画线状图或填充图的 GDI+方法,虽然在形式上与 GDI 的有所不同(方法 名前加了 Draw 或 Fill、将笔或刷作为第一个输入参数、部分输的位置入参数改成了大小参 数、并增加了浮点数版),但是在功能上却是相同的。
现在要讲的曲线绘制,则是 GDI+新增加的内容。曲线在机械设计、工程建筑和图形动 画等领域,都有十分广泛应用。
常用的曲线有 Bezier(贝塞尔)曲线和样条(spline)曲线。贝塞尔曲线比较简单,适 合于画控制点少的曲线。当控制点太多时,要不曲线的次数(比点数少 1)太高,要不拼接 比较困难,而且没有局部性(即修改一点影响全局),性能不太好。而样条曲线则可以画任 意多个控制点的曲线,曲线的次数也可以指定(一般为二次或三次),并且具有局部性。贝 塞尔曲线特别是样条曲线有很多变种。常见的贝塞尔曲线有普通贝塞尔曲线和有理贝塞尔曲 线。常用的样条曲线有:B 样条、β 样条、Hermite(厄密)样条、基样条(cardinal splines)、 Kochanek- Bartels 样条和 Catmull-Rom 样条等。
GDI+中所实现的是普通贝塞尔曲线(不过控制点,位于控制多边形的凸包之内)和基 样条曲线(过控制点)。有关曲线和曲面构造方法,会在课程《计算机图形学》中介绍。
(1)基样条曲线(cardinal spline curve)
```
Status DrawCurve(const Pen* pen, const Point* points, INT count, REAL tension = 0.5f);
Status DrawCurve(const Pen* pen, const PointF* points, INT count, REAL tension = 0.5f);
Status DrawClosedCurve(const Pen *pen, const Point* points, INT count, REAL tension = 0.5f);
Status DrawClosedCurve(const Pen *pen, const PointF* points, INT count, REAL tension = 0.5f);
```
其中:
+ 参数 tension(张力)指定曲线的弯曲程度,tension = 0.0(直线)~1.0(最弯曲)。
+ DrawClosedCurve 方法(连接首尾点)画封闭的基样条曲线。 例如(参见图 14-16):
```
void DrawPoints(Graphics &graph, const Color &col, int r, const Point* points, INT count) {
// 自定义的画点列函数 SolidBrush brush(col);
for (int i = 0; i < count; i++)
graph.FillEllipse(&brush, Rect(points[i].X - r,
points[i].Y - r, 2 * r, 2 * r));
}
Graphics graph(pDC->m_hDC);
// 定义 Pen 对象和 Point 对象的数组
Pen greenPen(Color::Green, 3);
Point p1(10, 100), p2(100, 50), p3(300, 10), p4(400, 100);
Point ps[4] = {p1, p2, p3, p4};
// 绘制不同张力的基样条曲线
graph.DrawCurve(&Pen(Color::Magenta), ps, 4, 1.0);
graph.DrawCurve(&greenPen, ps, 4, 0.5);
graph.DrawCurve(&Pen(Color::Blue), ps, 4, 0.0);
DrawPoints(graph, Color::Red, 5, ps, 4); // 绘制曲线的控制点
// 绘制默认张力的基样条、封闭基样条与贝塞尔曲线
graph.TranslateTransform(450, 0); // 水平右移 450 个像素
graph.DrawCurve(&greenPen, ps, 4);
graph.DrawClosedCurve(&Pen(Color::Aqua), ps, 4);
graph.DrawBeziers(&Pen(Color::Chocolate), ps, 4);
DrawPoints(graph, Color::Red, 5, ps, 4); // 绘制曲线的控制点
```
![image](https://box.kancloud.cn/2016-04-18_57144a7e84b80.jpg)
不同张力的基样条曲线 基样条、封闭基样条与贝塞尔曲线
图 14-16 基样条曲线与贝塞尔曲线
(2)贝塞尔曲线(Bezier curve)
```
Status DrawBezier(const Pen* pen, INT x1, INT y1, INT x2, INT y2, INT x3, INT y3, INT x4, INT y4);
Status DrawBezier(const Pen* pen, const Point& pt1, const Point& pt2, const Point& pt3, const Point& pt4);
Status DrawBeziers(const Pen* pen, const Point* points, INT count);
... // 对应的浮点版本
```
(3)填充封闭基样条曲线
```
Status FillClosedCurve(const Brush* brush, const Point* points, INT count);
Status FillClosedCurve(const Brush* brush, const Point* points, INT count, FillMode fillMode, REAL tension = 0.5f);
... // 对应的浮点版本
```
例如,将前面画图 14-15 所对应的填充多边形例子中的画填充五角星的三个语句中的 FillPolygon 方法,改为填充封闭基样条曲线方法 FillClosedCurve,结果如图 14-17 所示。
![image](https://box.kancloud.cn/2016-04-18_57144a7e988f8.jpg)
多边形 交替/环绕模式 交替模式 环绕模式
图 14-17 填充闭曲线
### 1.6.6 平滑处理
可以利用 Graphics 类的设置平滑模式方法
```
Status SetSmoothingMode(SmoothingMode smoothingMode);
```
来设置绘图时的平滑化处理。其中的输入参数为枚举类型:
```
typedef enum {
SmoothingModeInvalid = QualityModeInvalid, //无效(保留)
SmoothingModeDefault = QualityModeDefault, // 默认(低质,无平滑处理)
SmoothingModeHighSpeed = QualityModeLow, // 高速(低质,无平滑处理)
SmoothingModeHighQuality = QualityModeHigh, // 高质(使用 8*4 盒过滤器)
SmoothingModeNone, // 无平滑处理
SmoothingModeAntiAlias8x4, // 使用 8*4 盒过滤器(库中无)
SmoothingModeAntiAlias =
SmoothingModeAntiAlias8x4, // 使用 8*4 盒过滤器
SmoothingModeAntiAlias8x8 // 使用 8*8 盒过滤器(最高质,库中也无)
} SmoothingMode;
```
![image](https://box.kancloud.cn/2016-04-18_57144a7eaafb0.jpg)
图 14-18 平滑处理
例如(参见图 14-18):
```
Graphics graph(pDC->m_hDC);
Pen pen(Color::Black, 4);
Rect rect(10, 10, 200, 200);
graph.DrawRectangle(&pen, rect);
graph.RotateTransform(1);
graph.TranslateTransform(20, 20);
//graph.SetSmoothingMode(SmoothingModeNone);
graph.DrawRectangle(&pen, rect);
graph.TranslateTransform(20, 20);
graph.SetSmoothingMode(SmoothingModeAntiAlias);
graph.DrawRectangle(&pen, rect);
```
### 1.6.7 清屏方法 Clear
GDI 中没有用于清屏的专门函数,得自己用背景色画窗口大小的填充矩形,或者调用窗 口类的 Invalidate 和 UpdateWindow 函数。现在,GDI+有了清屏方法 Clear:
```
Status Clear(const Color &color);
```
其中的输入参数 color,为用户指定的填充背景色。例如:
```
Graphics graph(GetDC()->m_hDC);
……
graph.Clear(Color::White);
```