当前位置: 首页 > 文档资料 > Gdip 编程基础 >

1.6 图形类 Graphics

优质
小牛编辑
129浏览
2023-12-01

与 GDI 的 MFC 类 CDC 类似,GDI+的绘图功能主要由图形类 Graphics 承担。

图形类 Graphics 是 GDI+的核心,它提供绘制图形、图像和文本的各种方法(似 GDI 中的 CDC 类),还可以存储显示设备和被画项目的属性(到图元文件)。Graphics 类及其方 法都被定义在头文件 Gdiplusgraphics.h 中。

image

image

图 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

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

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

多边形 交替/环绕模式 交替模式 环绕模式

图 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

不同张力的基样条曲线 基样条、封闭基样条与贝塞尔曲线

图 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

多边形 交替/环绕模式 交替模式 环绕模式

图 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

图 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);