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

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

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

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM