## 1.2 GDI+的特色
本节介绍 GDI+的几个主要新增特性与功能,说明 GDI+在编程模式上的改变。
### 1.2.1 GDI+新增特性
与 GDI 相比,GDI+新增的特性主要有渐变画刷、样条和贝塞尔曲线、持久路径、矩阵 变换、伸缩区域、α 混色和对多种图像格式的支持。
(1)渐变画刷
![image](https://box.kancloud.cn/2016-04-18_57144a7d06772.jpg)
GDI+中新增加的渐变画刷(gradient brush,梯度刷),通过提供用于填充图形、路径和 区域的颜色线性渐变和路径渐变的画刷,扩展了 GDI 的功能。渐变画刷可用于绘制直线、 曲线和路径,参见图 14-4。
![image](https://box.kancloud.cn/2016-04-18_57144a7d1bd64.jpg)
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 显示一 个区域在执行三种变换(缩放、旋转和平移)前后的情况。
![image](https://box.kancloud.cn/2016-04-18_57144a7d2b8dc.jpg)
图 14-6 路径变换
![image](https://box.kancloud.cn/2016-04-18_57144a7d3dfec.jpg)
图 14-7 区域变换
![image](https://box.kancloud.cn/2016-04-18_57144a7d6f555.jpg)
图 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 对象,然后用熟悉的面向对象方式来调用其中的各种绘图方法,例如:
```
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 句柄作为输入参数了。
```
CDC *pDC = GetDC(); // 获取 DC 对象
CPen pen(PS_SOLID, 3, RGB(255, 0, 0)); // 创建笔
pDC->SelectObject(&pen); // 选笔入 DC
pDC->MoveTo(20, 10); // 将当前点移到直线的起点
pDC->LineTo(200, 100); // 从当前点画线到直线的终点
```
2)用 GDI+画线
利用 MFC 进行 GDI+绘图,步骤与 API 的差不多。只是代码改在 OnDraw 函数中,而且获取 DC 句柄的方法不同。
```
CDC *pDC = GetDC(); // 获取 DC 对象
Graphics myGraphics(pDC->m_hDC); // 利用 DC 句柄创建图形对象
Pen myPen(Color(255, 0 , 0), 3); // 创建笔
myGraphics.DrawLine(&myPen, 20, 10, 200, 100); // 画直线
```
(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 方法时也不会被破坏,因此,在一些单独的区域中, 可以将给定的路径作为构造块使用。例如:
```
Region region1(rect1);
Region region2(rect2);
region1.Union(onePath);
region2.Intersect(onePath);
```