Windows GDI和GDI+编程实例剖析 (2)

渐变的画刷

  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), &region);
 //区域变形
 Matrix matrix;
 matrix.Rotate(10.0f); //旋转顺时针20度
 matrix.Scale(1.0f, 0.3f); //拉伸
 region.Transform(&matrix); //应用变形
 //填充变形后的区域
 graphics.FillRegion(&SolidBrush(Color::Green), &region);
}


  上述程序中以蓝色填充一个三角形区域,接着将此区域旋转和拉伸,再次显示,其效果如图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位图编程全攻略 》一文中所介绍的用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的整理和封装。作为一种良好的"生产工具",它必将大大地促进开发时的"生产力"。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值