💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 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-&gt;m_hDC); ``` + 在视图类的其他函数中,可先利用 GetDC 函数得到 CDC 指针,然后再利用它去获 取 DC 的句柄。例如: ``` Graphics graph(GetDC()-&gt;m_hDC); ``` 也可以使用 Graphics 类的另一个构造函数 `Graphics(HWND hwnd, BOOL icm = FALSE);`,利用视图类的窗口句柄成员来构造 Graphics 对象。例如: ``` Graphics graph(this-&gt;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 &lt; 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-&gt;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-&gt;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); ```