GDI/GDI+介绍

1、GDI概述

  GDI在全称是Graphics Device Interface,即图形设备接口。是图形显示与实际物理设备之间的桥梁。

    GDI使得用户无需关心具体设备的细节,而只需在一个虚拟的环境(即逻辑设备)中进行操作。它的桥梁作用体现在:
  (1)用户通过调用GDI函数将逻辑空间的操作转化为具体针对设备驱动程序的调用。
  为实现图形设备无关性,Windows 的绘图操作在一个设备描述表上进行。用户拥有自己的"逻辑坐标"系统,它独立于实际的物理设备,与"设备坐标"相对应。开发Windows应用程序时,程序员关心的是逻辑坐标,我们在逻辑坐标系上绘图,利用GDI将逻辑窗口映射到物理设备上。
  (2)GDI能检测具体设备的能力,并依据具体的设备以最优方式驱动这些设备,完成真实的显示。
  GDI函数大致可分类为:设备上下文函数(如GetDC、CreateDC、DeleteDC)、 画线函数(如LineTo、Polyline、Arc)、填充画图函数(如Ellipse、FillRect、Pie)、画图属性函数(如SetBkColor、SetBkMode、SetTextColor)、文本、字体函数(如TextOut、GetFontData)、位图函数(如SetPixel、BitBlt、StretchBlt)、坐标函数(如DPtoLP、LPtoDP、ScreenToClient、ClientToScreen)、映射函数(如SetMapMode、SetWindowExtEx、SetViewportExtEx)、元文件函数(如PlayMetaFile、SetWinMetaFileBits)、区域函数(如FillRgn、FrameRgn、InvertRgn)、路径函数(如BeginPath、EndPath、StrokeAndFillPath)、裁剪函数(如SelectClipRgn、SelectClipPath)等。

GDI接口是基于函数,虽然使程序员省力不少,但是编程方式依然显得麻烦。例如显示一张位图,我们需要进行“创建位图,读取位图文件信息,启用场景设备,调色板变化“等一系列操作。然而有了GDI+,繁琐的步骤再次被简化。顾名思义,GDI+就是GDI的增强版,它是微软在Windows 2000以后操作系统中提供的新接口。

  2、GDI+概述

  GDI+主要提供以下三种功能:

  (1) 二维矢量图形:GDI+提供了存储图形基元自身信息的类(或结构体)、存储图形基元绘制方式信息的类以及实际进行绘制的类;

  (2) 图像处理:大多数图片都难以划定为直线和曲线的集合,无法使用二维矢量图形方式进行处理。因此,GDI+为我们提供了Bitmap、Image等类,它们可用于显示、操作和保存BMP、JPG、GIF等图像格式。

  (3) 文字显示:GDI+支持使用各种字体、字号和样式来显示文本。 相比于GDI,GDI+是基于C++类的对象化的应用程序接口,因此用起来更为简单。GDI的核心是设备上下文,GDI函数都依赖于设备上下文句柄,其编程方式是基于句柄的;GDI+无需时刻依赖于句柄或设备上下文,用户只需创建一个Graphics 对象,就可以用面向对象的方式调用其成员函数进行图形操作,编程方式是基于对象的。

  3、GDI绘制实例

  GDI在使用设备上下文绘制线条之前,必须先调用SelectObject 以使笔对象和设备上下文关联。其后,在设备上下文中绘制的所有线条均使用该笔,直到选择另一支不同的笔为止。 使用GDI画线代码如下

[cpp] view plain copy
// TODO: Add your command handler code here
CClientDC clientDC; //目标DC
CPen pen (PS_SOLID, 1, RGB(0, 0, 255));
clientDC.SelectObject(pen.GetSafeHandle()); //开始绘制
clientDC.MoveTo(0, 0);
clientDC.LineTo(rect.right, 0);
clientDC.SelectObject(oldObject);
  从上述代码可以看出:在GDI编程中,几乎所有的操作都围绕设备上下文dc展开。的确,这正是GDI编程的特点!设备上下文是 Windows 使用的一种结构,所有GDI操作前都需取得特定设备的上下文,函数中的CClientDC dc (this) 语句完成这一功能。 利用GDI进行图形、图像处理的一般操作步骤为:1. 取得指定窗口的DC。2. 确定使用的坐标系及映射方式。3. 进行图形、图像或文字处理。4. 释放所使用的DC。但是,在GDI+中,只需将Pen对象直接作为参数传递给Graphics类的DrawLine等方法即可,而不必使Pen对象与 Graphics对象关联。

  4、GDI+绘制实例 使用GDI+画线代码如下

