GDI+接口是Microsoft Whistler操作系统中的一部分,它是GDI的一个新版本,不仅在GDI基础上添加许多新特性而且对原有的GDI功能进行优化。在为开发人员提供的二维矢量图形、文本、图像处理、区域、路径以及图形数据矩阵等方面构造了一系列相关的类,如Bitmap(位图类)、Brush(画刷类)、Color(颜色类)、Font(字体类)、Graphics(图形类)、Image(图像类)、Pen(画笔类)和Region(区域类)等。其中,图形类Graphics是GDI+接口中的一个核心类,许多绘图操作都可用它来完成。
我们首先介绍一下GDI+的新特性以及其编程方式的改变,然后介绍用Visual C++.NET在基于对话框和单文档/多文档等应用程序中使用GDI+的一般方法。
GDI+新特性
GDI+与GDI相比,增加了下列新的特性:
1、渐变画刷
以往GDI实现颜色渐变区域的方法是通过使用不同颜色的线条来填充一个裁剪区域而达到的。现在GDI+拓展了GDI功能,提供线型渐变和路径渐变画刷来填充一个图形、路径和区域,甚至也可用来绘制直线、曲线等。这里的路径可以视为由各种绘图函数产生的轨迹。
2、样条曲线
对于曲线而言,最具实际意义的莫过于样条曲线。样条曲线是在生产实践的基础上产生和发展起来的。模线间的设计人员在绘制模线时,先按给定的数据将型值点准确地"点"到图板上。然后,采用一种称为"样条"的工具(一根富有弹性的有机玻璃条或木条),用压铁强迫它通过这些型值点,再适当调整这些压铁,让样条的形态发生变化,直至取得合适的形状,才沿着样条画出所需的曲线。如果我们把样条看成弹性细梁,那么压铁就可看成作用在这梁上的某些点上的集中力。GDI+的Graphics:: DrawCurve函数中就有一个这样的参数用来调整集中力的大小。除了样条曲线外,GDI+还支持原来GDI中的Bezier曲线。
3、持久的路径对象
我们知道,在GDI中,路径是隶属于一个设备环境(上下文),也就是说一旦设备环境指针超过它的有效期,路径也会被删除。而GDI+是使用Graphics对象来进行绘图操作,并将路径操作从Graphics对象分离出来,提供一个GraphicsPath类供用户使用。这就是说,我们不必担心路径对象会受到Graphics对象操作的影响,从而可以使用同一个路径对象进行多次的路径绘制操作。
4、矩阵和矩阵变换
在图形处理过程中常需要对其几何信息进行变换以便产生复杂的新图形,矩阵是这种图形几何变换最常用的方法。为了满足人们对图形变换的需求,GDI+提供了功能强大的Matrix类来实现矩阵的旋转、错切、平移、比例等变换操作,并且GDI+还支持Graphics图形和区域(Region)的矩阵变换。
5、Alpha混色
在图像处理中,Alpha用来衡量一个像素或图像的透明度。在非压缩的32位RGB图像中,每个像素是由四个部分组成:一个Alpha通道和三个颜色分量(R、G和B)。当Alpha值为0时,该像素是完全透明的,而当Alpha值为255时,则该像素是完全不透明。
Alpha混色是将源像素和背景像素的颜色进行混合,最终显示的颜色取决于其RGB颜色分量和Alpha值。它们之间的关系可用下列公式来表示:
显示颜色 = 源像素颜色 X alpha / 255 + 背景颜色 X (255 - alpha) / 255
GDI+的Color类定义了ARGB颜色数据类型,从而可以通过调整Alpha值来改变线条、图像等与背景色混合后的实际效果。
除了上述新特性外,GDI+还将支持重新着色、色彩修正、消除走样、元数据以及Graphics容器等特性。
GDI+编程模块的变化
为了简化GDI+的编程开发过程,Microsoft对GDI+的编程模块作了一些调整,这主要体现在以下几个方面:
1、不再使用设备环境或句柄
我们知道,在使用GDI绘图时,必须要指定一个设备环境(DC)。MFC为设备环境提供了许多由基类CDC派生的设备环境类,如CPaintDC、CClientDC和CWindowDC等,用来将某个窗口或设备与设备环境类的句柄指针关联起来,所有的绘图操作都与该句柄有关。而GDI+不再使用这个设备环境或句柄,取而代之是使用Graphics对象。
与设备环境相类似,Graphics对象也是将屏幕的某一个窗口与之相关联,并包含绘图操作所需要的相关属性。但是,只有这个Graphics对象与设备环境句柄还存在着联系,其余的如Pen、Brush、Image和Font等对象均不再使用设备环境。
2、绘图方式的变化
先来看看同样绘制一条从点(20, 10)到点(200, 100)直线的GDI和GDI+代码,假设这些代码都是添加在OnDraw函数中。
GDI绘制该直线的代码如下:
void CMyView::OnDraw(CDC* pDC)
{
CMyDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CPen newPen( PS_SOLID, 3, RGB(255, 0, 0) );
CPen* pOldPen = pDC->SelectObject( &newPen );
pDC->MoveTo( 20, 10 );
pDC->LineTo( 200, 100);
pDC->SelectObject( pOldPen );
}
GDI+绘制该直线的代码如下:
void CMyView::OnDraw(CDC* pDC)
{
CMyDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
using namespace Gdiplus; // 使用名称空间
Graphics graphics( pDC->m_hDC );
Pen newPen( Color( 255, 0, 0 ), 3 );
graphics.DrawLine(&newPen, 20, 10, 200, 100);
}
从上面代码可以看出,GDI先创建一个CPen(画笔)对象,然后通过SelectObject将该画笔选入到设备环境(pDC)中。接下来调用相应的画线函数,最后恢复设备环境中原来的GDI对象。而GDI+是先使用Graphics类创建一个与pDC设备环境相关联的Graphics对象,然后使用Pen类进行画笔的创建,最后调用相应的画线方法。由于Pen和设备环境是相互独立的,因而不需要像GDI那样恢复设备环境中原来的设置,而且Pen和Graphics对象的创建不存在先后次序。
2、Graphics绘图方法直接将Pen、Brush等对象作为自己的参数
从上面的代码可以看出,Graphics绘图方法直接将Pen对象作为自己的参数,从而避免了在GDI使用SelectObject进行繁琐的切换,类似的还有Brush、Path、Image和Font等。
3、不再使用"当前位置"
我们知道,GDI绘图操作(如画线)中总存在一个被称为"当前位置"的特殊位置。每次画线都是以此当前位置为起始点,画线操作结束之后,直线的结束点位置又成为了当前位置。设置当前位置的理由是为了提高画线操作的效率,因为在一些场合下,总是一条直线连着另一条直线,首尾相接。有了当前位置的自动更新,就可避免每次画线时都要给出两点的坐标。尽管有其必要性,但是单独绘制一条直线的场合总是比较多的,因此GDI+取消这个"当前位置"以避免当无法确定"当前位置"时所造成的绘图的差错,取而代之的是直接在DrawLine中指定直线起止点的坐标。
4、形状轮廓绘制和填充采用不同的方法
GDI总是让形状轮廓绘制和填充使用同一个绘图函数,例如Rectangle。我们知道,轮廓绘制需要一个画笔,而填充一个区域需要一个画刷。也就是说,不管我们是否需要填充所绘制的形状,我们都需要指定一个画刷,否则GDI采用默认的画刷进行填充。这种方式确实给我们带来了许多不便,现在GDI+将形状轮廓绘制和填充操作分开而采用不同的方法,例如DrawRectangle和FillRectangle分别用来绘制和填充一个矩形。
5、简化区域的创建
我们知道,GDI提供了许多区域创建函数,如CreateRectRgn、CreateEllpticRgn、CreateRoundRectRgn、CreatePolygonRgn和CreatePolyPolygonRgn等。诚然,这些函数给我们带来了许多方便。但在GDI+中,由于为了便于将区域引入矩阵变换操作,GDI+简化一般区域创建的方法,而将更复杂的区域创建交由Path接管。由于Path对象是与设备环境分离开来的,因而可以直接在Region构造函数中加以指定。
6、用Visual C++.NET使用GDI+的一般方法
在Visual C++.NET使用GDI+一般遵循下列步骤:
(1) 在应用程序中添加GDI+的包含文件gdiplus.h以及附加的类库gdiplus.lib。通常gdiplus.h包含文件添加在应用程序的stdafx.h文件中,而gdiplus.lib可用两种进行添加:第一种是直接在stdafx.h文件中添加下列语句:
#pragma comment( lib, "gdiplus.lib" ) |
另一种方法是:选择"项目"ò"属性"菜单命令,在弹出的对话框中选中左侧的"链接器"ò"输入"选项,在右侧的"附加依赖项"框中键入gdiplus.lib,结果如图1所示。
图1
(2) 在应用程序项目的应用类中,添加一个成员变量,如下列代码:
ULONG_PTR m_gdiplusToken; |
其中,ULONG_PTR是一个DWORD数据类型,该成员变量用来保存GDI+被初始化后在应用程序中的GDI+标识,以便能在应用程序退出后,引用该标识来调用Gdiplus:: GdiplusShutdown来关闭GDI+。
(3) 在应用类中添加ExitInstance的重载,并添加下列代码用来关闭GDI+:
int CEx_GDIPlusApp::ExitInstance() { Gdiplus::GdiplusShutdown(m_gdiplusToken); return CWinApp::ExitInstance(); } |
(4) 在应用类的InitInstance函数中添加GDI+的初始化代码:
BOOL CEx_GDIPlusApp::InitInstance() { CWinApp::InitInstance(); Gdiplus::GdiplusStartupInput gdiplusStartupInput; Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL); ... } |
(5) 在需要绘图的窗口或视图类中添加GDI+的绘制代码。
下面分别就单文档和基于对话框应用程序为例,说明使用GDI+的一般过程和方法。
1. 在单文档应用程序中使用GDI+
在上面的过程中,我们就是以一个单文档应用程序Ex_GDIPlus作为示例的。下面列出第5步所涉及的代码:
void CEx_GDIPlusView::OnDraw(CDC* pDC) { CEx_GDIPlusDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); using namespace Gdiplus; Graphics graphics( pDC->m_hDC ); Pen newPen( Color( 255, 0, 0 ), 3 ); HatchBrush newBrush( HatchStyleCross, Color(255, 0, 255, 0), Color(255, 0, 0, 255)); // 创建一个填充画刷,前景色为绿色,背景色为蓝色 graphics.DrawRectangle( &newPen, 50, 50, 100, 60); // 在(50,50)处绘制一个长为100,高为60的矩形 graphics.FillRectangle( &newBrush, 50, 50, 100, 60); // 在(50,50)处填充一个长为100,高为60的矩形区域 } |
编译并运行,结果如图2所示。
图2
2. 在基于对话框应用程序中使用GDI+
步骤如下:
(1) 创建一个默认的基于对话框的应用程序Ex_GDIPlusDlg。
(2) 打开stdafx.h文件添加下列代码:
#include <gdiplus.h> #pragma comment( lib, "gdiplus.lib" ) |
(3) 打开Ex_GDIPlusDlg.h文件,添加下列代码:
class CEx_GDIPlusDlgApp : public CWinApp { ... public: virtual BOOL InitInstance(); ULONG_PTR m_gdiplusToken; ... }; |
(4) 在 CEx_GDIPlusDlgApp类的属性窗口中,单击"重写"工具按钮,为该添加ExitInstance的重载:
int CEx_GDIPlusDlgApp::ExitInstance() { Gdiplus::GdiplusShutdown(m_gdiplusToken); return CWinApp::ExitInstance(); } |
(5) 定位到CEx_GDIPlusDlgApp::InitInstance函数处,添加下列GDI+初始化代码:
BOOL CEx_GDIPlusDlgApp::InitInstance() { CWinApp::InitInstance(); Gdiplus::GdiplusStartupInput gdiplusStartupInput; Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL); ... } |
(6) 定位到CEx_GDIPlusDlgDlg::OnPaint函数处,添加下列GDI+代码:
void CEx_GDIPlusDlgDlg::OnPaint() { if (IsIconic()) { ... } else { CPaintDC dc(this); // 用于绘制的设备上下文 using namespace Gdiplus; Graphics graphics( dc.m_hDC ); Pen newPen( Color( 255, 0, 0 ), 3 ); HatchBrush newBrush( HatchStyleCross, Color(255, 0, 255, 0), Color(255, 0, 0, 255)); graphics.DrawRectangle( &newPen, 50, 50, 100, 60); graphics.FillRectangle( &newBrush, 50, 50, 100, 60); CDialog::OnPaint(); } } |
(7) 编译并运行,结果如图3所示。
图3
从上述例子可以看出,只要能获得一个窗口的设备环境指针,就可构造一个Graphics对象,从而可以在其窗口中进行绘图,我们不必在像以往那样使用Invalidate/UpdateWindow来防止Windows对对话框窗口进行重绘。