双缓冲绘图

来源:互联网 发布:327国债事件 知乎 编辑:程序博客网 时间:2024/06/05 18:47

双缓冲绘图
更新日期:2011-8-28
测试环境:VS2008+WinXP

━━━━━━━━━━━━━━━━━━━━━━━━


一:新建一个对话框工程。在OnButton1()里添加以下代码。(WIN32 API版本)
━━━━━━━━━━━━━━━━━━━━━━━━
/***************************************************
更新于:2011-8-28
双缓冲原理简介:
虽然代码有点“繁琐”,但记得我们关心的主要是下面
代码中hMemDC。所有绘图函数使用这个内存dc句柄即可。

我们经常不是这样调用的吗:
    //屏幕上打印字体,画矩形
    HDC hdc=::GetDC(m_hWnd);
    ::TextOut(hdc.......);
    ::RectangleRect(hdc......);
    ::ReleaseDC(m_hWnd,hdc) ;

而在双缓冲里,只需把所有需要用到屏幕hdc的函数中的hdc替换成hMemDC,如:
    //内存dc上打印字体,画矩形
    ::TextOut(hMemDC.......);
    ::RectangleRect(hMemDC......);

画好后再调用BitBlt把内存hMemDC所有画好的东西拷贝到屏幕hScrDC
    BitBlt(hScrDC,0,60,1000,1000,hMemDC,0,0,SRCCOPY);
**************************************************/

/***************************************************
步骤一:创建。
1.主要关心的是CreateCompatibleBitmap的后两个参数,
你往内存hMemDC里画的东东就在这个这个范围内
2.如果你已经有一个屏幕dc了,就去掉
HDC hScrDC=::GetDC(m_hWnd)和::ReleaseDC(m_hWnd,hScrDC) 这两行代码,
直接使用已有的屏幕dc代替下面代码中的所有hScrDC
**************************************************/
    RECT ClientRect;
    ::GetClientRect(m_hWnd,&ClientRect);
    UINT ClientWidth=ClientRect.right-ClientRect.left;
    UINT ClientHeight=ClientRect.bottom-ClientRect.top;

    HDC hScrDC=::GetDC(m_hWnd);                    //创建屏幕DC
    HDC hMemDC=CreateCompatibleDC(hScrDC);        //创建内存DC
    HBITMAP bitmap=::CreateCompatibleBitmap(hScrDC,ClientWidth,ClientHeight);//创建兼容位图内存块,存放图片数据
    HBITMAP OldBitmap=(HBITMAP)::SelectObject(hMemDC,bitmap);    //内存hMemDC与位图内存块相关联


/***************************************************
步骤二:绘图。
使用hMemDC绘图,就像你平时绘图一样操作
绘图代码主要在这里完成
**************************************************/

    //先画整个客户区的背景
    ::FillRect(hMemDC,&ClientRect,(HBRUSH)GetStockObject(WHITE_BRUSH)); 

    //填充颜色矩形
    HBRUSH brush=::CreateSolidBrush(RGB(255,216,156));
    RECT rect={0,0,100,100};
    ::FillRect(hMemDC,&rect,brush);
    ::DeleteObject(brush);

    //画空矩形2
    ::SelectObject(hMemDC,(HBRUSH)GetStockObject(NULL_BRUSH));
    ::Rectangle(hMemDC,10,10,60,60);

/***************************************************
步骤三:将内存DC图拷贝到屏幕DC上进行显示。
1.BitBlt前4个参数nXDest,nYDest,nWidth,nHeight.确定屏幕DC上的一个矩形区域。
将来要显示在屏幕上的图形范围就在这4个参数所决定的矩形区域内。
2.BitBlt后面2个坐标参数x,y指定从内存hMemDC的哪个位置开始拷贝到屏幕DC上
**************************************************/
    BitBlt(hScrDC,0,0,ClientWidth,ClientHeight,hMemDC,0,0,SRCCOPY);    


/***************************************************
步骤四:最后的收尾工作(删除或释放资源)
1.如果你还想继续使用bitmap所指向的图片数据,请不要调用::DeleteObject(bitmap);    
**************************************************/
    ::SelectObject(hMemDC,OldBitmap);  //bitmap指向的内存块再也没有和hMemDC挂钩,之后你可以使用bitmap所指向的图片数据了
    ::DeleteObject(bitmap);    //释放位图所占Memory
    ::DeleteDC(hMemDC) ;            //删除内存DC
    ::ReleaseDC(m_hWnd,hScrDC) ;    //释放屏幕DC



二:新建一个对话框工程。在OnButton1()里添加以下代码。(MFC版本)
━━━━━━━━━━━━━━━━━━━━━━━━
/***************************************************
步骤一:创建。
1.主要关心的是CreateCompatibleBitmap的后两个参数,
你往内存hMemDC里画的东东就在这个这个范围内
2.如果你已经有一个屏幕dc了,就不要“CClientDC hScrDC(this);”
并且使用已有的屏幕dc代替下面代码中的所有hScrDC
**************************************************/
CClientDC hScrDC(this); //创建屏幕DC
CDC hMemDC;
hMemDC.CreateCompatibleDC(&hScrDC); //创建内存DC
CBitmap bitmap;
bitmap.CreateCompatibleBitmap(&hScrDC,1000,1000); //创建兼容位图
hMemDC.SelectObject(&bitmap); //创建兼容位图

/***************************************************
步骤二:绘图。
使用hMemDC绘图,就像你平时绘图一样操作
**************************************************/
//填充颜色矩形
CBrush brush(RGB(200,23,23));
RECT rect={0,0,200,200};
hMemDC.FillRect(&rect,&brush);

//画空矩形1
hMemDC.SelectObject((HBRUSH)GetStockObject(NULL_BRUSH));
hMemDC.Rectangle(10,10,20,20);

/***************************************************
步骤三:将内存DC图拷贝到屏幕DC上进行显示
1.BitBlt前4个参数nXDest,nYDest,nWidth,nHeight.确定一个矩形区域。
将来要显示在屏幕上的图形范围就在这4个参数所决定的矩形区域内
2.BitBlt后面2个x,y坐标参数指定从内存hMemDC哪个位置开始拷贝到屏幕DC上
**************************************************/
hScrDC.BitBlt(20,20,100,100,&hMemDC,0,0,SRCCOPY);

/***************************************************
补充说明:
当进行大量绘图时,判断所绘图形是否在CLipRect后在绘制,可以提高效率
RECT CLipRect;
::GetClipBox(hScrDC.m_hDC,&CLipRect);
**************************************************/


三、为了方便使用双缓冲,我们可以把这些看起来很繁琐的代码封装成一个类,以方便我们操作。
━━━━━━━━━━━━━━━━━━━━━━━━
/****************************************************************************
双缓冲封装类。

一、使用方法:
    (1) 创建一个内存dc,并指定宽和高
        CMenDC dc(m_hWnd,100,100);
        ::DrawText (dc.hMemDC...........)
    
    (2) 只需传递窗口句柄,创建的内存dc的宽和高会设置为窗口的客户端宽和高
        CMenDC dc(m_hWnd);
        ::DrawText (dc.hMemDC...........)

    (3) 传递一个已经创建的屏幕dc(如WM_PAINT里)
        CMenDC dc(hdc,100,100);
        ::DrawText (dc.hMemDC...........)

二、请不要用CMenDC创建的对象作为类的成员变量或静态变量。CMenDC创建的对象只能是临时的。
    因为需要对象析构(临时对象被摧毁)时调用析构函数来把内存图绘制到屏幕上
****************************************************************************/
class CMenDC
{
private:
    int        x;
    int        y;
    UINT    ClientWidth ;
    UINT    ClientHeight ;
    HWND    m_hWnd;
    HDC        hScrDC;
    HBITMAP bitmap;
    /*HBITMAP OldBitmap;*/

public:
    HDC hMemDC;