[cpp] view plain copy
// TODO: Add your command handler code here
CClientDC clientDC (this);
//创建Graphics对象
Graphics graphics(clientDC);
//创建pen
Pen myPen;
myPen.SetWidth(1);
//画X轴
myPen.SetColor(Color::Blue);
graphics.DrawLine(&myPen, 0, 0, rect.right, 0);
 (1)创建 Graphics 对象:Graphics 对象表示GDI+绘图表面,是用于创建图形图像的对象。

 (2)使用 Graphics 对象绘制线条和形状、呈现文本或显示与操作图像。

 GDI+的相对与GDI而言,新增了一系列功能:渐变的画刷(Gradient Brushes)、基数样条函数(Cardinal Splines)、持久的路径对象(Persistent Path Objects)、变形和矩阵对象(Transformations &Matrix Object)、可伸缩区域(Scalable Regions)、Alpha混合(Alpha Blending)和丰富的图像格式支持等。下面,我们来逐个用实际代码实现GDI+的新增功能。

  4.1渐变的画刷 (GDI+提供了用于填充图形、路径和区域的线性渐变画刷和路径渐变画刷。线性渐变画刷使用渐变颜色来填充图形。当用路径渐变画刷填充图形时,可指定从图形的一部分移至另一部分时画刷颜色的变化方式。例如,我们可以只指定图形的中心颜色和边缘颜色,当画刷从图形中间向外边缘移动时,画刷会逐渐从中心颜色变化到边缘颜色。 )

[cpp] view plain copy
// TODO: Add your command handler code here
CClientDC clientDC (this);
CRect rect; GetClientRect(&rect);
//创建Graphics对象
Graphics graphics(clientDC);
//创建渐变画刷
LinearGradientBrush lgb(Point(0, 0), Point(rect.right, rect.bottom), Color::Blue, Color::Green);
//填充
graphics.FillRectangle(&lgb, 0, 0, rect.right, rect.bottom);
  4.2基数样条函数

  (基数样条指的是一连串单独的曲线,这些曲线连接起来形成一条较大的曲线。样条由点(Point结构体)的数组指定,并通过该数组中的每一个点。基数样条平滑地穿过数组中的每一个点(不出现尖角),因此比用直线连接创建的路径精确。)

  4.3变形和矩阵对象

  (GDI+提供了Matrix对象,它是一种可以使变形(旋转、平移、缩放等) 简易灵活的强大工具,Matrix对象需与要被变形的对象联合使用。对于GraphicsPath类,我们可以使用其成员函数Transform接收 Matrix参数用于变形。)

  4.4丰富的图像格式支持

  (GDI +提供了Image、Bitmap 和Metafile 类,方便用户进行图像格式的加载、操作和保存。GDI+支持的图像格式有BMP、GIF、JPEG、EXIF、PNG、TIFF、ICON、WMF、 EMF等,几乎涵盖了所有的常用图像格式。)

来源: http://www.educity.cn/develop/477410.html

双缓冲内存贴图防止闪烁的实现方法:
VC 双缓冲绘图

在图形图象处理编程过程中,双缓冲是一种基本的技术。我们知道,如果窗体在响应WM_PAINT消息的时候要进行复杂的图形处理,那么窗体在重绘时由于过频的刷新而引起闪烁现象。解决这一问题的有效方法就是双缓冲技术。

因为窗体在刷新时,总要有一个擦除原来图象的过程OnEraseBkgnd,它利用背景色填充窗体绘图区,然后在调用新的绘图代码进行重绘,这样一擦一写造成了图象颜色的反差。当WM_PAINT的响应很频繁的时候,这种反差也就越发明显。于是我们就看到了闪烁现象。

我们会很自然的想到,避免背景色的填充是最直接的办法。但是那样的话,窗体上会变的一团糟。因为每次绘制图象的时候都没有将原来的图象清除,造成了图象的残留,于是窗体重绘时,画面往往会变的乱七八糟。所以单纯的禁止背景重绘是不够的。我们还要进行重新绘图,但要求速度很快,于是我们想到了使用BitBlt函数。它可以支持图形块的复制,速度很快。我们可以先在内存中作图,然后用此函数将做好的图复制到前台,同时禁止背景刷新,这样就消除了闪烁。以上也就是双缓冲绘图的基本的思路。

一、普通方法:

先按普通做图的方法进行编程。即在视类的OnDraw函数中添加绘图代码。在此我们绘制若干同心圆,代码如下:

[cpp] view plain copy
CBCDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CPoint ptCenter;
CRect rect,ellipseRect;
GetClientRect(&rect);
ptCenter = rect.CenterPoint();
for(int i=20;i>0;i–)
{
ellipseRect.SetRect(ptCenter,ptCenter);
ellipseRect.InflateRect(i*10,i*10);
pDC->Ellipse(ellipseRect);
}
编译运行程序,尝试改变窗口大小,可以发现闪烁现象。

二、双缓冲方法:
在双缓冲方法中,首先要做的是屏蔽背景刷新。背景刷新其实是在响应WM_ERASEBKGND消息。我们在视类中添加对这个消息的响应,可以看到缺省的代码如下:

[cpp] view plain copy
BOOL CMYView::OnEraseBkgnd(CDC* pDC)
{
return CView::OnEraseBkgnd(pDC);
}
是调用父类的OnEraseBkgnd函数,我们屏蔽此调用,只须直接return TRUE;即可。

