GDI+(Graphics Device Interface Plus,图形设备接口加)是 Windows XP 及以上版本操 作系统的图形子系统,也是传统.NET 框架的重要组成部分和窗体绘图的主要工具,负责在 屏幕和打印机上绘制图形图像和显示信息。顾名思义,GDI+是 Windows 早期版本所提供的 图形设备接口 GDI 的后续版本,是建立在 GDI 之上的一个高层图形子系统。
GDI+是一种 API,分别通过一套 C++类和一套部署为托管代码的类来展现,这两套类 分别被称为 GDI+的“C++封装”和“托管类接口”。
GDI+不但在功能上比 GDI 要强大很多,而且在代码编写方面也更简单,因此会成为 Windows 图形图像程序开发的主要工具之一。由于篇幅所限,本书只简单介绍利用 MFC 进 行 GDI+编程的一些基本内容,也不讲 GDI+的 API 编程。基于 GDI+托管封装的.NET 窗体 绘图,将在第 18 章中再介绍。对 GDI+编程有兴趣的读者,可以参考如下图书:
周鸣杨、赵景亮. 精通 GDI+编程. 清华大学出版社,2004 年 2 月(C++ / MFC)。
Mahesh Chand(韩江等译). GDI+图形程序设计. 电子工业出版社,2005 年 3 月(C# / .NET)。
本章将介绍 GDI+的结构和组成,讨论 GDI+的几个主要新增特性与功能,说明 GDI+给 Windows 图形图像程序的开发模式带来的变化。介绍 C++封装的 GDI+ API 的具体使用方法, 主要讲解二维矢量图形绘制和文字显示等基本内容。
GDI+的路径、区域、变换、图像处理和图元文件等高级编程内容,安排在下一章介绍。
1. GDI+的结构与组成
本节先介绍 GDI+的体系结构,再列出 C++封装的 GDI+ API 的具体组成。
1.1 GDI+的结构
GDI+是建立在 GDI 之上的一种高层图形子系统,基础是 GDI+平面 API,有 C++和托 管两种封装。
注意,虽然 GDI+是 GDI 的发展,但是 GDI+并非设计来替代 GDI 的,它不能独立工作, 底层还得靠 GDI 实现。
(1)GDI+的体系结构
GDI+与 GDI 一样,都具有设备无关性。而且 GDI+是建立在 GDI 之上的一种高层接口, 供 Windows API 和.NET 框架调用。与 GDI 类似,GDI+主要提供了二维矢量图形、图像处 理和文字显示版式三类功能(参见图 14-1),只是 GDI+比 GDI 的功能更强大,且编程模式发生了改变。
图 14-1 GDI+的体系结构
(2)GDI+平面 API 与封装
GDI+提供(expose)了一个平面(flat)API,它包含大约 600 个函数,被实现在 Gdiplus.dll 中,声明在 Gdiplusflat.h 内。这些函数被包装到了前面讨论过的 GDI+ API 的 54 个 C++类的 集合之中。
作为 C++封装的替代方案,微软.NET 框架提供了 GDI+的一个托管代码封装类集,包 含大约 60 个类、50 个枚举和 8 个结构。它们分属于下列命名空间:System.Drawing、 System.Drawing.Drawing2D 、 System.Drawing.Imaging 、 System.Drawing.Text 和 System. Drawing.Printing。
GDI+的平面 API 与其 C++及托管封装的关系如图 14-2 所示。
图 14-2 GDI+的封装与使用
1.2 GDI+的组成
GDI+的 C++封装,包含了 54 个类、12 个全局函数、(6 类)226 个图像常量、55 种枚 举和 19 种结构。GDI+的.NET 托管封装,则包含了大约 60 个类、50 个枚举和 8 个结构。这 两种封装都是基于 GDI+平面 API 的。本小节只介绍 GDI+的 C++封装,GDI+的托管封装将 在第 18 章的.NET 窗体绘图中有所涉及。
(1)类
GDI+的 C++封装中共有 54 个类,核心类是 Graphics,它是实际绘制直线、曲线、图形、 图像和文本的类。许多其它 GDI+类是与 Graphics 类一起使用的。例如,DrawLine 方法接收 Pen 对象,该对象中存有所要绘制的线条的属性(颜色、宽度、虚线线型等)。FillRectangle 方法可以接收指向 LinearGradientBrush 对象的指针,该对象与 Graphics 对象配合工作来用 一种渐变色填充矩形。Font 和 StringFormat 对象影响 Graphics 对象绘制文本的方式。Matrix 对象存储并操作 Graphics 对象的仿射变换——旋转、缩放和翻转图像。
GDI+还提供了用于组织图形数据的几种结构类(例如 Rect、Point 和 Size)。而且,某 些类的主要作用是结构化数据类型。例如,BitmapData 类是 Bitmap 类的帮助器,PathData 类是 GraphicsPath 类的帮助器。图 14-3 是 GDI+ API 类的层次结构图。
注意:在 GDI+、.NET、C#、Java 和 VB 中,都把类的成员函数称为方法。当我们在
C++中,使用 GDI+和.NET 框架类库中的类和功能时,也常常将其函数改称为方法。
图 14-3 GDI+类的层次结构
(2)全局函数
GDI+命名空间中的全局函数有 12 个,常用的有如下两个(其余的大多数与图像相关):
关闭 GDI+:GdiplusShutdown(清除 GDI+所使用的资源)。
启动 GDI+:GdiplusStartup(初始化 GDI+)。
(3)常量、枚举和结构
GDI+中有 6 类共计 226 个图像常量(都被定义在头文件 GdiplusImaging.h 中),包括图 像 文 件 格式 常 量 11 个( 如 ImageFormatBMP 、 ImageFormatGIF 、 ImageFormatJPEG 、 ImageFormatPNG、ImageFormatTIFF 等)、图像帧维常量 2 个、图像编码器常量 13 个、图
像像素格式常量 14 个、图像特性标志类型 9 个、图像特性标志 217 个。
GDI+定义了 55 种枚举类型,它们都是相关常数的集合。例如:PenType、BrushType、DashStyle、ImageType、LineCap、FillMode、ImageFlags 等。
GDI+ API 中还定义了 19 种结构,用于 GDI+的各种方法调用中。例如:ColorMap、 ColorMatrix、ColorPalette、Gdiplus Abort、GdiplusStartupInput、GdiplusStartupOutput 等。
1.2 GDI+的特色
本节介绍 GDI+的几个主要新增特性与功能,说明 GDI+在编程模式上的改变。
1.2.1 GDI+新增特性
与 GDI 相比,GDI+新增的特性主要有渐变画刷、样条和贝塞尔曲线、持久路径、矩阵 变换、伸缩区域、α 混色和对多种图像格式的支持。
(1)渐变画刷
GDI+中新增加的渐变画刷(gradient brush,梯度刷),通过提供用于填充图形、路径和 区域的颜色线性渐变和路径渐变的画刷,扩展了 GDI 的功能。渐变画刷可用于绘制直线、 曲线和路径,参见图 14-4。
a)(水平)线性渐变 b)(贝塞尔)路径渐变
图 14-4 渐变画刷 图 14-5 基样条曲线与折线
(2)曲线方法
GDI+支持基样条(cardinal splines)和贝塞尔(Bezier)方法,可以由若干控制点生成 光滑的曲线,参见图 14-5。
(3)持久路径对象
GDI 中的路径(path)属于设备上下文,并且会在绘制时被毁坏。而 GDI+则可以创建 并维护多个与 Graphics 对象分开的持久(persistent)路径对象——GraphicsPath 对象,在绘 图操作时也不会破坏,因此可多次使用同一个 GraphicsPath 对象来绘制路径。
(4)变换和矩阵对象
GDI+提供了 Matrix(矩阵)对象,它是一种可以使(缩放、旋转和平移等)变换(transformation)简易灵活的强 大工具,矩 阵对象一般 与变换对 象联合使用 。例如,GraphicsPath 对象具有 Transform 方法,此方法接收 Matrix 对象作为参数。参见图 14-6。
(5)可伸缩区域
GDI+通过对可伸缩区域(scalable region)的支持极大地扩展了 GDI。在 GDI 中,区域 被存储在设备坐标中,而且,可应用于区域的惟一变换是平移。而 GDI+在全局坐标中存储 区域,并且允许区域发生任何可存储在变换矩阵中的变换(如缩放和旋转)。图 14-7 显示一 个区域在执行三种变换(缩放、旋转和平移)前后的情况。
图 14-6 路径变换
图 14-7 区域变换
图 14-8 不同透明度
(6)α混色
在图 14-7 中,可以在变换区域(用蓝色阴影画笔填充)中看到未变换区域(用红色填充),这是由 GDI+支持的α 混色(alpha blending,透明混合)实现的。使用α 混色,可以指定填充颜色的透明度。透明色与背景色相混合———填充色越透明,透出的背景色就越多。 图 14-8 显示四个用相同颜色(红色)填充、但透明层次不同的椭圆。
(7)丰富的图像格式支持
GDI+提供 Image、Bitmap 和 Metaf ile 类,可以用不同的格式加载、保存和操作图像。 GDI+支持 BMP、GIF、JPEG、EXIF、PNG、TIFF、ICON、WMF、EMF 共 9 种常见的图像 格式。这些已经被 ATL/MFC 中的基于 GDI+的 CImage 类所体现。
(8)GDI+的不足
虽然,相对于 GDI 来说,GDI+确实增加了许多新特性,而且功能更强大,使用也更方 便。但是,这并不等于 GDI+就能够完全代替 GDI。
因为 GDI+实际上是 GDI 的高层封装和功能扩展,GDI+的执行效率一般要低于 GDI 的。 另外,GDI+不支持图形的位运算,那么就不能进行异或绘图等操作。而且在 Visual C++中, GDI+还不直接支持双缓存机制(如内存 DC 和显示 DC),这将大大影响 GDI+在高速图形、 图像、动画和视频等方面的应用。
1.2.2 编程模式的改变
GDI+的出现,也使基于 GDI 的编程模式产生了很大变化:GDI+用一个“无状态模式”, 取代了 GDI 中(需要先将各种工具和项目选入 DC 对象后,才能进行绘图的)“状态模式”。 主要体现在以下几个方面:
(1)DC 句柄和图形对象
设备上下文(DC)是 GDI 中使用的一种结构,用于存储与特定显示设备相关的的绘制 工具及属性的信息,用于屏幕显示的 DC 还与特定窗口相关联。为了使用 GDI API 进行绘图, 必须首先获得一个 DC 的句柄(HDC),然后将该句柄作为参数,传递给实际进行绘图的 GDI 函数。在 MFC 中,DC 及其绘图功能被封装在 CDC 类中,DC 句柄成为了成员变量,绘图 函数变成了方法,不再需要显式传递 HDC 参数。
使用 GDI+,不需要再(直接)使用句柄或设备上下文,而是只需(通过 HDC)创建一个 Graphics 对象,然后用熟悉的面向对象方式来调用其中的各种绘图方法,例如:
1 | myGraphicsObject.DrawLine(&pen, x1, y1, x2, y2); |
正如 DC 是 GDI 的核心,Graphics 对象也位于 GDI+的核心。DC 和 Graphics 对象的作 用相似,但在使用设备上下文(GDI)的基于句柄的编程模式和使用 Graphics 对象(GDI+) 的面向对象的编程模型之间,存在一些基本的差异。
Graphics 对象(像 DC 一样)与屏幕上的特定窗口关联,并具有指定如何绘制项目的属 性(如 SmoothingMode 和 TextRenderingHint)。但是,Graphics 对象不受笔、刷、路径、图 像或字体的约束,这与设备上下文不同。例如,使用设备上下文绘制线条之前,必须先调用 SelectObject 将笔选入 DC 中,以使笔对象和 DC 关联。在设备上下文中绘制的所有线条均 使用该笔,直到选择另一支不同的笔为止。在 GDI+中,将 Pen 对象作为参数传递给 Graphics 类的 DrawLine 等画线方法。可以在一系列的 DrawLine 调用的每个调用中,使用不同的 Pen 对象,而不必将给定的 Pen 对象与 Graphics 对象关联。
(2)画线的两种方法
下面每个示例都从点(20, 10)到点(200, 100)绘制一条宽为 3 的红色线条。第一个示例调 用 GDI,第二个示例则通过托管类接口调用 GDI+,这里都使用 MFC。也可以不使用 MFC, 而直接用 API 来进行 GDI+绘图(由于篇幅有限,这里就不介绍了)。
1)用 GDI 画线
利用 MFC 进行 GDI 绘图,步骤与 API 的差不多,只是 MFC 将各种 GDI 功能封装到了 不同的类中。例如,笔的类为 CPen、点的类为 CPoint、设备上下文的类为 CDC。而且所有 的绘图函数都被封在 CDC 类中,所以只能作为其对象的方法才能被使用,当然也就不用再 带 HDC 句柄作为输入参数了。
1 | CDC *pDC = GetDC(); // 获取 DC 对象 |
2)用 GDI+画线
利用 MFC 进行 GDI+绘图,步骤与 API 的差不多。只是代码改在 OnDraw 函数中,而且获取 DC 句柄的方法不同。
1 | CDC *pDC = GetDC(); // 获取 DC 对象 |
(3)作为参数的绘图工具
前面的示例显示:在 GDI+中,创建和维护 Pen 对象,可以与提供绘制方法的 Graphics 对象分开。同样,创建和维护 Brush、GraphicsPath、Image 和 Font 对象也可以与 Graphics 对象分开,Graphics 类提供的许多绘制方法,都将笔、刷、路径、图像和字体等对象,作为 参数接收。例如,Brush 对象作为参数传递至 FillRectangle 方法,GraphicPath 对象作为参数 传递至 DrawPath 方法。同样,Image 和 Font 对象传递至 DrawImage 和 DrawString 方法。
这与 GDI 不同,在 GDI 中,需要先将笔、刷、路径、图像或字体等 GDI 工具对象选入 DC,然后(API)将 DC 的句柄作为参数传递至绘制函数或(MFC)采用 CDC 类对象的函 数使用 DC 中当前的笔、刷、路径、图像或字体来绘图。
(4)无当前位置
GDI+从总体上已经放弃了当前位置的概念,如在前面所述的 DrawLine 方法中线条的起 点和终点均被作为参数接收。这与 GDI 方案不同,在 GDI 中,调用 MoveToEx(hdc, x1, y1, NULL) 或 pDC->MoveTo(x1, y1)来设置当前笔位置之后,再调用 LineTo(hdc, x2 , y2) 或 pDC->LineTo(x2, y2)来绘制一条从(x1, y1)到(x2 , y2)的线条。
(5)绘制和填充的不同方法
GDI 的 Rectangle 和 Ellipse 等函数,可一步完成绘制轮廓和填充内部的功能。轮廓由当 前选定的笔绘制,而内部则由当前选定的刷来填充。GDI+则必须分别调用绘制轮廓和填充 内部的两个不同方法来做到这一点。例如,Graphics 类的 DrawRectangle 方法将 Pen 对象作 为其参数之一,而 FillRectangle 方法将 Brush 对象作为其参数之一。所以在绘制轮廓和填充 图形内部时,GDI+要比 GDI 更灵活,但也更麻烦。
(6)构造区域
GDI 提供几种用于创建区域的 函数(在 MFC 中,它们被 封装在 CRng 类里): CreateRectRgn 、 CreateEllpticRgn 、 CreateRoundRectRgn 、 CreatePolygonRgn 和 CreatePolyPolygonRgn。您或许希望 GDI+中的 Region 类也有类似的构造函数,将矩形、椭 圆、圆角矩形和多边形作为参数接收,但事实并非如此。GDI+中的 Region 类提供一个接收 Rectangle 对象的构造函数和另一个接收 GraphicsPath 对象的构造函数。如果想基于椭圆、 圆角矩形或多边形构造区域,可以通过创建一个 GraphicsPath 对象(可包含椭圆的对象等), 然后将其传递至 Region 构造函数来轻松实现。
GDI+通过组合图形和路径,使得构成复杂区域十分简单。Region 类具有 Union 和 Intersect 方法,可用于扩展具有路径的现有区域或其它区域。GDI+方案一个很好的功能就是 GraphicsPath 对象在作为参数传递至 Region 构造函数时不会被破坏(在 GDI 中,可以使用 PathToRegion 方法将路径转换为区域,但在此过程中,路径将被破坏)。另外,GraphicsPath 对象在作为参数传递给 Union 或 Intersect 方法时也不会被破坏,因此,在一些单独的区域中, 可以将给定的路径作为构造块使用。例如:
1 | Region region1(rect1); |
1.3 GDI+的 MFC 编程
本节介绍利用 MFC 进行 GDI+编程的必要的准备,并通过例子说明 GDI+编程的具体步 骤,最后给出如何解决存在的 new 操作符问题的方法。
C++封装的 GDI+的(英文)帮助内容,位于 VS08 的“目录/Win32 和 COM 开发/Graphics and Multimedia/GDI+”,主要的参考资料位于其子目录“GDI+ Reference”中。
1.3.1 设置与初始化
封装了 GDI+ API 的各种 C++类、函数、常量、枚举和结构,都被定义在 Gdiplus.h 头 文件所包含的一系列头文件中。所以,采用 MFC 进行 GDI+编程,必须包含 Gdiplus.h 头文 件。
从 14.1.2 的有关 GDI+平面 API 的讨论可知,封装在 GDI+类中方法,最后都需要调用GDI+平面 API 中的相关底层函数,才能完成实际的操作。所以,为了运行 GDI+应用程序,在操作系统平台中,必须安装动态链接库 Gdiplus.dll。对 Windows XP 及以上版本,该 DLL已经自动被操作系统包含。
该动态链接库所对应的静态库文件为 GdiPlus.lib,而且它在 VC08 及之前的早期版本中 不是 C++和 MFC 的默认链接库。所以,对早期的 VC 版本必须在项目设置,添加该库作为 链接器输入的附加依赖项。但是对 VC08 SP1 及 VC10,该库已经成为标准链接库之一,不 必再为链接器输入的附加依赖项添加此库。
因为在 Gdiplus.h 头文件中,将所有的 GDI+的类、函数、常量、枚举和结构等都定义在 了命名空间 Gdiplus 中。所以,一般在 GDI+程序中,都应该使用如下的命名空间声明:
1 | using namespace Gdiplus; |
(1)VC 中的设置
为了在 MFC 应用程序中能使用 GDI+,必须包含 GDI+头文件、使用 GDI+命名空间。 对 VC08 及之前的版本,还要为项目添加 GDI+链接库。
1) 包含头文件、使用命名空间——在要使用 GDI+的文件(如视图类的头文件或代码 文件)头部包含 GDI+的头文件:
1 |
并加上使用 GDI+命名空间的 using 指令(区分大小写,注意首字母大写):
1 | using namespace Gdiplus; |
2) 添加链接库(对 VC08 SP1 及 VC10 不必添加)——在 VS08 及其早期版本中,选 “项目/*属性”菜单项,打开项目的属性页窗口,先选“所有配置”,再选“配置 属性/链接器/输入”项,在右边上部的“附加依赖项”栏的右边,键入 GdiPlus.lib(参见图 14-9)后按“应用”钮,最后按“确定”钮关闭对话框。
(2)GDI+的初始化与清除
为了在 MFC 应用程序中使用采用 C++封装的 GDI+ API,必须在 MFC 项目的应用程序 类 中 , 调 用 GDI+ 命 名 空 间 中 的 GDI+ 启 动 函 数 GdiplusStartup 和 GDI+ 关 闭 函 数 GdiplusShutdown,来对 GDI+进行初始化(装入动态链接库 Gdiplus.dll,或锁定标志+1)和 清除(卸载动态链接库 Gdiplus.dll,或锁定标志-1)工作。它们一般分别在应用程序类的InitInstance 和 ExitInstance 重载方法中调用。
图 14-9 在项目属性对话框中添加静态链接库
函数 GdiplusStartup 和 GdiplusShutdown,都被定义在 GdiplusInit.h 头文件中:
1 | Status WINAPI GdiplusStartup( OUT ULONG_PTR *token, |
其中:
类型 ULONG_PTR,是用无符号长整数表示的指针,被定义在 basetsd.h 头文件中:
1 | typedef _W64 unsigned long ULONG_PTR; |
输出参数 token(权标),供关闭 GDI+的函数使用,所以必须设置为应用程序类的 成员变量(或全局变量,不提倡)。
结构 GdiplusStartupInput 和 GdiplusStartupOutput,都被定义在 GdiplusInit.h 头文件中。
GDI+启动输入结构指针参数 input,一般取默认构造值即可,即(设:无调 试事件回调过程、不抑制背景线程、不抑制外部编解码):
1 | input = GdiplusStartupInput(NULL, FALSE, FALSE); |
GDI+启动输出结构指针参数 output,一般不需要,取为 NULL 即可。 注意,采用 MFC 进行 GDI+ API 编程时,在使用任何 GDI+的功能调用之前,必须先调用 GDI+启动函数 GdiplusStartup 来进行初始化 GDI+的工作;在完成所有的 GDI+功能调用 之后,必须调用 GDI+关闭函数 GdiplusShutdown 来进行清除 GDI+的工作。
(3)过程框图
图 14-10 是使用 MFC 进行 GDI+编程的设置、准备与初始化过程的逻辑框图。
图 14-10 GDI+的设置、准备与初始化
1.3.2 编程例子
下面通过一个简单的例子,来说明如何使用 GDI+进行应用程序开发。
(1)创建和设置
创建一个名为 Gdip 的传统界面 MFC 单文档应用程序项目,在应用程序类和视图类的CPP 代码文件中,包含头文件并使用命名空间:
1 |
|
对 VC08 及之前的版本还需在项目属性中添加链接库 GdiPlus.lib。
(2)初始化与清除
然后再进行 GDI+系统的初始化,这需要在应用程序类 CGdipApp 中声明一个成员变量:
1 | ULONG_PTR m_gdiplusToken; // ULONG PTR 为 int64 类型 并在该类的初始化函数 CGdipApp::InitInstance()中加入以下代码来对 GDI+进行初始化: |
注意:这两个语句必须加在应用程序类的 InitInstance 函数中的
1 | CWinApp::InitInstance(); |
语句之前,不然以后会造成视图窗口不能自动重画、程序中不能使用字体等等一系列问题。
还要在 CGdipApp::ExitInstance()中加入以下代码来关闭 GDI+:
1 | GdiplusShutdown(m_gdiplusToken); |
上面的 InitInstance 和 ExitInstance 都是应用程序类的重写型方法。而且,默认时 VC08 SP1 及其以前版本是不会自动生成 ExitInstance 方法代码的(不过 VC10 会自动生成此方法),需 要自己利用属性窗口来添加(不要手工添加)。
(3)绘图
接下来就可以利用 GDI+进行绘图了。下面的代码段是在 OnDraw 函数中画一个带网格 的透明度连续变化的图:
1 | CGdipView::OnDraw(CDC* pDC) { |
运行的结果如图 14-11 所示。其中,左图为第一个循环所绘制的结果、中图为前两个循 环所绘制的结果、右图为全部三个循环所绘制的结果。
图 14-11 透明度的连续变化
1.3.3 new 问题
在 VC08(包括 SP1)中使用 GDI+时,不能用 new 来动态创建 GDI+对象。解决办法有 如下两种:
(1)修改 GdiplusBase 类
打开(默认)位于“C:\Program Files\Microsoft SDKs\Windows\v6.0A\ Include\”目录中 的 Gdiplus Base.h 头文件,并注释掉里面 Gdiplus Base 类的内容(该类其实只含 new、new[]、 delete 和 delete[]这四个运算符的重载),使其成为一个空类(但不要删除整个类)。
为了不修改原始安装目录中的 Gdiplus Base.h 头文件,可以:
将该头文件复制到你的项目目录中。
注释掉该头文件里面 Gdiplus Base 类的内容(保留类定义)。
在项目中所有的#include <gdiplus.h>语句之前,包含”Gdiplus Base.h”头文件,形如:
1
2则编译系统会优先包含项目目录中的 gdiplus Base.h 头文件,从而屏蔽掉原来位于 平台 SDK 的 Include 目录中的同名头文件。
(2)用&代替 new
也可以在有些使用 new 的地方改用&,例如将代码 Pen pPen = new Pen(Color::Red); 改 为 Pen pPen = &Pen(Color::Red);。
1.4 几何辅助类
与 GDI 的类似,在 GDI+ API 中也定义了许多绘图的辅助类,常用的有点、大小和矩形 等几何类。它们都是没有基类的独立类,被定义在头文件 GdiplusTypes.h 中。与 GDI 不同 的是,在 GDI+中新增加了浮点型的几何类。
浮点数版的几何对象和绘图方法,是 GDI+新增的功能,这些在各种工程技术领域都非 常有用。因为一般的实际图形设计,都是基于实数坐标的。包括机械(../轮船/飞机 等)、建筑(../道路/堤坝等)和图形动画设计(../../轨迹等)等设 计,都必须使用浮点参数和坐标系。
下面对 GDI+的几何辅助类,逐个进行简单的介绍。
1.4.1 Point[F](点)
GDI+中,有两种类型的点:整数点(对应于 Point 类,与 GDI 的 MFC 类 CPoint 类似) 和浮点数点(对应于 PointF 类),下面分别加以介绍。
(1)整数点类 Point
1 | class Point |
其中:typedef int INT; 为 4 字节有符号整数(windef.h)。 注意,GDI+的点与 GDI 的区别:POINT 和 CPoint 采用小写的 x、y。
(2)浮点数点类 PointF
1 | class PointF |
其中:typedef float REAL; 为 4 字节浮点数(GdiplusTypes.h)。
1.4.2 Size[F](大小)
GDI+中,也有两种类型的大小(尺寸):整数大小(对应于 Size 类,与 GDI 的 MFC 类 CSize 类似)和浮点数大小(对应于 SizeF 类)。下面分别加以介绍:
(1)整数大小类 Size:
class Size
{
public:
Size() {Width = Height = 0;}
Size(INT width, INT height) {Width = width; Height = height;}
…
public:
INT Width;
INT Height; // 宽和高,不再是 cx 和 cy
};
注意,这里的大小与 GDI 的区别:SIZE 和 CSize 的分量成员为 cx 和 cy,不是宽和高。
(2)浮点数大小类 SizeF:
1 | class SizeF |
1.4.3 Rect[F](矩形)
GDI+中,也有两种类型的矩形:整数矩形(对应于 Rect 类,与 GDI 的 MFC 类 CRect 类似)和浮点数矩形(对应于 RectF 类),下面分别加以介绍。
(1)整数矩形类 Rect:
1 | class Rect |
注意,这里的矩形与 GDI 的区别:RECT 和 CRect 的分量成员是左、顶、右、底而不 是 X、Y、宽、高。虽然 Rect 中的(X, Y)等价于 RECT 的(left, top),但是 Rect 中的(Width, Height) 却不同于 RECT 的(right, bottom)。
(2)浮点数矩形类 RectF:
1 | class RectF |
在 GDI 的 MFC 封装中,除了定义有点、大小和矩形的类外,还保留了对应的 API 结构 POINT、SIZE 和 RECT,主要是考虑运行效率及与底层 GDI API 的兼容。
比较可知,GDI 和 GDI+都有对应的几何类,不过 GDI+没有对应的结构(但有新增加 的浮点数版类),而 GDI 则没有对应的浮点数版类(但却有对应的结构)。
1.5 颜色
与 GDI 相比,GDI+的颜色新增了一个透明分量,并且定义了颜色类 Color。
1.5.1 颜色类型 ARGB
GDI+中的颜色,与 GDI 中的颜色的最大不同,是增加了一个字节(8 位)的透明分量 alpha(α ),用来表示颜色的不透明度:0 透明(看不见前景色,只有背景色)~255 不透明(看不见背景色,只有前景色,相当于覆盖和拷贝)。背景色指屏幕窗口中原有图形的颜色, 前景色指将要绘制图形的颜色。
因此,GDI+中的颜色一般都是用四个字节表示(Intel CPU 中,多字节整数的低位在前):
整数序(高位→低位):
字节序(低字节→高字节):
在 GDI 中没有专门的颜色类,只有一个颜色类型 COLORREF,也定义为:
1 | typedef DWORD COLORREF; // 0x00bbggrr (windef.h) |
和一个生成颜色的宏:
1 | COLORREF RGB( BYT E bRed, BYTE bGreen, BYT E bBlue); |
其中:
1 | typedef unsigned char BYTE; // 单字节无符号字符整数 |
在 GDI+中,也将颜色数据定义为无符号 4 字节长整数类型 DWORD,但是改名为 ARGB:
1 | typedef DWORD ARGB; // gdipluspixelformats.h |
1.5.2 颜色类 Color
而且 GDI+中还定义了专门的 Color 类,不仅包含了此颜色数据,而且还有多个构造函 数和其他辅助方法、枚举和常量。
Color 类的构造函数中,最常用的是:
1 | Color( BYTE a, BYTE r, BYT E g, BYTE b); // a 为 alpha 分量α 但也有一个默认构造函数: |
还有一个与 GDI 兼容的构造函数:
1 | Color( BYTE r, BYTE g, BYTE b); // 不透明色,相当于 Color(255, r, g, b); |
你也可以直接用含颜色数据的 4 字节无符号整数,来构造 Color 类的对象:
1 | Color( ARGB argb); // 相当于 Color(a, r, g, b); |
该整数可以由 Color 类的静态方法 MakeARGB 或动态方法 GetValue 获得:
1 | static ARGB MakeARGB( BYT E a, BYTE r, BYTE g, BYTE b); |
你还也可以用 Color 类的方法:
1 | COLORREF ToCOLORREF() const; |
将 Color 对象中的颜色,转换为 GDI 的颜色类型。
Color 类还提供了各个颜色分量的获取方法及其简化版:
1 | BYTE GetAlpha() const; |
你也可以先用宏:
1 | BYTE GetRValue(DWORD rgb); // COLORREF rgb |
获取 COLORREF 的 R、G、B 值,然后再用这些值调用 Color 类的构造函数来创建 Color 对象。例如:
1 | COLORREF crCol = colDlg.GetColor(); |
1.5.3 颜色枚举常量
GDI+在颜色头文件 GdiplusColor.h 中,定义了 141 个公用颜色枚举常量,都是对应颜色 的英文单词。可以用 Color 类直接访问,例如:Color ::Red。
下面是部分颜色枚举常量(按字母顺序排列):
1 | public: |
图 14-12 是各种颜色枚举字符常量(包括中文译名)所对应的色块表(按字母顺序排列)。
1.6 图形类 Graphics
与 GDI 的 MFC 类 CDC 类似,GDI+的绘图功能主要由图形类 Graphics 承担。
图形类 Graphics 是 GDI+的核心,它提供绘制图形、图像和文本的各种方法(似 GDI 中的 CDC 类),还可以存储显示设备和被画项目的属性(到图元文件)。Graphics 类及其方 法都被定义在头文件 Gdiplusgraphics.h 中。
图 14-12 颜色枚举常量
1.6.1 构造函数
Graphics 类的构造函数有如下 4 种:
1 | Graphics(Image* image); // 用于绘制图像 |
其中,最常用的是第二种——在当前视图窗口中绘图的图形类构造函数。
注意,该构造函数的输入参数,是设备上下文的句柄,而不是 CDC 类对象的指针。一 般可以由 CDC 对象得到(因 CDC 类含有公用数据成员 HDC m_hDC;):
在 OnDraw 函数中,利用输入参数 CDC *pDC,就可直接得到 DC 句柄。例如:
1
Graphics graph(pDC-
在视图类的其他函数中,可先利用 GetDC 函数得到 CDC 指针,然后再利用它去获 取 DC 的句柄。例如:
1
Graphics graph(GetDC()->m_hDC);
也可以使用 Graphics 类的另一个构造函数 Graphics(HWND hwnd, BOOL icm = FALSE);
,利用视图类的窗口句柄成员来构造 Graphics 对象。例如:
1 | Graphics graph(this->m_hWnd); |
1.6.2 状态枚举 status
在图形类 Graphics 中,封装了各种绘图方法。每种绘图方法被调用后,都会返回一种 叫做 status 的枚举值,反映该方法是否被正确执行,0 表示正确,其他大于 0 的值为错误代 码(GdiplusTypes.h):
1 | typedef enum { // 状态枚举(含 22 个枚举值) |
GDI+的绘图功能被封装在图形类 Graphics 中,下面介绍其中的常用绘图方法。先讲绘 制线型图的方法,再讲绘制填充图的方法,最后讲绘制文字的方法。
1.6.3 画线型图的方法
GDI+中绘制线型图形的方法与 GDI 的类似,也包括绘直线、矩形、椭圆和多边形等, 但是 GDI+增加了浮点版本和若干新功能。GDI+的画线函数都是 Graphics 类的方法,而且所 有方法的名称都是以 Draw 开头。
(1)画直线[折线]DrawLine[s]
在 GDI+中定义了 6 种绘制直线和折线的方法,前三个为整数版,后三个为对应的浮点 数版:
1 | Status DrawLine(const Pen* pen, INT x1, INT y1, INT x2, INT y2); |
其中:
DrawLine——画直线(4 个重载),参数 pen 为画直线所用的笔、(x1, y1)和 pt1 为 直线的起点、(x2, y2)和 pt2 为直线的终点。GDI 的相应函数为 MoveTo 和 LineTo。
DrawLines——画折线(一串相互连接的直线段)(2 个重载),参数 points 为点数 组、count 为数组中点的数目。GDI 的相应函数为 Polyline。
(2)画矩形[组] DrawRectangle[s]
在 GDI+中也定义了 6 种绘制矩形和矩形组的方法,也是前三个为整数版,后三个为对 应的浮点数版:
1 | Status DrawRectangle(const Pen* pen, const Rect& rect); |
其中:
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 个重载的绘制椭圆的方法,如果输入参数所确定的外接矩形的宽高相等, 则画圆。也是前两个为整数版,后两个为对应的浮点数版:
1 | Status DrawEllipse(const Pen* pen, const Rect& rect); |
这些方法的功能,与 GDI 中的函数:
1 | BOOL Ellipse( int x1, int y1, int x2, int y2 ); |
图 14-13 画弧方法的输入参数
类似,但是同样要注意 GDI+的 DrawEllipse 方法与 GDI 的 Ellipse 函数的主要区别(与画矩 形的方法与函数类似),是上面的以坐标为参数的第 2、4 个 GDI+画椭圆方法的后两个输入 参数,也是矩形的宽高而不再是矩形的右下角坐标了。
(4)画[椭]圆弧 DrawArc
GDI+中也有 4 个重载的绘制椭圆弧的方法,如果输入参数所确定的外接矩形的宽高相 等,则画圆弧。也是前两个为整数版,后两个为对应的浮点数版。
1 | Status DrawArc(const Pen* pen, INT x, INT y, INT width, INT height, REAL startAngle, REAL sweepAngle); |
注意,角度的单位是度(不是弧度,C++的三角函数 采用的是弧度单位),而且都必须是实数。零度角为 x 轴 方向,顺时针方向为正(这与数学上反时针方向为正刚好相反),参见图 14-13。
(5)画多边形 DrawPolygon
GDI+中有 2 个重载的绘制多边形的方法,前一个为整数版,后一个为对应的浮点数版:
1 | Status DrawPolygon(const Pen* pen, const Point* points, INT count); |
其中,各参数的含义同画折线方法 DrawLines 的,只是 DrawPolygon 方法会将点数组中的起点和终点连接起来,形成一个封闭的多边形区域。 该方法的功能与 GDI 的 Polygon 函数相同:
1 | 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 个为对应的浮点 数版:
1 | Status FillRectangle(const Brush* brush, const Rect& rect); |
用指定刷子 Brush,填充 rect 的内部区域,无边线,填充区域包括矩形的左边界和上边 界,但不包括矩形的右边界和下边界。功能与 GDI 的 FillRect 函数类似:
1 | void FillRect( LPCRECT lpRect, CBrush* pBrush ); |
但是,GDI 中没有同时填充一个矩形数组的函数。不过 GDI 却有 GDI+中所没有的画填充圆 角矩形的函数 FillSolidRect。
(2) 画填充椭圆 FillEllipse
GDI+中有 4 个重载的绘制填充椭圆的方法,前 2 个为整数版,后 2 个为浮点数版:
1 | Status FillEllipse(const Brush* brush, const Rect& rect); |
GDI 中没有类似函数,但可以用(采用当前刷填充的)Ellipse 函数来代替。
(3) 画饼图 DrawPie
GDI+中有 4 个重载的绘制饼图的方法,前 2 个为整数版,后 2 个为浮点数版:
1 | Status DrawPie(const Pen* pen, const Rect& rect, REAL startAngle, REAL sweepAngle); |
与 GDI 的下列函数类似,但是部分输入参数的含义有所不同:
1 | BOOL Pie( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 ); |
例如(参见图 14-14):
1 | void DrawPies(Graphics &graph, const Color cols[], Point &O, int r, const float data[], int n) { |
(4) 画填充多边形 FillPolygon
GDI+中有 4 个重载的绘制填充多边形的方法,前 2 个为整数版,后 2 个为浮点数版:
1 | Status FillPolygonconst Brush* brush, const Point* points, INT count); |
其中,填充模式参数 FillMode,可取如下两个值之一(参见 8.5.3 中的 1.):
1 | typedef enum { |
对简单图形,这两种模式的效果是一样的,但对复杂图形,特别是有穿插的图,结果可 能是不同的。例如(画五角星,参见图 14-15):
1 | // 定义五角星顶点数组 |
多边形 交替/环绕模式 交替模式 环绕模式
图 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)
1 | Status DrawCurve(const Pen* pen, const Point* points, INT count, REAL tension = 0.f); |
其中:
参数 tension(张力)指定曲线的弯曲程度,tension = 0.0(直线)~1.0(最弯曲)。
DrawClosedCurve 方法(连接首尾点)画封闭的基样条曲线。 例如(参见图 14-16):
1 | void DrawPoints(Graphics &graph, const Color &col, int r, const Point* points, INT count) { |
不同张力的基样条曲线 基样条、封闭基样条与贝塞尔曲线
图 14-16 基样条曲线与贝塞尔曲线
(2)贝塞尔曲线(Bezier curve)
1 | Status DrawBezier(const Pen* pen, INT x1, INT y1, INT x2, INT y2, INT x3, INT y3, INT x4, INT y4); |
(3)填充封闭基样条曲线
1 | Status FillClosedCurve(const Brush* brush, const Point* points, INT count); |
例如,将前面画图 14-15 所对应的填充多边形例子中的画填充五角星的三个语句中的 FillPolygon 方法,改为填充封闭基样条曲线方法 FillClosedCurve,结果如图 14-17 所示。
多边形 交替/环绕模式 交替模式 环绕模式
图 14-17 填充闭曲线
1.6.6 平滑处理
可以利用 Graphics 类的设置平滑模式方法
1 | Status SetSmoothingMode(SmoothingMode smoothingMode); |
来设置绘图时的平滑化处理。其中的输入参数为枚举类型:
1 | typedef enum { |
图 14-18 平滑处理
例如(参见图 14-18):
1 | Graphics graph(pDC->m_hDC); |
1.6.7 清屏方法 Clear
GDI 中没有用于清屏的专门函数,得自己用背景色画窗口大小的填充矩形,或者调用窗 口类的 Invalidate 和 UpdateWindow 函数。现在,GDI+有了清屏方法 Clear:
1 | Status Clear(const Color &color); |
其中的输入参数 color,为用户指定的填充背景色。例如:
1 | Graphics graph(GetDC()->m_hDC); |
1.7 笔和刷
本节介绍 GDI+的两类绘图工具——笔和刷,它们与 GDI 的相比新增加了许多功能。
1.7.1 笔
与 GDI 中的一样,GDI+中的笔(pen)也是画线状图的工具,但是功能更加强大。例 如:透明笔、图案笔、自定义虚线风格、线帽、笔的缩放和旋转、笔的连接点属性等。
GDI+中的笔对应于 Pen 类,被定义在 GdiplusPen.h 头文件中。 笔的构造方法主要有两个:
1 | Pen(const Color &color, REAL width = 1.0); // 单色笔 |
其中,最常用的是第一个,它构造一个颜色为 color,宽度为 width(默认为 1)的单色笔。 如果颜色的α 值<255,则所创建的笔就是带透明度的笔。
(1)笔对齐
当笔宽大于 1 时,默认情况下,是以笔的中心与绘图坐标对齐。但是,也可以采用 Pen类的方法:
1 | Status SetAlignment(PenAlignment penAlignment); |
设置为内对齐,其输入参数取枚举类型 PenAlignment 的符号常量:
1 | typedef enum { |
例如(输出结果如图 14-19 所示):
1 | Graphics graph(pDC->m_hDC); |
a) 中心对齐(默认值) b) 内对齐
图 14-19 笔对齐
(2)图案笔
笔类 Pen 的第二个构造方法,是从刷子来创建笔,如果是单色的实心刷,则相当于第一 个笔构造方法。如果刷子为条纹(影线)或纹理(图像)等图案刷,则该构造函数所创见的 就是对应的图案笔。
例如(条纹笔画椭圆,参见图 14-20):
1 | HatchBrush hBrush(HatchStyleCross, Color::Green, Color::Red); // 创建十字线条纹刷 |
图 14-20 条纹笔椭圆
图 14-21 纹理笔椭圆
又例如(纹理笔画椭圆,参见图 14-21):
1 | Image img(L"张东健.bmp"); // 创建图像对象,并装入图像文件 |
(3)线型
与 GDI 一样,对 GDI+中的笔,也可以设置线型。所用的方法为:
1 | Status SetDashStyle(DashStyle dashStyle); |
其中的输入参数,为虚线风格枚举 DashStyle:(GdiplusEnums.h)
1 | enum DashStyle { |
可以用 Pen 类的另一个方法来获取笔的线型:
1 | DashStyle GetDashStyle() const; |
GDI+中的线型,大多数与 GDI 中的相同,区别主要有两点:
GDI 中的非实线线型,对宽度>1 的笔无效;而 GDI+的笔对任意非零宽度的笔都是有效的。
GDI+中新增了一种风格——自定义虚线风格。 具体的自定义虚线风格,由 Pen 类的设置虚线图案的方法
1 | Status SetDashPattern(const REAL *dashArray, INT count); |
来设置,其中的实数数组 dashArray 含若干个正实数(单位为像素),按线、空、线、空、„„ 的交叉方式排列;参数 count 为数组中实数的个数(须>0)。
例如(参见图 14-22):
1 | Graphics graph(pDC->m_hDC); |
图 14-22 虚线风格
还可以用 Pen 类的另一个方法来获取笔的自定义虚线图案数据:
1 | INT GetDashPatternCount(VOID); // 获取虚线数组中实数的个数 |
(4)线帽
线帽(line cap)是指线条两端的外观,默认为正方形,也可以用 Pen 类的下列方法来 设置不同的线端形状:
1 | Status SetStartCap(LineCap startCap); // 设置起点的线帽 |
其中的线帽枚举 LineCap 为(GdiplusEnums.h):
1 | typedef enum { |
自定义线帽,需要用到 GDI+专门为此定义的类 CustomLineCap。其构造函数为:
1 | CustomLineCap(const GraphicsPath *fillPath, const GraphicsPath *strokePath, LineCap baseCap, REAL baseInset); |
其中要用到图形路径类 GraphicsPath,该类中有图形各种添加图形方法,只是把 Graphics 类
绘图方法名中的 Draw 改成 Add 即可。例如:AddLine、AddRectangle 和 AddPolygon 等。使 用时,可以先创建一个空路径,然后调用这些添加图形方法若干次,就可以生成路径了。
例如(各类线帽,参见图 14-23 和图 14-24):
箭头线帽 构造箭头线帽头尾所使用的坐标系
图 14-23 自定义线帽
1 | // 自定义箭头线帽 |
图 14-24 线帽的种类 图 14-25 箭头线帽的旋转直线簇 又例如(旋转箭头线帽,参见图 14-25):
1 | // startCap 和 endCap 的创建同上例,需包含头文件 <math.h> |
方法 SetLineCap 的最后一个输入参数 DashCap dashCap,用于设置虚线内部各线段端点 的形状。其取值是枚举类型(GdiplusEnums.h):
1 | typedef enum { |
可见,只有三种选择:平、圆和三角。之所以枚举常量所对应的值不连续,是因为要同 LineCap 枚举的对应常量一致。
注意,虚线帽的设置,只影响其虚线内部的线段,不会影响整条虚线的头尾形状,它们 是由 SetLineCap 方法的前两个参数来分别设置的。例如(参见图 14-26):
1 | Graphics graph(pDC->m_hDC); |
DashCapRound 圆虚线帽
DashCapFlat 平虚线帽
DashCapTriangle 三角虚线帽
图 14-26 虚线帽
(5)线连接
笔的线连接(join)属性,也是 GDI+新增的功能。可以使用 Pen 类的方法:
1 | Status SetLineJoin( LineJoin lineJoin); |
来设置笔的线连接属性。其中输入参数为枚举类型 LineJoin:
1 | enum LineJoin { |
例如(参见图 14-27):
1 | Graphics graph(pDC->m_hDC); |
LineJoinMiter LineJoinBevel LineJoinRound LineJoinMiterClipped
斜接 斜截 圆角 斜剪
图 14-27 线连接
从该例还看不出斜剪与斜接有什么区别,因为斜剪 LineJoinMiterClipped 主要针对交角 很小,相交部分很长的情形。在斜剪线连接方式下,可以调用 Pen 类的方法
1 | Status SetMiterLimit(REAL miterLimit); |
来设置相交部分的最大限制长度,默认是 10.0(相对于线宽的比值)。
对 LineJoinMiterClipped 方式的线连接,如果 miterLimit < 相交部分的长度,则会截断 至线头(同斜截方式,相当于 miterLimit = 1.0);如果 miterLimit >= 相交部分的长度,则绘 制完整的相交部分。
但是对 LineJoinMiter 方式的线连接,如果 miterLimit < 相交部分的长度,则会截断至
miterLimit 所指定比例的长度;如果 miterLimit >= 相交部分的长度,则绘制完整的相交部分。 例如(参见图 14-28):
1 | Graphics graph(pDC->m_hDC); |
图 14-28 小交角线连接 图 14-29 不同斜接限长下的斜接线连接 如果不断修改斜接线连接 LineJoinMiter 方式下的线长限制(0.0f~13.0f),则可得到不同
截断长度的斜交角。例如(参见图 14-29):
1 | pen.SetLineJoin(LineJoinMiter); // 斜接 |
1.7.2 刷
与 GDI 中的一样,GDI+中的刷(brush)也是画填充图的工具,GDI+中也有与 GDI 相 对应的实心刷(单色刷)、条纹刷(影线刷)和纹理刷(图像刷)。不过,GDI+又新增加了 功能强大的线性渐变刷和路径渐变刷,而且还为所有这些刷各自建立了对应的类,基类是Brush(功能少)。
图 14-30 是 GDI+中各种刷类的层次结构图, 所有刷类都被定义在头文件 Gdiplus Brush.h 中。
(1)刷基类 Brush
Brush 是所有 GDI+具体刷类的基类,Brush 类没有自己的公用构造函数,属于非实例化
类(用户不能创建 Brush 类的对象和实例),只是定义了三个公用的方法(接口):
1 | Brush *Clone( VOID) const; // 克隆,用于复制 Brush 及其派生类对象 Status |
下面是 BrushType 枚举类型的定义(GdiplusEnums.h):
1 | typedef enum { |
(2)实心刷类 SolidBrush
GDI+中,实心的单色刷对应于 SolidBrush 类,它只有一个构造函数:
1 | SolidBrush(const Color &color); |
输入参数为颜色对象的引用。
在前面的例子中已经多次使用了 SolidBrush 类,下面再举一个画正叶曲线的例子,下面 是正叶曲线的极坐标方程及其到直角坐标系的转换公式:
其中,l 为叶片长度、n 为叶片数目。
因为 GDI+并没有画正叶曲线的专门函数,所以需要用多边形、样条曲线或图形路径来 刻画它。可以使用填充多边形、填充封闭曲线和填充图形路径等方式来进行绘制,下面的代码使用的是填充闭基样条曲线,输出结果如图 14-31 所示。
绘制单个正叶曲线的函数代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void DrawLeaves(Graphics &graph, const Color col, Point &O,
int l, int n) {
double radian = 3.14159265358979323846 / 180.0;
int m = n < 5 ? 21 : 11;
int N = m * n;
double da = 360.0 / N; PointF *ps = new PointF[N];
for (int i = 0; i < N; i++) {
double r = abs(l * cos(radian * (n * i * da)/ 2.0)),
x = r * cos(i * da * radian),
y = r * sin(i * da * radian);
ps[i].X = REAL(O.X + x);
ps[i].Y = REAL(O.Y + y);
}
graph.FillClosedCurve(&SolidBrush(col), ps, N);
}绘制系列彩色正叶曲线的调用序列:
1 | Graphics graph(pDC->m_hDC); |
图 14-31 彩色正叶曲线系列
(3)条纹刷类 HatchBrush
条纹是一种重复填充的小方形图案,一般为横线、竖线、斜线和小方块等构成。GDI+ 中,条纹刷(hatch brush 影线刷/阴影刷)对应于 HatchBrush 类,它也只有一个构造函数:1
HatchBrush(HatchStyle hatchStyle, const Color &foreColor, const Color &backColor = Color());
其中:第一个参数为条纹类型,第二个参数为前景色(条纹色),第三个参数为背景色(空隙色)。
GDI+中一共有 53 种条纹风格,而 GDI 中只有前 6 种。条纹风格枚举 HatchStyle 也被定 义在头文件 GdiplusEnums.h 中:
1 | enum HatchStyle { |
例如(参见图 14-32):
1 | Graphics graph(pDC->m_hDC); Pen pen(Color::Black); |
图 14-32 条纹刷的条纹风格
与 GDI 一样,在 GDI+中也可以调整条纹刷和图像刷的起点。这需要使用图像类 Graphics 的方法 SetRenderingOrigin 来设置渲染原点为(x, y)(默认为(0, 0)):
1 | Status SetRenderingOrigin(INT x, INT y); |
(4)纹理刷类 TextureBrush
纹理刷(texture brush)就是图像刷,它将刷中所装入的图像,在目标区域中进行平铺, 可达到纹理效果。GDI 中也有图像刷,但仅限于使用位图资源和(非常费事才能使用)BMP 文件。在 GDI+中,纹理刷所对应的是 TextureBrush 类,它有 7 个构造函数,最常用的为:
1 | TextureBrush(Image* image, WrapMode wrapMode = WrapModeTile); |
其中,第一个参数是图像对象的指针,第二个参数是排列方式的枚举常量(GdiplusEnums.h):
1 | typedef enum { |
还可以用纹理刷类的下面两个方法来设置和获取刷的排列方式:
1 | Status SetWrapMode(WrapMode wrapMode); WrapMode GetWrapMode() const; |
例如(参见图 14-33):
1 | Graphics graph(pDC->m_hDC); |
平铺(WrapModeTile)
平铺且 X 向翻转(WrapModeTileFlipX)
平铺且 Y 向翻转(WrapModeTileFlipY)
平铺且 XY 向翻转(WrapModeTileFlipXY)
不平铺(WrapModeClamp)
图 14-33 纹理刷排列方式
纹理刷类 TextureBrush 中,还有几个方法,可以对刷中的图像进行平移(translate)、旋 转(rotate)和缩放(scale)等变换(transform)(这是 GDI 里所没有的功能):
1 | Status TranslateTransform(REAL dx, REAL dy, MatrixOrder order = MatrixOrderPrepend); |
例如(参见图 14-34):
1 | Graphics graph(pDC->m_hDC); Image img(L"张东健.bmp"); |
平移(30, 30)
旋转 30 度
水平放大 3 倍
垂直放大 3 倍
图 14-34 纹理刷变换
(5)线性渐变刷类 LinearGradientBrush
线性渐变刷(linear gradient brush 线性梯度刷)使用逐渐变化的颜色填充目标区域。是 GDI+新增的功能。线性渐变刷所对应的类为 LinearGradientBrush,它有 6 个构造函数,前 3 个是整数版,后 3 个是对应的浮点数版。下面是 3 个整数版的构造函数:
1 | LinearGradientBrush(const Point& point1, const Point& point2, const Color& color1, const Color& color2); |
在这三种构造函数中,第一个是点到点、第二个是矩形与渐变模式、第三个是是矩形与旋转角度。限于篇幅,这里只介绍其中点到点的整数版构造函数的具体使用方法。
1)点到点渐变
点到点的渐变是指刷子所填充的颜色,沿着点 point1 到点 point2 的直线,从颜色 color1 连续变化到 color2。若 p1 和 p2 点的 y 值相等,则为水平方向的渐变;若 p1 和 p2 点的 x 值 相等,则为垂直方向的渐变;p1 和 p2 点的 x 和 y 值都不相等,则为斜对角方向的渐变。
例如(参见图 14-35):
1 | Graphics graph(pDC->m_hDC); |
水平渐变 垂直渐变 对角渐变
图 14-35 线性渐变刷
其实,线性渐变刷默认是按 WrapModeTile 平铺方式重复排列的(原点是 point1),例如(参见图 14-36 a)):
1 | Graphics graph(pDC->m_hDC); |
你也可以将上面代码中的注释符“//”去掉,利用线性渐变刷类的方法
1 | Status SetWrapMode(WrapMode wrapMode); |
来设置画刷的排列方式为 WrapModeTileFlipX 平铺并水平翻转,参见图 14-36 b)。
图 14-37 参数的含义
a) 平铺重复排列 b) 加水平翻转
图 14-36 按平铺重复排列的线性渐变刷
下面是一个利用水平线性渐变刷来画阴阳八卦中的阴阳鱼例子(参见图 14-37 和图 14-38):
1 | LinearGradientBrush R2BBrush(Point(0, 10), Point(200, 10), Color(255, 0, 0), Color(0, 0, 255)); |
图 14-38 绘制阴阳鱼的分步输出结果
2)多色渐变 线性渐变刷还有很多其他功能,例如可利用刷的方法:
1 | Status SetInterpolationColors(const Color *presetColors, const REAL *blendPositions, INT count); |
来设置多色渐变。其中,presetColors 为多色数组、blendPositions 为以百分比表示的对应混色点的位置(首、尾值必须为 0.0f 和 1.0f,中间的值应该按递增序排列)、count 为颜色和混 色点位的数目。例如(参见图 14-39):
1 | Color cols[] = {Color::Red, Color::Orange, Color::Yellow, Color::Green, Color::Cyan, Color::Blue, Color::Purple, Color::Magenta}; |
图 14-39 多色渐变 另外,也可以像纹理刷和条纹刷一样,设置线性渐变刷的渲染原点等。 路径渐变刷的内容,安排到下一章的第 15.1.2 小节中,在介绍过路径的基本概念和使用方法之后再来讲解。
1.8 文字
GDI+的文本排版和字体处理的功能比 GDI 的更加强大。特别是 Windows XP 及以上版本,提供了对 LCD(液晶)显示器的特殊优化功能,GDI+也提供了对应的 ClearType(清晰活字)文字处理技术,以增强字体的清晰度。另外,GDI+还提供了构造专用字体集的功能,可以包含私有的临时字体(不需预先安装到系统中)。
Windows 中使用的字体,一般是 TrueType(真实活字) 字体(TTF=TrueType Font),它是 1991 年 Apple 和 Microsoft 联合开发的一种字体技术,采用二次贝塞尔曲线来描述字符 的轮廓。
在 GDI+中,与文字相关的类有(参见图 14-40):字体族类 FontFamily、字体类 Font 和字体集类 FontCollection 及其两个派生类 InstalledFontCollection (已安装字体集)和 PrivateFontCollection(专用字体集)。而在 GDI 中,则只有 CFont 一个字体类。
图 14-40 字体类的层次结构
1.8.1 字体
下面介绍字体族类 FontFamily 和字体类 Font 及相关参数。
(1)字体族类 FontFamily
字体族(font family)是一组具有同一字样(typeface),但是风格(style)不同的字体(font)。其中,字样是指字体的种类,如 Arial、Times New Roman、宋体、楷体_GB2312。 风格是指:正常(regular)、粗体(bold)、斜体(italic)、粗斜体(bold and italic)、下划线(underline)、删除线(strikeout)等。
1)构造函数
字体族类 FontFamily 有两个构造函数:
1 | FontFamily( VOID); // 构造一个空字体族(少用) |
只要不是使用专用字体集中的字体,一般不需要设置第二个输入参数,取默认的 NULL 即可。例如:
1 | FontFamily fontFamily(L"宋体"); 或 |
2)显示当前系统已装入的字体(族)名称
可先利用(字体集 FontCollection 的派生类)已安装字体集类 InstalledFontCollection 的 方法 GetFamilyCount 和 GetFamilies 来分别获取当前系统中已经安装字体集中字体族的数目 和对象指针:
1 | INT GetFamilyCount( VOID) const; |
然后再利用字体族类的方法 GetFamilyName 来获取每个字体族的名称:
1 | Status GetFamilyName(WCHAR name[LF_FACESIZE], WCHAR language = LANG_NEUTRAL) const; |
其中 LANG_NEUTRAL 表示采用中立语言,即用户的默认语言。
例如(可创建一个带滚动视图类的单文档 MFC 应用程序 Fonts,添加对 GDI+的支持, 输出结果如图 14-41 所示):
1 | void CFontsView::OnDraw(CDC* pDC) { |
图 14-41 获取并显示当前系统的字体
注:如果想用程序将这些名称写入一个文本文件,需要注意 ofstream 不支持宽字符串的 流输出,可以用实例模板类 ofwstream 来定义一个新的文件输出流类型。还可以采用 CFile 来输出,但要注意宽字符串采用的是 UTF-16 编码,需要在文本文件的开始处,添加用 0xFE 和 0xFF 这两个字节表示的 UTF-16 编码标志。
(2)字体类 Font
字体类 Font 的构造函数有 6 个,常用的是如下两个:
1 | Font(const FontFamily *family, REAL emSize, INT style = FontStyleRegular, Unit unit = UnitPoint); |
其中的第一个构造函数,其第一个输入参数是字体族的指针,所以必须先创建字体族对象。
而第 2 个构造函数的第一个输入参数则是字体族(字样)的名称,不需要创建字体族对象, 并且还多了可以选择的字体集作为最后一个输入参数。其余的构造函数都与 API 中的字体 句柄、逻辑结构和 DC 中的当前字体有关,在 GDI 中已经讨论过。
注意:在使用 VC08 SP1 和 VC10 时,需要注释掉(默认)位于 c:\program files\microsoft visual studio 9.0\vc\include\目录中的 VC 头文件 comdef.h 中的第 309~315 行:
1 | // hard-coded smart pointer defs |
不然,编译时会出现两个 Font 类定义冲突问题的错误。也可以不改 comdef.h,而在代码中 的每个 Font 类名的前面,都加上命名空间限定符“Gdiplus::”,如 Gdiplus::Font,不过这样 又太麻烦。
下面我们重点讨论第二个构造函数的使用,先介绍其中的各个参数。
1)字体种类 familyName(字体族名)—— 宽字符串表示的字体名称
常用的英文字体族名有:
- Times New Roman:Font Family Name 字体族名(有衬线)
- Arial: Font Family Name 字体族名(无衬线)
- Arial Narrow: Font Family Name 字体族名(窄体)
- Courier New: Font Family Name 字体族名(等宽)
常用的中文字体族名有:
- 宋体: Font Family Name 字体族名(正文)
- 楷体_GB2312: Font Family Name 字体族名(正文、标题)
- 黑体: Font Family Name 字体族名(标题、美术)
- 仿宋_GB2312: Font Family Name 字体族名(标题、美术)
- 隶书: Font Family Name 字体族名(标题、美术)
2)字体风格 style—— 字体的风格,可以取如下枚举常量(默认为 FontStyleRegular):
1 | typedef enum { |
3)字体单位 unit 与大小 emSize——字体的大小与有单位关,可用单位有:
1 | typedef enum { |
其中,em = M,在印刷行业中表示一个西文印刷符号的全长或全宽。
在 GDI 的 CFont 部分,已经介绍了中文字号与英文磅数(相当于这里的 UnitPoint 点值) 的关系,表 14-1 列出了中文字号与几种主要 Unit 单位的关系(设 1 像素=1/54 英寸)。
表 14-1 中文字号与 Unit 单位的关系
汉字字号 | Pixel像素 | Point点 | Inch英寸 | Document文档 | Millimeter毫米 |
---|---|---|---|---|---|
特号 | 133.33 | 100 | 1.39 | 416.67 | 35.28 |
小特 | 80 | 60 | 0.83 | 250 | 21.17 |
初号 | 56 | 42 | 0.58 | 175 | 14.82 |
小初 | 48 | 36 | 0.5 | 150 | 12.7 |
一号 | 34.67 | 26 | 0.36 | 108.33 | 9.17 |
小一 | 32 | 24 | 0.33 | 100 | 8.47 |
二号 | 29.33 | 22 | 0.31 | 91.67 | 7.76 |
小二 | 24 | 18 | 0.25 | 75 | 6.35 |
三号 | 21.33 | 16 | 0.22 | 66.67 | 5.64 |
小三 | 20 | 15 | 0.21 | 62.5 | 5.29 |
四号 | 18.67 | 14 | 0.19 | 58.33 | 4.94 |
小四 | 16 | 12 | 0.17 | 50 | 4.23 |
五号 | 14 | 10.5 | 0.15 | 43.75 | 3.70 |
小五 | 12 | 9 | 0.125 | 37.5 | 3.175 |
六号 | 10 | 7.5 | 0.10 | 31.25 | 2.65 |
小六 | 8.67 | 6.5 | 0.09 | 27.08 | 2.29 |
七号 | 7.33 | 5.5 | 0.08 | 22.92 | 1.94 |
八号 | 6.67 | 5 | 0.07 | 20.83 | 1.76 |
例如(参见图 14-42):
1 | REAL fs[] = {100, 60, 42, 36, 26, 24, 22, 18, 16, 15, 14, 12, |
图 14-42 中文字号与 Unit 单位
1.8.2 绘制文本
在 GDI 中,我们用 CDC 类的方法 TextOut、DrawText 和 ExtTextOut 等来输出文本串。 在 GDI+中,我们则是利用 Graphics 类的重载方法 DrawString 来绘制文本。
(1)画串方法 DrawString
1 | Status DrawString(const WCHAR *string, INT length, const Font *font, const PointF &origin, const Brush *brush); |
这三个同名的 Graphics 类重载方法,都以宽字符串作为第一个输入参数(不支持普通 字符串)、串长为第二个参数(对以 null 结尾的字符串,可以使用-1 来代替)、最后一个参 数则都是绘制文本用的画刷指针。
不同的是第三个输入参数(都是浮点数版本,不支持整数版本):前两个方法的是浮点 数版的点类 PointF 对象,表示文本串的位置(默认是左上角);最后一个方法的是浮点数版 的矩形类 RectF 对象,表示绘制文本的范围(超出部分会被截掉)。
另 一 个不 同之 处是 ,后 两个 方法 比第 一个 方法 多了 一个 输入 参数 —— 串格 式 类 StringFormat 对象的指针,用于设置文本的对齐方式、输出方向、自动换行、制表符定制、 剪裁等。
第一个画串方法最简单,使用得也最多。例如:
1 | graph.DrawString(str, INT(wcslen(str)), &Font(L" 宋体 ", 12), PointF(10.0f, 10.0f), &brush); |
(2)串格式类 StringFormat
StringFormat 是从 Gdiplus Base 类直接派生的一个 GDI+类,用于设置绘制字符串时的各 种格式。其主要的构造函数为:
1 | StringFormat(INT formatFlags = 0, LANGID language = LANG_NEUTRAL); |
其中:
formatFlags(格式标志位)—— 用于设置各种输出格式,取值为 StringFormatFlags 枚举的下列常量之位或“|”:
1 | typedef enum { |
language (语言) —— 取值为 16 位 语 言 标识 符 类 型 LANGID , 默认 值为 LANG_NEUTRAL(语言中立),表示采用用户的默认语言。
(3)输出方向
默认的文本串输出方向是从左到右水平绘制。也可以在 StringFormat 类的构造函数中使 用参数值:StringFormatFlagsDirectionRightToLeft 和 StringFormatFlagsDirectionVertical 来修 改文本串的输出方向为从右到左水平绘制和从上到下垂直绘制。
(4)剪裁与换行
默认情况下,使用矩形输出长文本串时,会自动换行和剪裁。但是也可以在 StringFormatF 类的构造函数中,利用第一个输入参数的 StringFormatFlags 枚举值,来改变默认的设置。
(5)对齐
可以通过 StringFormat 类的方法来设置输出文本串的对齐方式:
1 | Status SetAlignment(StringAlignment align); // 设置水平对齐 |
其中的输入参数为枚举常量:
1 | typedef enum { |
1.8.3 美术字
下面介绍阴影、条纹、纹理、渐变、空心字和彩心字等绘制美术字的方法,它们是利用不同颜色、条纹和渐变的画刷,以及多次绘图的方式,来实现特定美术效果的。
(1)阴影字
可以使用两种不同颜色的画刷,经过在不同的为位置多次绘制同一文本串,就可以达到 输出阴影字的效果。例如(参见图 14-43):
1 | Graphics graph(pDC->m_hDC); |
1 | graph.DrawString(str, str.GetLength(), &font, PointF(d, 150 + d), &textBrush); |
图 14-43 阴影字
(2)条纹字
也可以直接利用条纹刷,来绘制条纹状的字符串。例如(参见图 14-44):
1 | Graphics graph(pDC->m_hDC); |
图 14-44 条纹字
(3)纹理字
还可以利用纹理(图像)刷来绘制纹理字符串。例如(参见图 14-45):
1 | Graphics graph(pDC->m_hDC); |
图 14-45 纹理字
(4)渐变字
当然,也可以利用线性渐变刷来绘制色彩变幻的字符串。例如,使用前面的多色渐变刷 代码,可以得到很好的变色效果(参见图 14-46):
1 | Graphics graph(pDC->m_hDC); |
图 14-46 渐变字
(5)空心字与彩心字
还可以利用 GDI+的路径和路径渐变刷,来绘制空心和彩心字符串。例如(参见图 14-47):
1 | Graphics graph(pDC->m_hDC); |
中,由于彩心字用到了随机颜色,所以每次刷新时的颜色都不一样。
图 14-47 普通、空心和彩心字符串
1.8.4 平滑处理与 ClearType 技术
为了提高文字的清晰度,需要对绘制的文本串进行平滑处理,防止在(特别是点阵)文 字被放大后出现明显的锯齿(马赛克 mosaic)现象。ClearType(清晰活字)是微软公司于 1999 年 4 月 7 日推出的一种图形显示技术,主要用于改善 LCD(Liquid Crystal Display,液 晶显示)显示器的显示效果,提高图形和文字的清晰度。
可以在 GDI+程序中,利用 Graphics 类的两个文本绘制提示(hint)方法:
1 | TextRenderingHint GetTextRender ingHint(VOID) const; |
来获取和设置文字绘制时的平滑处理方法。其中的枚举类型 TextRenderingHint 的定义为:
1 | typedef enum { |
这里的网格匹配(grid fit),主要是指在文本绘制时,通过调整字形的平直和垂直笔画的宽 度,以达到提高文字输出质量的一种方法。
例如(输出结果如图 14-48,为放大后的效果):
1 | Graphics graph(pDC->m_hDC); |
图 14-48 文字绘制时的平滑处理方式 细心的同学可能会发现,除了渐变和路径刷及平滑处理外,大多数文本输出功能,GDI
都有。而且 GDI 还可以以任意角度绘制文本串,但是 GDI+好像不能。其实,这可以利用 GDI+的矩阵变换来实现。矩阵变换的内容,会在后面的 14.2.8 小节介绍。
专用字体集
如果你想使用系统中还没有被安装的字体,有如下两种方法可供选择。
(1)手工安装字体
选择 Windows XP 操作系统的“../控制面板/字体”图标,启动“字体”程序; 然后再选择“文件/安装新字体”菜单项,打开“添加字体”对话框(参见图 14-49);选择 字体所在的文件目录,会出现目录中所有字体的名称和类型列表;选中想安装的字体后,按 确定关闭对话框。这样,就完成了字体的安装工作。将字体装进系统后,就可以和其他已装 入字体一样正常使用了。
图 14-49 添加字体对话框
(2)使用专用字体集
与已安装字体集类 InstalledFontCollection 一 样 , 专 用 ( 私 有 ) 字 体 集 类 PrivateFontCollection 也是字体集类 FontCollection 的派生类。
PrivateFontCollection 类只有一个构造一个空的字体集默认构造函数:
1 | PrivateFontCollection(VOID); |
但是可用方法 AddFontFile 来向字体集中添加字体文件:
1 | Status AddFontFile(const WCHAR* filename); |
将字体文件加入专用字体集后,我们就可以利用其父类的方法
1 | Status GetLastStatus(VOID); |
和 GetFamilyCount、GetFamilies 等,来判断装入是否成功、字体集中共有多少种字体、获 取指定数目的字体族 FontFamily 对象的数组(指针)。这些都与前面“显示当前系统已装入 的字体(族)名称”部分所讲的类似。包括用 FontFamily 类的 GetFamilyName 方法获取字 体名称,并用该名称来创建字体对象(使用带字体集参数的构造函数),最后绘制文本串。
下面的例子,使用汉鼎繁印篆和汉鼎繁特行两种专用字体,输出文本串“专用字体集: 字体名称”和诗句“三顾频烦天下计 两朝开济老臣心”(参见图 14-50)。这两种字体所对应的字体文件 HDZB_25.TTF 和 HDZB_16.TTF,已经放入我个人网页的资源子目录 res 中。
你也可以自己从网上下载其他字体文件来进行试验。
1 | // 装入专用字体文件 |
图 14-50 使用汉鼎繁印篆和汉鼎繁特行专用字体
复习思考题
1. GDI+与 GDI 是什么关系?
2. GDI+有哪两种封装?它们的基础是什么?
3. GDI+有哪些新增功能?
4. GDI+的绘图方式与 GDI 有什么不同?
5. 用 MFC 编写 GDI+程序需要做那些装备工作?对 VC08 SP1 及 VC10 版有什么不同?
6. 如何对 GDI+进行初始化和清除?需要特别注意什么?
7. 怎么解决 GDI+编程中 new 操作符问题?
8. GDI+的几何辅助类与 GDI 的有哪些不同?
9. 与 GDI 相比,GDI+的颜色哪些新内容?
10. 有多少颜色枚举常量?如何使用它们?GDI+的 Graphics 类与 GDI 的 MFC 封装中的什么类相似?怎样创建 Graphics 对象?
13. GDI+的绘图方法属于哪个类?
14. GDI+的画线方法的名称都有什么共同的前缀?
15. GDI+的画直线、矩形和椭圆的方法与 GDI 的对应方法有哪些不同?
16. GDI+中的画填充图的方法都有什么共同的前缀?有例外吗?
17. GDI+画填充多边形有哪两种填充模式?它们的差别在哪里?
18. GDI+提供了哪两种由控制点列画曲线的方法?它们有什么差别?
19. GDI+中有专门的清屏方法吗?GDI 中有没有?
20. 与 GDI 相比,GDI+中的笔增加了哪些新功能?
21. GDI+的条纹刷和纹理刷与 GDI 相比有哪些新加内容?
22. GDI+新增加了哪些画刷种类?
23. GDI+中有哪些字体类?
24. 在 VC08 SP1 及 VC10 中直接使用 Font 类会出现什么编译错误?如何解决?
25. 在 GDI+中如何使用字体?
26. GDI+使用什么方法绘制文本?
27. 如何实现阴影、条纹、纹理、渐变、空心和彩心等绘制美术字效果?
28. 如何使用专用字体集?
练习题
1. (选做题,例子)实现本章中的各例。
2. (正叶曲线)编写一个画填充正叶曲线的通用程序。可以指定叶片数、颜色、圆心和叶长(半径)等。(参见 14.7.2 的 2.和图 14-31)
3. (阴阳八卦图)编写一个绘制阴阳八卦图的通用程序。可指定各种颜色、圆心和半径等 等,参见图 14-51。
图 14-51 阴阳八卦图
4. (选做题,交互绘图)编写一个使用 GDI+接口的交互式绘图程序,实现 GDI+的所有 基本功能和各种新增加的功能。(提示,可以用 GDI 交互移动,用 GDI+成图。)
5. (选做题,补充绘图方法)编写若干(带不同类型输入参数的)画(线框和填充)弓弦 和 圆 角 矩 形 的 GDI+ 方法 ( DrawChord 、 FillChord 、 DrawRoundRectangle 和 FillRoundRectangle)。
6. (选做题,色块)绘制颜色枚举常量色块图。