GDI中使用双缓冲可以明显的降低图像的闪烁。简单的意思就是:分配内存DC一份、画图先在兼容的内存DC中进行(CreateCompatibleDC),画好之后,还不能直接显示,还要找个中介(CreateCompatibleBitmap),最后以bitmap的形式push(BitBlt)到显卡显示。不像直接绘图,每步都要直接刷新(输出至显卡,这也是flicker的原因)。
使用双缓冲的步骤如下:
CDC MemDC; //首先定义一个显示设备对象,所有的绘制首先绘制到这块内存中
CBitmap MemBitmap; //定义一个位图对象
//随后建立与屏幕显示兼容的内存显示设备
MemDC.CreateCompatibleDC(NULL); //or MemDC.CreateCompatibleDC(pDC);
MemDC.SetStretchBltMode(HALFTONE);
//这时还不能绘图,因为没有地方画
//下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小
MemBitmap.CreateCompatibleBitmap(&dc /*是dc这个参数,而不是MemDC*/,rectCanvas.Width(),rectCanvas.Height());
//将位图选入到内存显示设备中
//只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上
CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);
//这一句只是为了填充一个背景,
MemDC.FillSolidRect(rectCanvas.left,rectCanvas.top,rectCanvas.Width(),rectCanvas.Height(),GetBackgroundColor());
//在MemDC上进行操作,绘制你想要绘制的东西
//...moveto lineto rectangle etc...
//将MemDC的图拷贝到屏幕(dc)上进行显示
dc.BitBlt(rectCanvas.left,rectCanvas.top,rectCanvas.Width(), rectCanvas.Height(),&MemDC,0,0,SRCCOPY);
//绘图完成后的清理
MemBitmap.DeleteObject();
MemDC.DeleteDC();
好,我们看看函数的解释,在继续。
函数解释:
函数原型:CreateCompatibleBitmap
bitmap.CreateCompatibleBitmap(HDC hdc, int nWidth, int nHeight);
函数功能:该函数创建与指定的设备环境相关的设备兼容的位图。
参数:
hdc: 设备环境句柄。
nWidth:指定位图的宽度,单位为像素。
nHeight:指定位图的高度,单位为像素。
返回值:如果函数执行成功,那么返回值是位图的句柄;如果函数执行失败,那么返回值为NULL。若想获取更多错误信息,请调用GetLastError。
备注:由CreateCompatibleBitmap函数创建的位图的颜色格式与由参数hdc标识的设备的颜色格式匹配。该位图可以选入任意一个与原设备兼容的内存设备环境中。由于内存设备环境允许彩色和单色两种位图。因此当指定的设备环境是内存设备环境时,由CreateCompatibleBitmap函数返回的位图格式不一定相同。然而为非内存设备环境创建的兼容位图通常拥有相同的颜色格式,并且使用与指定的设备环境一样的色彩调色板。
函数原型:HDC CreateCompatibleDC(HDC hdc);
函数功能:该函数创建一个与指定设备兼容的内存设备上下文环境(DC)。
参数:
hdc:现有设备上下文环境的句柄,如果该句柄为NULL,该函数创建一个与应用程序的当前显示器兼容的内存设备上下文环境。
返回值:如果成功,则返回内存设备上下文环境的句柄;如果失败,则返回值为NULL。
CreateCompatibleDc函数只适用于支持光栅操作的设备,应用程序可以通过调用GetDeviceCaps函数来确定一个设备是否支持这些操作。
这里有段具体的代码:
void CSkf_MapView::OnDraw(CDC* pDC)
{
CSkf_MapDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
GetClientRect(&rect); //获取当前客户区域的大小
//在内存中开辟一块与当前DC相同的DC然后在其上作图
CDC mDC;
mDC.CreateCompatibleDC(pDC);
CBitmap MemBitmap;
MemBitmap.CreateCompatibleBitmap(pDC,rect.right ,rect.bottom );
CBitmap *pOldBitmap=mDC.SelectObject(&MemBitmap);
//改变背景颜色
CBrush Brush_Bak(BKColor);
CBrush *pOldBrush=mDC.SelectObject(&Brush_Bak);
mDC.SelectObject(Brush_Bak);
mDC.FillRect(&rect,&Brush_Bak);
mDC.SelectObject(pOldBrush);
//在以下绘图函数调用中,语句的先后决定了图层的先后,即先绘的在最下层。
for (int i=0;i<3;i++)
SKFDrawMap(&mDC,i);
SKFDrawBitmap(&mDC,rect.right-190,rect.bottom-528,IDB_BMPME);
SKFDrawText(&mDC);
SKFDrawYellowriver(&mDC);
SKFDrawChinese(&mDC);
//将内存DC上所绘内容一次性拷贝到当前DC
pDC->BitBlt(0,0,rect.right ,rect.bottom ,&mDC,0,0,SRCCOPY);
mDC.DeleteDC();
}
OK,基本上这样就比较清楚了。Keith Rule已经将double buffer封装成了一个类CMemDC(
那已经是将近17年前的事情了),可以用它来代替上面的操作。
这段时间刚刚接触GDI的东西,感觉很无头绪。正在积极练习呢,没想到上网一看GDI在Win8中已经不能使用了,学习微软的东西真麻烦,这么快就要Out了。好在目前只需在XP下解决问题。