Win32下双缓冲绘图技术

一:双缓冲原理

  为了解决窗口刷新频率过快所带来的闪烁问题,利用双缓冲技术进行绘图。所谓双缓冲技术,就是将资源加载到内存,然后复制内存数据到设备DC(这个比较快),避免了直接在设备DC上绘图(这个比较慢)。打个简单的比方:有个画家在街边办了一个即时画展,在同一块画布上根据观众的要求画不同的图像,每当有一位观众制定要看什么画时,画家先把之前画布上的东西全部擦干净,再重新绘画。显然有一些经典的画像是大家都想看的,按照以前的老办法,画家每次都要重新画这幅图像,但这种擦了画,画了擦的方式很费时。所以画家想了一个办法,把这些经典画像预先用一块或几块画布画下来,等有人需要看时,把这些预备好的画布贴在现有画布的前面,这样就能满足观众的实时性要求。那么这些事先预备好的画布就相当于内存DC,把资源放在内存DC里,等到要刷新显示时,将内存DC上的东西“贴”到当前窗口DC上,就可以减少延时带来的闪烁问题,这就是双缓冲的原理。

详细介绍见后面的几片博文。下面举两个例子:

二: 例子

例子一:加载位图

代码:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;
    PAINTSTRUCT ps;
    HDC hdc;

    switch (message)
    {
    case WM_COMMAND:
        wmId    = LOWORD(wParam);
        wmEvent = HIWORD(wParam);
        // Parse the menu selections:
        switch (wmId)
        {
        case IDM_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        break;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        // TODO: Add any drawing code here...
        myDraw(hdc);

        EndPaint(hWnd, &ps);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

myDraw函数的实现:

const int g_picHeight = 1024;
const int g_picWidth  = 675;
void myDraw(HDC &dc)
{
    RECT rect;
    HBITMAP hOrgBitmap;
    HBITMAP hOldBitmap;
    int disHeight, disWidth;

    GetClientRect(g_hWnd, &rect);//获取客户区大小
    disHeight = rect.bottom-rect.top;
    disWidth = rect.right-rect.left;

    //加载图片
    hOrgBitmap = (HBITMAP)::LoadImage(hInst, _T("test2.bmp"), IMAGE_BITMAP, g_picWidth, g_picHeight, LR_LOADFROMFILE);

    HDC mDc = ::CreateCompatibleDC(dc);//创建当前上下文的兼容dc(内存DC)
    hOldBitmap = (HBITMAP)::SelectObject(mDc, hOrgBitmap);//将位图加载到内存DC

    //拷贝内存DC数据块到当前DC,自动拉伸
    ::StretchBlt(dc, 0, 0, disWidth, disHeight, mDc, 0, 0, g_picWidth, g_picHeight, SRCCOPY);

    //恢复内存原始数据
    ::SelectObject(mDc, hOldBitmap);

    //删除资源,防止泄漏
    ::DeleteObject(hOrgBitmap);
    ::DeleteDC(mDc);
}

结果:

调整窗口大小,发现无闪烁现象。

例子二:画各种直线和曲线。这是一个稍微复杂点的例子,是我平时做的一个demo。可以顺便熟悉一下Windows绘图的一些操作。

代码:

void CRTVIEW_win32DlgDlg::OnPaint()
{
    if (IsIconic())
    {
           /********此段代码忽略*********/
    }
    else
    {
        CDialog::OnPaint(); //调用基类的默认控件绘制
        CRect ctrlRect;
        CStatic *pDegreePicCtrl = (CStatic *)GetDlgItem(IDC_STC_DEGREEPIC);
        pDegreePicCtrl->GetClientRect(&ctrlRect);//获取静态控件尺寸

        CDC *pdc = pDegreePicCtrl->GetWindowDC();//获取控件DC
        CDC dcMemory;
        dcMemory.CreateCompatibleDC(pdc);//创建内存DC

        CBitmap *pOldMapMemory;
        CBitmap mapMemory;
        mapMemory.CreateCompatibleBitmap(pdc, ctrlRect.Width(), ctrlRect.Height());//创建控件DC的兼容位图。其实就是与控件DC大小相同的一块区域
        pOldMapMemory = dcMemory.SelectObject(&mapMemory);//加载兼容位图,只有制定了“桌布”尺寸之后,你才能在内存DC上面绘图

        DrawDegreePicBkGrd(&dcMemory);//在内存DC上绘图
        pdc->BitBlt(0, 0, ctrlRect.Width(), ctrlRect.Height(), &dcMemory, 0, 0, SRCCOPY);//将内存DC上的内容复制到控件DC上
        dcMemory.SelectObject(pOldMapMemory);//还原原来的内存DC
        ::DeleteObject(mapMemory);//删除兼容位图资源
        ::DeleteDC(dcMemory);//删除内存DC
        ReleaseDC(pdc);//释放控件DC
    }
}
void CRTVIEW_win32DlgDlg::DrawDegreePicBkGrd(CDC *pdc)
{
    CRect stcRect, picRect;
    CStatic *pDegreePicCtrl = (CStatic *)GetDlgItem(IDC_STC_DEGREEPIC);
    pDegreePicCtrl->GetClientRect(&stcRect);
    if(stcRect.Width() > stcRect.Height()) {
        int diff = (stcRect.Width() - stcRect.Height()) / 2;
        picRect.left = stcRect.left + diff;
        picRect.right = stcRect.right - diff;
        picRect.top = stcRect.top;
        picRect.bottom = stcRect.bottom;
    } else {
        int diff = (stcRect.Height() - stcRect.Width()) / 2;
        picRect.left = stcRect.left;
        picRect.right = stcRect.right;
        picRect.top = stcRect.top + diff;
        picRect.bottom = stcRect.bottom - diff;
    }

    CBrush *pOldBrush;
    /**************画圆形***************/
    CBrush newBrush1;
    newBrush1.CreateSolidBrush(RGB(0, 255, 0));
    pOldBrush = pdc->SelectObject(&newBrush1);
    pdc->Ellipse(&picRect);

    /**************画原点***************/
    CRect orgRect(stcRect.Width()/2-2, stcRect.Height()/2-2, stcRect.Width()/2+2, stcRect.Height()/2+2);
    CBrush newBrush2;
    newBrush2.CreateSolidBrush(RGB(255,0,0));
    pOldBrush = pdc->SelectObject(&newBrush2);
    pdc->Ellipse(&orgRect);

    pdc->SelectObject(pOldBrush);

    /*************画刻度***************/
    CPoint center(stcRect.Width()/2, stcRect.Height()/2);
    double radias = (double)picRect.Width()/2;

    CPen newPen(PS_SOLID, 1, RGB(255,0,0));
    CPen *poldPen = pdc->SelectObject(&newPen);
    CPoint startPoint, endPoint;

    for(int i=0; i<360; i=i+5) {
        double cosval = cos(DEGREETORADIAN(i));
        double sinval = sin(DEGREETORADIAN(i));
        startPoint.x = center.x + int(radias * cosval); //当前角度对应的圆上的点的x坐标
        startPoint.y = center.y - int(radias * sinval); //当前角度对应的圆上的点的y坐标

        if(i%10 == 0) {
            endPoint.x = startPoint.x - int(10 * cosval);
            endPoint.y = startPoint.y + int(10 * sinval);
        } else {
            endPoint.x = startPoint.x - int(5 * cosval);
            endPoint.y = startPoint.y + int(5 * sinval);
        }
        pdc->MoveTo(startPoint);
        pdc->LineTo(endPoint);
    }
    pdc->SelectObject(poldPen);
}

效果:

三:小结

  这两个例子里面,其实每次重绘都是重新申请内存DC,然后复制到窗口DC。虽然这样子比较繁琐,但是也不影响效果,如果在响应onpaint消息时,不擦除背景(如调用Invalidate(FALSE)),也不会产生闪烁。不过最好的办法,就是文章开头说的,只画一次,把那个内存DC的句柄保存下来,每次在onpaint里面重绘时,直接调用BitBlt复制即可。不过要注意这些句柄对象的销毁,以免内存泄漏。

下面这些文章也可以看看:

http://baike.baidu.com/view/1149326.htm

http://blog.csdn.net/xsc2001/article/details/5378601

http://www.cppblog.com/wrhwww/archive/2011/03/01/140913.html

http://www.cnblogs.com/afarmer/archive/2012/03/31/2427315.html

http://www.programlife.net/mfc-draw-pictures-with-memory-dc-buffer.html

http://blog.csdn.net/zxzerster/article/details/5659775

http://blog.csdn.net/yiruirui0507/article/details/6153607

http://blog.csdn.net/zicheng_lin/article/details/7179278

Win32下双缓冲绘图技术

时间: 2024-10-13 18:36:58

Win32下双缓冲绘图技术的相关文章

VC双缓冲绘图技术介绍

VC双缓冲绘图技术介绍 双缓冲绘图,它是一种基本的图形图像绘图技术.首先,它在内存中创建一个与屏幕绘图区域一致的对象,然后将图形绘制到内存中的这个对象上,最后把这个对象上的图形数据一次性地拷贝并显示到屏幕上.这种技术能够大大地提高绘图的速度,减少卡顿和闪屏的问题. 我们为什么要使用双缓冲技术来进行绘图? 在应用程序开发中,当图像信息数据量很大时,绘图可能需要几秒钟甚至更长的时间,这时,应用程序可能会出现卡顿的现象.另外,如果窗体在响应WM_PAINT消息的同时也要进行复杂的图形处理,那么窗体在重

【MFC】MFC绘制动态曲线,用双缓冲绘图技术防闪烁

摘自:http://zhy1987819.blog.163.com/blog/static/841427882011614103454335/ MFC绘制动态曲线,用双缓冲绘图技术防闪烁 2011-07-14 10:34:54|  分类: 学习笔记 |  标签:双缓冲绘图技术  mfc  动态曲线   |举报 |字号 订阅 先上效果图 随着时间的推移,曲线向右平移,同时X轴的时间坐标跟着更新.一.如何绘制动态曲线. 所谓动画,都是一帧一帧的图像连续呈现在用户面前形成的.所以如果你掌握了如何绘制静

C# - 双缓冲绘图技术

双缓冲绘图,是指先在内存中进行各种绘图操作,在将内存中绘制好的图形取出显示在控件上,这样可以避免窗口闪烁的现象. 根据上述原理,我们可以自行实现双缓冲绘图,示例代码如下: private void Paint() { // tempImage -> 临时位图 // tempGraphics -> 临时位图的绘图对象 // viewGraphics -> 显示控件的绘图对象 using (Bitmap tempImage = new Bitmap(pictureBox1.ClientSiz

[Android学习笔记]双缓冲绘图技术

双缓冲技术绘图: 什么情况下产生的双缓冲技术?当数据量很大时,绘图可能需要花费很长的时间,这样屏幕就会出现卡顿,闪烁等现象. 什么是双缓冲技术?双缓冲是在内存中创建一个与屏幕绘制区域一致的对象,先将图形绘制到内存中的这个对象上,再一次性将这个对象上的图形拷贝到屏幕上.其过程如下:1.在内存中创建与画布一致的缓冲区2.在缓冲区画图3.将缓冲区位图拷贝到当前画布上4.释放缓冲区内存 android开发中,surfaceView就是实现了双缓冲技术的View

C#-gdi绘图,双缓冲绘图,Paint事件的触发---ShinePans

在使用gdi技术绘图时,有时会发现图形线条不够流畅,或者在改变窗体大小时会闪烁不断的现象.(Use DoubleBuffer to solve it!)                                                                                                                                                                              

GDI双缓冲绘图

一.简介 在进行复杂图形绘制时,若直接在屏幕DC上进行绘制,则会出现明显的闪烁.闪烁产生的原因是当绘制的图形较为 复杂时,图形绘制过程中就被刷新到屏幕上,导致结果断断续续地显示出来.双缓冲绘图的原理是在另开辟一块内存用于绘制,当所有绘制工作完成后将内存数据一 次性拷贝到屏幕上. 双缓冲绘图步骤: 创建兼容DC(CreateCompatibleDC) 创建兼容位图(CreateCompatibleBitmap) 将兼容位图选入兼容DC(SelectObject) 在兼容DC中进行绘制工作 将兼容D

MFC双缓冲绘图解决界面闪烁问题

一:为什么会产生界面闪烁? 解释这个之前,我们需要明白的是在MFC里面绘图的消息响应机制,大概的就是如果我们要在某一个 东西上面绘图,比如对话框,单文档等等,就必须先得到图形DC的句柄(handle),然后在指定句柄的基础上进行图形操作,也就是MFC常用的CDC *DC = this->getDC();其中的this就是你想画图的目标. MFC里在消息响应的过程中,WM_PAINT被转变为OnDraw()(单文档 Single Document)或是OnPaint()(对 话框Dialog)之类

简单的 &quot;双缓冲&quot; 绘图的例子(研究一下)

所谓双缓冲就是先画到内存画布(如: TBitmap), 然后再转帖到目的地. 譬如下面小程序: procedure TForm1.FormCreate(Sender: TObject); begin   Timer1.Interval := 100;   Color := clWhite; end; procedure TForm1.Timer1Timer(Sender: TObject); begin   Canvas.Pen.Color := Random($FFFFFF);   Canva

MFC双缓冲绘图(2015.09.24)

问题引入: 最近在尝试编写贪吃蛇游戏时遇到这么一个问题:当系统以较快频率向窗口发送WM_PAINT消息时,调用OnPaint()函数在窗口中绘制图形就会发生闪烁现象. 问题分析: 当我们把绘图过程放在OnPaint()函数中时(放在OnDraw()函数中也是如此,因为OnDraw()会被OnPaint()调用),由于频繁收到系统的WM_PAINT消息,窗口需要执行重绘.而重绘过程首先是执行了窗口内容的擦除(用当前背景色的画刷对窗口重新绘制),然后再根据绘图语句在窗口客户区中对窗口内容进行重绘.由