    //设置整幅内存图绘制在窗口上的位置(相对于窗口的左上角)
    void SetBitBltPos(int x,int y) { this->x=x ; this->y=y; }

    CMenDC(HWND wnd) : m_hWnd(wnd),x(0),y(0)
    {
        ASSERT(m_hWnd);
        
        RECT ClientRect;
        ::GetClientRect(m_hWnd,&ClientRect);
        ClientWidth=ClientRect.right-ClientRect.left;
        ClientHeight=ClientRect.bottom-ClientRect.top;

        hScrDC=::GetDC(m_hWnd);                    //创建屏幕DC
        hMemDC=CreateCompatibleDC(hScrDC);        //创建内存DC
        bitmap=::CreateCompatibleBitmap(hScrDC,ClientWidth,ClientHeight);//创建兼容位图内存块,存放图片数据
        /*OldBitmap=(HBITMAP)*/::SelectObject(hMemDC,bitmap);    //内存hMemDC与位图内存块相关联
    }

    CMenDC(HWND wnd,UINT width,UINT height) : m_hWnd(wnd),ClientWidth(width),ClientHeight(height),x(0),y(0)
    {
        ASSERT(m_hWnd);
        hScrDC=::GetDC(m_hWnd);                    //创建屏幕DC
        hMemDC=CreateCompatibleDC(hScrDC);        //创建内存DC
        bitmap=::CreateCompatibleBitmap(hScrDC,ClientWidth,ClientHeight);//创建兼容位图内存块,存放图片数据
        /*OldBitmap=(HBITMAP)*/::SelectObject(hMemDC,bitmap);    //内存hMemDC与位图内存块相关联
    }

    CMenDC(HDC hdc,UINT width,UINT height) : m_hWnd(NULL),hScrDC(hdc),ClientWidth(width),ClientHeight(height),x(0),y(0)
    {
        ASSERT(hdc);
        hMemDC=CreateCompatibleDC(hScrDC);        //创建内存DC
        bitmap=::CreateCompatibleBitmap(hScrDC,ClientWidth,ClientHeight);//创建兼容位图内存块,存放图片数据
        /*OldBitmap=(HBITMAP)*/::SelectObject(hMemDC,bitmap);    //内存hMemDC与位图内存块相关联
    }

    //在类析构时把内存图bitblt到窗口上
    ~CMenDC()
    {
        /***************************************************
        步骤三:将内存DC图拷贝到屏幕DC上进行显示。
        1.BitBlt前4个参数nXDest,nYDest,nWidth,nHeight.确定屏幕DC上的一个矩形区域。
        将来要显示在屏幕上的图形范围就在这4个参数所决定的矩形区域内。
        2.BitBlt后面2个坐标参数x,y指定从内存hMemDC的哪个位置开始拷贝到屏幕DC上
        **************************************************/
        BitBlt(hScrDC,x,y,ClientWidth,ClientHeight,hMemDC,0,0,SRCCOPY);    

        /***************************************************
        步骤四:最后的收尾工作(删除或释放资源)
        1.如果你还想继续使用bitmap所指向的图片数据,请不要调用::DeleteObject(bitmap);    
        **************************************************/
        /*::SelectObject(hMemDC,OldBitmap);*/  //bitmap指向的内存块再也没有和hMemDC挂钩,之后你可以使用bitmap所指向的图片数据了
        ::DeleteObject(bitmap);    //释放位图所占Memory
        ::DeleteDC(hMemDC) ;            //删除内存DC
        if (m_hWnd)
            ::ReleaseDC(m_hWnd,hScrDC) ;    //释放屏幕DC
    }
};


