4.GDI+
编程
“
GDI+
”菜单下的“画线”子菜单单击事件消息处理函数的代码如下:
void CGdiexampleDlg::OnGdipDrawLine()
{
// TODO: Add your command handler code here
CClientDC dc(this);
//
逻辑坐标与设备坐标变换
CRect rect;
GetClientRect(&rect);
dc.SetMapMode(MM_ANISOTROPIC);
dc.SetWindowOrg(0, 0);
dc.SetWindowExt(rect.right, rect.bottom);
dc.SetViewportOrg(0, rect.bottom / 2);
dc.SetViewportExt(rect.right, - rect.bottom);
//
创建
Graphics
对象
Graphics graphics(dc);
//
创建
pen
Pen myPen(Color::Red);
myPen.SetWidth(1);
//
画正旋曲线
for (int i = 0; i < rect.right; i++)
{
graphics.DrawLine(&myPen, i, 100 *sin(2 *(i / (rect.right / 5.0)) *PI), i +
1, 100 *sin(2 *((i + 1) / (rect.right / 5.0)) *PI));
}
//
画
X
轴
myPen.SetColor(Color::Blue);
graphics.DrawLine(&myPen, 0, 0, rect.right, 0);
}
由于我们使用的是
Visual C++6.0
而非
VS.Net
,我们需要下载微软的
GDIPLUS
支持包。在微软官方网站下载时需认证
Windows
为正版,我们可从这个地址下载:
[url]http://www.codeguru.com/code/legacy/gdi/GDIPlus.zip[/url]
。一个完整的
GDI+
支持包至少包括如下文件:
(
1
)头文件:
gdiplus.h
(
2
)动态链接库的
.lib
文件:
gdiplus.lib
(
3
)动态链接库的
.dll
文件:
gdiplus.dll
少了(
1
)、(
2
)程序不能编译,少了(
3
)程序能以共享
DLL
的方式编译但是不能运行,运行时找不到
.dll
文件。
为使得
Visual C++6.0
支持
GDI+
,我们需要在使用
GDI+
对象的文件的开头添加如下代码:
#define UNICODE
#ifndef ULONG_PTR
#define ULONG_PTR unsigned long*
#endif
#include "c:\gdiplus\includes\gdiplus.h"
using namespace Gdiplus;
#pragma comment(lib, "c:\\gdiplus\\lib\\gdiplus.lib")
在
Visual C++
中使用
GDI+
必须先进行
GDI+
的初始化,我们在
CWinApp
派生类的
InitInstance
函数中进行此项工作是最好的:
/////////////////////////////////////////////////////////////////////////////
// CGdiexampleApp initialization
BOOL CGdiexampleApp::InitInstance()
{
AfxEnableControlContainer();
// Standard initialization
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
//
初始化
gdiplus
的环境
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
//
初始化
GDI+.
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
CGdiexampleDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK){}
else if (nResponse == IDCANCEL){}
//
关闭
gdiplus
的环境
GdiplusShutdown(gdiplusToken);
return FALSE;
}
单击“
GDI+
”菜单下的“画线”子菜单,也会出现如图
1
所示的效果。
观察
void CGdiexampleDlg::OnGdipDrawLine()
函数,我们发现用
GDI+
进行图形、图像操作的步骤为:
(
1
)创建
Graphics
对象:
Graphics
对象表示
GDI+
绘图表面,是用于创建图形图像的对象;
(
2
)使用
Graphics
对象绘制线条和形状、呈现文本或显示与操作图像。
Graphics
对象是
GDI+
的核心,
GDI
中设备上下文
dc
和
Graphics
对象的作用相似,但在
GDI
中使用的是基于句柄的编程模式,而
GDI+
中使用的则是基于对象的编程模式。
Graphics
封装了
GDI+
绘图面,而且此类无法被继承,它的所有成员函数都不是虚函数。
下面,我们来逐个用实际代码实现
GDI+
的新增功能,这些新增功能包括:渐变的画刷
(Gradient Brushes)
、基数样条函数
(Cardinal Splines)
、持久的路径对象
(Persistent Path Objects)
、变形和矩阵对象
(Transformations &Matrix Object)
、可伸缩区域
(Scalable Regions)
、
Alpha
混合
(Alpha Blending)
和丰富的图像格式支持等。
渐变的画刷
GDI+
提供了用于填充图形、路径和区域的线性渐变画刷和路径渐变画刷。
线性渐变画刷使用渐变颜色来填充图形。
当用路径渐变画刷填充图形时,可指定从图形的一部分移至另一部分时画刷颜色的变化方式。例如,我们可以只指定图形的中心颜色和边缘颜色,当画刷从图形中间向外边缘移动时,画刷会逐渐从中心颜色变化到边缘颜色。
void CGdiexampleDlg::OnGradientBrush()
{
// TODO: Add your command handler code here
CClientDC dc(this);
CRect rect;
GetClientRect(&rect);
//
创建
Graphics
对象
Graphics graphics(dc);
//
创建渐变画刷
LinearGradientBrush lgb(Point(0, 0), Point(rect.right, rect.bottom), Color
::Blue, Color::Green);
//
填充
graphics.FillRectangle(&lgb, 0, 0, rect.right, rect.bottom);
}
本程序使用线性渐变画刷,当画刷从客户区左上角移向客户区右下角的过程中,颜色逐渐由蓝色转变为绿色。
图
3 GDI+
渐变画刷
基数样条函数
GDI+
支持基数样条,基数样条指的是一连串单独的曲线,这些曲线连接起来形成一条较大的曲线。样条由点(
Point
结构体)的数组指定,并通过该数组中的每一个点。基数样条平滑地穿过数组中的每一个点(不出现尖角),因此比用直线连接创建的路径精确。
void CGdiexampleDlg::OnCardinalSpline()
{
// TODO: Add your command handler code here
CClientDC dc(this);
//
创建
Graphics
对象
Graphics graphics(dc);
Point points[] =
{
Point(0, 0), Point(100, 200), Point(200, 0), Point(300, 200), Point(400, 00)
};
//
直接画线
for (int i = 0; i < 4; i++)
{
graphics.DrawLine(&Pen(Color::Blue, 3), points[i], points[i + 1]);
}
//
利用基数样条画线
graphics.DrawCurve(&Pen(Color::Red, 3), points, 5);
}
图
4
演示了直接连线和经过基数样条平滑拟合后的线条的对比,后者的曲线(
Curve
)没有尖角。这个工作我们在中学的数学课上把离散的点连接成曲线时做过。
图
4 GDI+
基数样条
持久的路径对象
在
GDI
中,路径隶属于一个设备上下文,一旦设备环境指针超过它的生存期,路径也会被删除。利用
GDI+
,可以创建并维护与
Graphics
对象分开的
GraphicsPath
对象,它不依赖于
Graphics
对象的生存期。
变形和矩阵对象
GDI+
提供了
Matrix
对象,它是一种可以使变形
(
旋转、平移、缩放等
)
简易灵活的强大工具,
Matrix
对象需与要被变形的对象联合使用。对于
GraphicsPath
类,我们可以使用其成员函数
Transform
接收
Matrix
参数用于变形。
void CGdiexampleDlg::OnTransformationMatrix()
{
// TODO: Add your command handler code here
CClientDC dc(this);
//
创建
Graphics
对象
Graphics graphics(dc);
GraphicsPath path;
path.AddRectangle(Rect(250, 20, 70, 70));
graphics.DrawPath(&Pen(Color::Black, 1), &path); //
在应用变形矩阵之前绘制矩形
//
路径变形
Matrix matrix1, matrix2;
matrix1.Rotate( 45.0f); //
旋转顺时针
45
度
path.Transform(&matrix1); //
应用变形
graphics.DrawPath(&Pen(Color::Red, 3), &path);
matrix2.Scale( 1.0f, 0.5f); //
转化成为平行四边形法则
path.Transform(&matrix2); //
应用变形
graphics.DrawPath(&Pen(Color::Blue, 3), &path);
}
图
5
演示了正方形经过旋转和拉伸之后的效果:黑色的为原始图形,红色的为旋转
45
度之后的图形,蓝色的为经过拉伸为平行四边形后的图形。
图
5 GDI+
变形和矩阵对象
可伸缩区域
GDI+
通过对区域(
Region
)的支持极大地扩展了
GDI
。在
GDI
中,区域存储在设备坐标中,可应用于区域的唯一变形是平移。但是在
GDI +
中,区域存储在全局坐标(世界坐标)中,可对区域利用变形矩阵进行变形
(
旋转、平移、缩放等
)
。
void CGdiexampleDlg::OnScalableRegion()
{
// TODO: Add your command handler code here
CClientDC dc(this);
//
创建
Graphics
对象
Graphics graphics(dc);
//
创建
GraphicsPath
GraphicsPath path;
path.AddLine(100, 100, 150, 150);
path.AddLine(50, 150, 150, 150);
path.AddLine(50, 150, 100, 100);
//
创建
Region
Region region(&path);
//
填充区域
graphics.FillRegion(&SolidBrush(Color::Blue), ®ion);
//
区域变形
Matrix matrix;
matrix.Rotate( 10.0f); //
旋转顺时针
20
度
matrix.Scale( 1.0f, 0.3f); //
拉伸
region.Transform(&matrix); //
应用变形
//
填充变形后的区域
graphics.FillRegion(&SolidBrush(Color::Green), ®ion);
}
上述程序中以蓝色填充一个三角形区域,接着将此区域旋转和拉伸,再次显示,其效果如图
6
。
图
6 GDI+
区域变形
丰富的图像格式支持
GDI +
提供了
Image
、
Bitmap
和
Metafile
类,方便用户进行图像格式的加载、操作和保存。
GDI+
支持的图像格式有
BMP
、
GIF
、
JPEG
、
EXIF
、
PNG
、
TIFF
、
ICON
、
WMF
、
EMF
等,几乎涵盖了所有的常用图像格式。
void CGdiexampleDlg::OnImage()
{
// TODO: Add your command handler code here
CClientDC dc(this);
//
创建
Graphics
对象
Graphics graphics(dc);
Image image(L "d:\\1.jpg");
//
在矩形区域内显示
jpg
图像
Point destPoints1[3] =
{
Point(10, 10), Point(220, 10), Point(10, 290)
};
graphics.DrawImage(&image, destPoints1, 3);
//
在平行四边形区域内显示
jpg
图像
Point destPoints2[3] =
{
Point(230, 10), Point(440, 10), Point(270, 290)
};
graphics.DrawImage(&image, destPoints2, 3);
}
上述程序将
D
盘根目录下文件名为“
1.jpg
”的
jpg
图像以矩阵和平行四边形两种方式显示,效果如图
7
。
图
7 GDI+
多种图像格式支持
由此我们可以看出,
GDI+
在图像显示和操作方面的确比
GDI
简单许多。回忆我们在《
Visual C++
中
DDB
与
DIB
位图编程全攻略》一文(地址:
[url]http://dev.yesky.com/72/2150572.shtml?412[/url]
)中所介绍的用
GDI
显示位图的方式,其与
GDI+
图像处理的难易程度真是有天壤之别。
Alpha
混合
Alpha
允许将两个物体混合起来显示,在
3D
气氛和场景渲染等方面有广泛应用。它能“雾化”图像,使得一个图像着色在另一个半透明的图像上,呈现一种朦胧美。我们知道,一个像素可用
R
,
G
,
B
三个维度来表示,我们可以再加上第
4
个即:
Alpha
维度(
channel
),表征透明程度。
void CGdiexampleDlg::OnAlphaBlend()
{
// TODO: Add your command handler code here
CClientDC dc(this);
//
创建
Graphics
对象
Graphics graphics(dc);
//
创建
ColorMatrix
ColorMatrix ClrMatrix =
{
1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.5f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f, 1.0f
};
//
将
ColorMatrix
赋给
ImageAttributes
ImageAttributes ImgAttr;
ImgAttr.SetColorMatrix(&ClrMatrix, ColorMatrixFlagsDefault,
ColorAdjustTypeBitmap);
//
在矩形区域内显示
jpg
图像
Image img1(L "d:\\1.jpg");
Point destPoints1[3] =
{
Point(10, 10), Point(220, 10), Point(10, 290)
};
graphics.DrawImage(&img1, destPoints1, 3);
//Alpha
混合
Image img2(L "d:\\2.jpg");
int width, height;
width = img2.GetWidth();
height = img2.GetHeight();
graphics.DrawImage(&img2, RectF(10, 10, 210, 280), 0, 0, width, height,
UnitPixel, &ImgAttr);
//
在平行四边形区域内显示
jpg
图像
Point destPoints2[3] =
{
Point(230, 10), Point(440, 10), Point(270, 290)
};
graphics.DrawImage(&img1, destPoints2, 3);
//Alpha
混合
graphics.DrawImage(&img2, destPoints2, 3, 0, 0, width, height, UnitPixel,
&ImgAttr);
}
上述程序中将
D
盘根目录下文件名为“
1.jpg
”的图像以矩阵和平行四边形两种方式显示,然后将文件名为为“
2.jpg
”的图像与之进行混合,其效果如图
8
。
图
8 GDI+ Alpha
混合
为了能进行
Alpha
混合,我们需要使用
ImageAttributes
类和
ColorMatrix
矩阵,
ImageAttributes
可以进行颜色、灰度等调整从而达到控制图像着色方式的目的。
ColorMatrix
是
ImageAttributes
类大多数函数的参数,它包含了
Alpha
、
Red
、
Green
、
Blue
维度的值,以及另一维
w
,顺序为
RGBaw
。
CGdiexampleDlg::OnAlphaBlend()
函数中
ColorMatrix
的实例
ClrMatrix
中元素(
4,4
)的值为
0.5
,表示
Alpha
度的值为
0.5
(即半透明)。在
ColorMatrix
中,元素(
5,5
)的值恒定为
1.0
。我们把
ClrMatrix
的元素(
0,0
)修改为
0.0
,即使得图像
2.jpg
的红色维度全不显示,再看效果,为图
9
。列位读者,我们以前在豪杰超级解霸中调整
R
,
G
,
B
值从而控制图像输出颜色的时候,调的就是这个东东!图
9
的效果很像破旧彩色电视机,红色电子枪“嗝”了。刚大学毕业时,俺那个叫穷啊,就买了这么个电视机,还看得很爽,真是往事不堪回首!
图
9 GDI+
中的
ColorMatrix
强大的文字输出
GDI+
拥有极其强大的文字输出处理能力,输出文字的颜色、字体、填充方式都可以直接作为
Graphics
类
DrawString
成员函数的参数进行设置,其功能远胜过
GDI
设备上下文的
TextOut
函数。
void CGdiexampleDlg::OnText()
{
// TODO: Add your command handler code here
CClientDC dc(this);
//
创建
Graphics
对象
Graphics graphics(dc);
//
创建
20
号
"
楷体
"
字体
FontFamily fontFamily1(L "
楷体
_GB2312"); //
定义
"
楷体
"
字样
Font font1(&fontFamily1, 20, FontStyleRegular, UnitPoint);
//
定义输出
UNICODE
字符串
WCHAR string[256];
wcscpy(string, L "
天极网的读者朋友,您好!
");
//
以蓝色画刷和
20
号
"
楷体
"
显示字符串
graphics.DrawString(string, (INT)wcslen(string), &font1, PointF(30, 10),
&SolidBrush(Color::Blue));
//
定义字符串显示画刷
LinearGradientBrush linGrBrush(Point(30, 50), Point(100, 50), Color(255, 255,
0, 0), Color(255, 0, 0, 255));
//
以线性渐变画刷和创建的
20
号
"
楷体
"
显示字符串
graphics.DrawString(string, (INT)wcslen(string), &font1, PointF(30, 50),
&linGrBrush);
//
创建
20
号
"
华文行楷
"
字体
FontFamily fontFamily2(L "
华文行楷
"); //
定义
"
楷体
"
字样
Font font2(&fontFamily2, 20, FontStyleRegular, UnitPoint);
//
以线性渐变画刷和
20
号
"
华文行楷
"
显示字符串
graphics.DrawString(string, (INT)wcslen(string), &font2, PointF(30, 90),
&linGrBrush);
//
以图像创建画刷
Image image(L "d:\\3.jpg");
TextureBrush tBrush(&image);
//
以图像画刷和
20
号
"
华文行楷
"
显示字符串
graphics.DrawString(string, (INT)wcslen(string), &font2, PointF(30, 130),
&tBrush);
//
创建
25
号
"
华文中宋
"
字体
FontFamily fontFamily3(L "
华文中宋
"); //
定义
"
楷体
"
字样
Font font3(&fontFamily2, 25, FontStyleRegular, UnitPoint);
//
以图像画刷和
20
号
"
华文行楷
"
显示字符串
graphics.DrawString(string, (INT)wcslen(string), &font3, PointF(30, 170),
&tBrush);
}
上述代码的执行效果如图
10
所示,字体、颜色和填充都很丰富!
图
10 GDI+
文本输出
5.GDI
与
GDI+
的比较
GDI+
相对
GDI
而言主要在编程方式上发生了巨大的改变。
GDI
的核心是设备上下文,
GDI
函数都依赖于设备上下文句柄,其编程方式是基于句柄的;
GDI+
无需时刻依赖于句柄或设备上下文,用户只需创建一个
Graphics
对象,就可以用面向对象的方式调用其成员函数进行图形操作,编程方式是基于对象的。
GDI
在使用设备上下文绘制线条之前,必须先调用
SelectObject
以使钢笔对象和设备上下文关联。其后,在设备上下文中绘制的所有线条均使用该钢笔,直到选择另一支不同的钢笔为止。
CGdiexampleDlg::OnGdiDrawLine
函数中的下列语句完成的就是这个功能:
//
创建绘制正旋曲线的
pen
并将其选入设备上下文
CPen pen(PS_SOLID,1,RGB(255,0,0));
HGDIOBJ oldObject = dc.SelectObject(pen.GetSafeHandle());
…
//
创建绘制
x
轴的
pen
并将其选入设备上下文
CPen penx(PS_SOLID,1,RGB(0,0,255));
dc.SelectObject(penx.GetSafeHandle());
…
//
恢复原先的
pen
dc.SelectObject(oldObject);
但是,在
GDI+
中,只需将
Pen
对象直接作为参数传递给
Graphics
类的
DrawLine
等方法即可,而不必使
Pen
对象与
Graphics
对象关联,例如
CGdiexampleDlg::OnGdipDrawLine
函数中的下列语句:
Pen myPen(Color::Red);
myPen.SetWidth(1);
…
graphics.DrawLine(&myPen,i,100*sin(2*(i/(rect.right/5.0))*PI),
i+1,100*sin(2*((i+1)/(rect.right/5.0))*PI));
…
graphics.DrawLine(&myPen,0,0,rect.right,0);
GDI
中有当前位置的概念,所以在使用
GDI
绘制线条前应该先使用
MoveTo
移动当前位置,再使用
LineTo
画线,例如:
//
绘制正旋曲线
dc.MoveTo(0,0) ;
for(int i=0;i<rect.right;i++)
{
dc.LineTo(i,100*sin(2*(i/(rect.right/5.0))*PI));
}
而
GDI+
中则没有当前位置的概念,画线函数中可以直接指定起点和终点,例如:
graphics.DrawLine(&myPen,0,0,rect.right,0);
6.
结论
鉴于
GDI+
良好的易用性和其具有的强大功能,我们建议尽快抛弃
GDI
编程方式,因为我们没有必要将时间浪费在无意义的重复代码的设计上。
GDI+
对
GDI
的增强,某种意义上类似于
MFC
对
Windows API
的整理和封装。作为一种良好的“生产工具”,它必将大大地促进开发时的“生产力”。