下面是内存缓冲作图的步骤。
[cpp] view plain copy
CPoint ptCenter;
CRect rect,ellipseRect;
GetClientRect(&rect);
ptCenter = rect.CenterPoint();
CDC dcMem; //用于缓冲作图的内存DC
CBitmap bmp; //内存中承载临时图象的位图
dcMem.CreateCompatibleDC(pDC); //依附窗口DC创建兼容内存DC
bmp.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());//创建兼容位图
dcMem.SelectObject(&bmp); //将位图选择进内存DC
//按原来背景填充客户区,不然会是黑色
dcMem.FillSolidRect(rect,pDC->GetBkColor());
for(int i=20;i>0;i–) //在内存DC上做同样的同心圆图象
{
ellipseRect.SetRect(ptCenter,ptCenter);
ellipseRect.InflateRect(i*10,i*10);
dcMem.Ellipse(ellipseRect);
}
pDC->BitBlt(0,0,rect.Width(),rect.Height(),
&dcMem,0,0,SRCCOPY);//将内存DC上的图象拷贝到前台
dcMem.DeleteDC(); //删除DC
bm.DeleteObject(); //删除位图

由于复杂的画图操作转入后台,我们看到的是速度很快的复制操作,自然也就消除了闪烁现象。
注意:

[cpp] view plain copy
bmp.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());
这里面CreateCompatibleBitmap第一个参数不能用dcMem,这样的话创建的是黑白位图。如果你要创建彩色位图,需要用pDC,它用来创建了内存DC. 详细请见下面的MSDN:
When a memory device context is created, it initially has a 1-by-1 monochrome bitmap selected into it. If this memory device context is used in CreateCompatibleBitmap, the bitmap that is created is a monochrome bitmap. To create a color bitmap, use the hDC that was used to create the memory device context, as shown in the following code:

[cpp] view plain copy
HDC memDC = CreateCompatibleDC ( hDC );
HBITMAP memBM = CreateCompatibleBitmap ( hDC, nWidth, nHeight );
SelectObject ( memDC, memBM );

在CScrollView中的双缓冲方法
双 缓冲绘图时,绘图用的到的坐标是是窗口的坐标,拷贝位图到目的DC时,也是从(0,0)开始拷贝的,把以我们创建位图大小就不能设为通过 GetClientRect()获得的视口大小,而要设为调用 SetScrollSizes(MM_TEXT,CSize(1280,800))时所设的窗口大小,这样绘图就不会有问题.

这样绘的图比较大,为了加快绘图速度,我们可以调用CDC::GetClipBox()获得刷新区域,判断我们要绘的图在不在刷新区域,如果不在就可以不画,画了也不会显示.

[cpp] view plain copy
// only paint the rect that needs repainting
CRect client;
pDC->GetClipBox(client);//边界类型
CRect rect = client;
DocToClient(rect);

if (!pDC->IsPrinting())//确定正在使用的设备上下文是否用于打印 ,不打印  
{  
    // draw to offscreen bitmap for fast looking repaints  
    if (dc.CreateCompatibleDC(pDC))//函数只适用于支持光栅操作的设备,创建的内存DC不能立刻使用  
    {  
        if (bitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()))// 创建一个内存Bitmap  
        {  
            OnPrepareDC(&dc, NULL);  
            pDrawDC = &dc;  

            // offset origin more because bitmap is just piece of the whole drawing  
            dc.OffsetViewportOrg(-rect.left, -rect.top);//相对于当前视区起点坐标修改视区起点  
            pOldBitmap = dc.SelectObject(&bitmap);  
            dc.SetBrushOrg(rect.left % 8, rect.top % 8);//指定选入设备上下文的下一个画刷的起点  

            // might as well clip to the same rectangle  
            dc.IntersectClipRect(client);//该函数创建了一个新的剪切区域,该区域是当前剪切区域和一个特定矩形的交集  
        }  
    }  
}  

// paint background  
CBrush brush;  
if (!brush.CreateSolidBrush(pDoc->GetPaperColor()))  
    return;  

brush.UnrealizeObject();  
pDrawDC->FillRect(client, &brush);  

if (!pDC->IsPrinting() && pDoc->GetGrid(szGrid))  
    DrawGrid(pDrawDC,szGrid);  

pDrawDC->BitBlt(0, 0,pDoc->m_size.cx,pDoc->m_size.cy,  
    pDoc->pMemDC, 0, 0, SRCCOPY);  
pDoc->Draw(pDrawDC, this);  
if (pDrawDC != pDC)  
{  
    pDC->SetViewportOrg(0, 0);  
    pDC->SetWindowOrg(0,0);  
    pDC->SetMapMode(MM_TEXT);  
    dc.SetViewportOrg(0, 0);  
    dc.SetWindowOrg(0,0);  
    dc.SetMapMode(MM_TEXT);  
    pDC->BitBlt(rect.left, rect.top, rect.Width(), rect.Height(),  
        &dc, 0, 0, SRCCOPY);  
    dc.SelectObject(pOldBitmap);  
}  

}
当然也可以将兼容位图设为视口大小,但是在画图时要判断出视口内要画哪部分图形,还要计算出绘图的偏移量,使用起来也不太方便.

来源: http://blog.csdn.net/guguarchieve/article/details/5610455

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值