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

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), &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 位图编程全攻略》一文(地址: [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的增强,某种意义上类似于MFCWindows API的整理和封装。作为一种良好的“生产工具”,它必将大大地促进开发时的“生产力”。




 本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/120755,如需转载请自行联系原作者


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值