天天看點

MFC雙緩沖+提升繪圖效率方法(号稱三緩沖):自定義靜态背景不頻繁擦除(★firecat推薦★)

應用場景是:繪制一個運動小球,在大螢幕滾動,螢幕背景圖也是一個自定義繪制的(填充色,線條和文字等組成)。如圖所示。紅色是小球,在大背景裡動态移動,而背景是靜态的,靜止不動。

MFC雙緩沖+提升繪圖效率方法(号稱三緩沖):自定義靜态背景不頻繁擦除(★firecat推薦★)
1、MFC克服C++窗體重繪時的閃爍問題,用到的技巧是雙緩沖。雙緩沖原理網上的文章好多,這裡不贅述。

//---------------------------------MFC雙緩沖//---------------------------------
void CViewImage::OnPaint()
{
    CPaintDC dc(this); // device context for painting
        // TODO: 在此處添加消息處理程式代碼
        // 不為繪圖消息調用 CFormView::OnPaint()
    OnPrepareDC(&dc);
    OnDraw(&dc); //調用了OnDraw
}
void CViewImage::OnDraw(CDC *pDC)
{
    CDC dcMemory; //圖形重繪,雙緩沖防止閃屏
    CRect rect;
    GetClientRect(&rect);
    CBitmap bmp;
    dcMemory.CreateCompatibleDC(pDC);
    bmp.CreateCompatibleBitmap(pDC, rect.right, rect.bottom);
    CBitmap *pOldBitmap = dcMemory.SelectObject(&bmp);
    dcMemory.SetBkMode(TRANSPARENT);
    DrawBackgroundImage(&dcMemory);//繪制自定義背景圖
    DrawBall(&dcMemory);//繪制小球
    pDC->BitBlt(rect.left, rect.top, rect.right, rect.bottom, &dcMemory, 0, 0, SRCCOPY);
    dcMemory.SelectObject(pOldBitmap);
    dcMemory.DeleteDC();
    bmp.DeleteObject();
}
BOOL CViewImage::OnEraseBkgnd(CDC* pDC)
{
    //Invalidate(FALSE)不擦除背景,直接畫,它隻會向消息隊列中添加了WM_PAINT消息。
    //Invalidate(TRUE)擦除背景,它會向消息隊列中添加了WM_ERASEBKGND和WM_PAINT兩個消息。
    //可見:Invalidate(FALSE)不會清空之前所畫圖像。
    //如果你想用Invalidate(TRUE)來實作Invalidate(FALSE)一樣的效果,
    //你可以添加對WM_ERASEBKGND消息響應的函數,
    //修改OnEraseBkgnd函數的傳回值為:return TRUE;
    //傳回值return TRUE就是不擦除背景,此時Invalidate(TRUE)與Invalidate(FALSE)的效果是一樣的。
    return TRUE;//禁止背景重繪; true表示已處理背景重新整理,false表示需要在OnPaint裡處理
    //return CFormView::OnEraseBkgnd(pDC);
}      

