主界面需顯示一張包含人臉的圖片,通過 dlib detector 獲取 68 個面部關鍵點並繪製其上。用戶可拖拽關鍵點調整位置並保存。核心難點:拖拽過程中頻繁重繪導致嚴重閃爍。本文聚焦雙緩沖實現,其他業務邏輯略過。
CDC dc_mem; // 內存繪圖 DC
CDC* dc; // 螢幕繪圖 DC
vector<CPoint> face; // 68 個關鍵點坐標
CBitmap bitmap; // 內存位圖
CImage image; // 圖片對象
BOOL CEditLmDlg::OnInitDialog()
{
CDialog::OnInitDialog();
dc = GetDC(); // 獲取客戶區 DC
return TRUE;
}
在類中聲明並映射 OnEraseBkgnd,直接返回 TRUE 防止系統擦除背景引發閃爍:
// 頭文件聲明
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
// 消息映射
ON_WM_ERASEBKGND()
// 實現
BOOL CEditLmDlg::OnEraseBkgnd(CDC* pDC)
{
return TRUE; // 關鍵步驟!
}
DrawOnBuffer()void CEditLmDlg::DrawOnBuffer()
{
CRgn rgn;
rgn.CreateRectRgn(0, 0, image_width, image_height);
dc->SelectClipRgn(&rgn); // 設置裁剪區:僅刷新圖片區域,避免其他控件閃爍
dc_mem.CreateCompatibleDC(dc);
bitmap.CreateCompatibleBitmap(dc, edit_rect.Width(), edit_rect.Height());
CBitmap* pOldBit = dc_mem.SelectObject(&bitmap);
dc_mem.FillSolidRect(edit_rect, dc->GetBkColor()); // 填充背景
dc_mem.SetStretchBltMode(HALFTONE); // 高質量拉伸模式
CRect rect(0, 0, image_width, image_height);
image.Draw(dc_mem.m_hDC, rect); // 繪製底圖到內存 DC
// 將所有關鍵點繪製到 dc_mem(代碼略)
// 一次性 BitBlt 到螢幕
dc->BitBlt(0, 0, edit_rect.Width(), edit_rect.Height(), &dc_mem, 0, 0, SRCCOPY);
dc->SelectClipRgn(NULL); // 恢復裁剪區
dc_mem.DeleteDC();
bitmap.DeleteObject();
}
關鍵點解析:
CreateRectRgn 限制刷新區域僅為圖片矩形,其他控件不受影響BitBlt 上屏,極快無閃爍處理三個滑鼠消息,更新坐標後調用 Invalidate() 觸發重繪:
// 頭文件聲明
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
// 消息映射
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MOUSEMOVE()
在 OnLButtonUp、OnMouseMove 中更新 face 向量對應點座標,隨即 Invalidate() → OnPaint → DrawOnBuffer() 流暢閉環。
雙緩沖精髓在於:將所有繪圖操作集中在單一繪圖函數中,利用內存 DC 完成合成,最後一次性輸出螢幕。代價是需要較多全局/成員變量保存繪圖狀態數據,但換來的流暢體驗絕對值得。
原文參考:https://www.cnblogs.com/battlescars/p/cpp_mfc_drawonbuffer.html