双缓冲类测试代码:
━━━━━━━━━━━━━━━━━━━━━━━━
void CDemoDlg::OnButton1() 
{
    RECT rect;
    ::GetClientRect(m_hWnd,&rect);

    CMenDC dc(m_hWnd,rect.right-rect.left,rect.bottom-rect.top);
//    CMenDC dc(m_hWnd);

    //先画整个客户区的背景
    ::FillRect(dc.hMemDC,&rect,(HBRUSH)GetStockObject(WHITE_BRUSH)); 

    //填充颜色矩形
    HBRUSH brush=::CreateSolidBrush(RGB(255,216,156));
    RECT rect2={0,0,100,100};
    ::FillRect(dc.hMemDC,&rect2,brush);
    ::DeleteObject(brush);

    //绘制文字
    ::DrawText (dc.hMemDC,_T("双缓冲绘图"),-1,&rect2,DT_SINGLELINE | DT_LEFT | DT_VCENTER|DT_END_ELLIPSIS ) ;    

    //画空矩形2
    ::SelectObject(dc.hMemDC,(HBRUSH)GetStockObject(NULL_BRUSH));
    ::Rectangle(dc.hMemDC,10,10,60,60);

    //设置整幅内存图绘制在窗口上的位置(相对于窗口的左上角)
    dc.SetBitBltPos(20,20);

}



三、实例解说双缓冲

转自http://www.vckbase.com/document/viewdoc/?id=1612

实例解说双缓冲

作者:HateMath 的网上田园

源代码下载

本文适合初学者

(转自 VC知识库 Blog HateMath 的网上田园)

  昨天在论坛上,有人问起双缓冲的实现问题,想起网上这方面资料比较凌乱,而且多是 DirectX 相关的,今天特地在这里给大家简要的介绍一下双缓冲技术及其在 VC++ 的 GDI 绘图环境下的实现。

1、Windows 绘图原理

  我们在 Windows 环境下看到各种元素,如菜单、按钮、窗口、图像,从根本上说,都是“画”出来的。这时的屏幕,就相当于一块黑板,而 Windows 下的各种 GDI 要素,如画笔、画刷等,就相当于彩色粉笔了。我们在黑板上手工画图时,是一笔一划的,电脑亦然。只不过电脑的速度比手工快的太多,所以在我们看起来好像所有的图形文字都是同时出现的。

2、普通绘图方式的局限

  上述绘图方式我们暂且称之为普通绘图方式吧。虽然这种方式能满足相当一部分的绘图需要,但是当要绘制的对象太复杂,尤其是含有位图时,电脑便力不从心了。这时的画面会显示的很慢,对于运动的画面,会给人“卡”住了的感觉,总之一个字:不爽。

3、解决之道:双缓冲

  双缓冲的原理可以这样形象的理解:把电脑屏幕看作一块黑板。首先我们在内存环境中建立一个“虚拟“的黑板,然后在这块黑板上绘制复杂的图形,等图形全部绘制完毕的时候,再一次性的把内存中绘制好的图形“拷贝”到另一块黑板(屏幕)上。采取这种方法可以提高绘图速度,极大的改善绘图效果。下面是原理图:



图一 双缓冲原理示意图

4、相关的函数介绍

1)、为屏幕 DC 创建兼容的内存 DC:CreateCompatibleDC()

if(!m_dcMemory.CreateCompatibleDC(NULL)) // CDC m_dcMemory;

::PostQuitMessage(0);
}

2)、创建位图:CreateCompatibleBitmap()

m_Bmp.CreateCompatibleBitmap(&m_dcMemory, rt.Width(), rt.Height()); // CBitmap m_Bmp;

3)、把位图选入设备环境:SelectObject(),可以理解为选择画布

::SelectObject(m_dcMemory.GetSafeHdc(), m_Bmp);

4)、把绘制好的图形“拷贝“到屏幕上:BitBlt()

pdcView->BitBlt(0, 0, rt.Width(), rt.Height(), &m_dcMemory, 0, 0, SRCCOPY);

函数的具体用法详见 MSDN。有一句话我重复了多遍,再说一遍也无妨:MSDN是最好的老师。

5、本文给出了一个例子,用效果对比的方法说明普通绘图方式的局限和双缓冲技术的好处。

这个例子在一个 View 上画出很多半径渐变的圆,大家可以注意两种不同的绘图方式下动画的效果:

0 0
原创粉丝点击