2、本文要解決的是提升繪圖效率,不要讓背景頻繁擦除。本方法可以認為是MFC三緩沖繪圖。(#^.^#)

在上面的源碼OnDraw函數中,每次都需要繪制自定義背景圖和小球圖。每隔50毫秒通過Invalidate函數發送WM_PAINT消息重新整理螢幕。但實際運作過程中發現,每次繪制自定義背景太占CPU(4%,4核CPU),有什麼更好的辦法嗎?

辦法就是不要每次都重繪背景圖,因為它是靜止不動的。于是想到将背景預先繪制到一個記憶體裝置區域,然後在OnDraw中将該記憶體裝置環境中的内容複制到目前的記憶體裝置環境中。

方法1:畫在已知的記憶體型bmp

//---------------------------------方法1:---------------------------------
定義類的成員變量
CDC dcMemory;//圖形重繪,雙緩沖防止閃屏
CBitmap bmp;
CBitmap *pOldBitmap;
void CViewImage::initBackgroundImage(CDC* pDC)
{
    if (!bFirst)
    {
        return;
    }
    CRect rect;
    GetClientRect(&rect);
    dcMemory.CreateCompatibleDC(pDC);
    bmp.CreateCompatibleBitmap(pDC, rect.right, rect.bottom);
    pOldBitmap = dcMemory.SelectObject(&bmp);
    dcMemory.SetBkMode(TRANSPARENT);
    //dcMemory.FillSolidRect(0, 0, rect.right, rect.bottom, RGB(255, 255, 255));//預設是黑色
    DrawBackgroundImage(&dcMemory);//繪制自定義背景圖
}
void CViewImage::destroyBackgroundImage()
{
    dcMemory.SelectObject(pOldBitmap);
    dcMemory.DeleteDC();
    bmp.DeleteObject();
}
void CViewImage::OnDraw(CDC* pDC)//方法1:畫在已知的記憶體型bmp
{
    initBackgroundImage(CDC* pDC);//使用布爾量控制隻繪制一次,但是遇到OnSize消息時,背景圖需要重繪
    CRect rect;
    GetClientRect(&rect);
    CDC dcCompatible;
    dcCompatible.CreateCompatibleDC(pDC);
    pOldBitmap = dcCompatible.SelectObject(&bmp);
    DrawBall(&dcCompatible);//繪制小球
    pDC->BitBlt(rect.left, rect.top, rect.right, rect.bottom, &dcCompatible, 0, 0, SRCCOPY);
    dcCompatible.SelectObject(pOldBitmap);
    dcCompatible.DeleteDC();
}      

方法2:一個CDC拷貝到另一個CDC

//---------------------------------方法2:---------------------------------
定義類的成員變量
CDC dcMemory1;
CBitmap bmp1;
CBitmap *pOldBitmap1;
void CViewImage::initBackgroundImage(CDC* pDC)
{
    if (!bFirst)
    {
  return;
    }
    CRect rect;
    GetClientRect(&rect);
    dcMemory1.CreateCompatibleDC(pDC);
    bmp1.CreateCompatibleBitmap(pDC, rect.right, rect.bottom);
    pOldBitmap1 = dcMemory1.SelectObject(&bmp1);
    dcMemory1.SetBkMode(TRANSPARENT);
    //dcMemory1.FillSolidRect(0, 0, rect.right, rect.bottom, RGB(255, 255, 255));//預設是黑色
    DrawBackgroundImage(&dcMemory1);//繪制自定義背景圖
}
void CViewImage::destroyBackgroundImage()
{
    dcMemory1.SelectObject(pOldBitmap1);
    dcMemory1.DeleteDC();
    bmp1.DeleteObject();
}
void CViewImage::OnDraw(CDC* pDC)
{
    initBackgroundImage(CDC* pDC);//使用布爾量控制隻繪制一次,但是遇到OnSize消息時,背景圖需要重繪
    //方法2:一個CDC拷貝到另一個CDC
    CDC dcMemory2;
    dcMemory2.CreateCompatibleDC(&dcMemory1);
    CBitmap bmp2;
    bmp2.CreateCompatibleBitmap(&dcMemory1, rect.right, rect.bottom);
    CBitmap *pOldBitmap2 = dcMemory2.SelectObject(&bmp2);
    dcMemory2.SetBkMode(TRANSPARENT);
    dcMemory2.BitBlt(rect.left, rect.top, rect.right, rect.bottom, &dcMemory1, 0, 0, SRCCOPY);//CDC拷貝
    DrawBall(&dcMemory2);//繪制小球
    pDC->BitBlt(rect.left, rect.top, rect.right, rect.bottom, &dcMemory2, 0, 0, SRCCOPY);//繪制在客戶區
    dcMemory2.SelectObject(pOldBitmap2);
    dcMemory2.DeleteDC();
    bmp2.DeleteObject();
}      

推薦使用方法2!!

---題外話---

//---------------------------------題外話:外部加載位圖并顯示---------------------------------

void CViewImage::OnDraw(CDC* pDC)
{
    CBitmap bitmap;
    bitmap.LoadBitmap(IDB_BITMAP_DEBUG_TEACH);
    BITMAP bmp1;
    bitmap.GetBitmap(&bmp1);
    CDC dcCompatible;
    dcCompatible.CreateCompatibleDC(pDC);
    pOldBitmap = dcCompatible.SelectObject(&bitmap);
    dcCompatible.MoveTo(1, 1);
    dcCompatible.LineTo(40, 50);
    pDC->BitBlt(rect.left, rect.top, rect.right, rect.bottom, &dcCompatible, 0, 0, SRCCOPY);
    dcCompatible.SelectObject(pOldBitmap);
    dcCompatible.DeleteDC();
    bitmap.DeleteObject();
}      

---

參考文獻:

https://www.cnblogs.com/renyuan/p/3474802.html https://bbs.csdn.net/topics/390929456 https://blog.csdn.net/ooyyee11/article/details/7600625 https://www.cnblogs.com/lujin49/p/4704795.html

繼續閱讀