在图形图像处理编程过程中,双缓冲是一种基本的技术。如果窗体在响应WM_PAINT消息的时候要进行复杂的图形处理,那么窗体在重绘是由于过于频繁的刷新而出现闪烁现象。解决这一问题的有效方法就是双缓冲技术。
因为窗体在刷新时,总要有一个擦除原来图像的过程OnEraseBkgnd,它利用背景色填充窗体绘图区,即重绘背景,然后再调用新的绘图代码进行重绘,这样一擦一写就造成了图像颜色的反差。当WM_PAINT消息响应很频繁时,这种反差也就越明显,这样就会出现闪烁现象。
禁止重绘背景固然可以消除反差,但也会导致窗体内容的混乱,即原先的图像残留得不到清除,窗体内容无法完全更新。因此,不能单纯禁止重绘背景来解决闪烁现象。为了消除闪烁现象而不降低绘图速度,这里就需要利用双缓冲的思想了。
双缓冲的思路是:在内存中创建缓冲DC(后台DC),把要显示的图形图像绘制到内存缓冲DC中,然后利用BitBlt函数将内存缓冲DC的图像复制到最终的显示DC(前台DC)上。由于BitBlt的复制操作速度非常快,与原先的绘制操作相比,几乎不会增加绘图显示时间。与此同时,禁止背景重绘。这样既消除了闪烁现象,也几乎不影响绘图显示速度。
具体操作如下:
一、禁止背景重绘
CView类的WM_ERASEBKGND消息响应函数实现了重绘背景的功能。这里在工程的视图类中添加该响应函数,并将其默认代码(调用CView类的OnEraseBkgnd()函数)注释掉,令其不做任何处理,直接返回TRUE即可。
二、在OnDraw或OnPaint中实现双缓冲
1、创建缓冲DC
- CRect rect; // 存储客户区大小
- CDC MemDC; // 用于缓冲绘图的内存DC
- CBitmap MemBitmap ; // 内存DC中承载临时图像的位图
- // 获取当前客户区大小
- GetClientRect( &rect );
- // 创建兼容当前DC的内存DC
- MemDC.CreateCompatibleDC( pDC );
- // 创建兼容位图
- MemBitmap.CreateCompatibleBitmap( pDC, rect.Width(), rect.Heigth() );
- // 将兼容位图选入到内存缓冲DC,相当于画布,必须选进内存DC
- MemDC.SelectObject( &MemBitmap );
- // 使用原背景色填充内存缓冲DC
- MemDC.FillSolidRect( rect, pDC->GetBkColor() );
注意:CreateCompatibleBitmap的第一个参数必须是当前DC,而不能是内存DC。否则创建的位图将是黑白单色位图,要创建彩色位图,必须使用当前DC指针pDC。
2、将图像图像绘制到内存缓冲DC中
将绘图代码中的“pDC->”全部改为“MemDC.”即可。
3、将内存缓冲DC中的内容复制到当前DC
- pDC->BitBlt( 0,0,rect.Width().rect.Height(),&MemDC,0,0,SRCCOPY );
经过这样的处理,无论窗体如何变化,窗体内容都不会再出现闪烁现象。
值得注意的是,如果图形绘制需要的时间不多(步骤二、2),上述方法的防闪烁效果非常好。但如果图形绘制需要的时间较多,比如2或3秒甚至更多,上述方法就会产生一个问题:窗体内容会在相应的时间内(2或3秒甚至更多)没有变化,呈现假死机现象。
不使用双缓冲时,随着图形绘制代码的执行,窗体会将每一个图形逐个显示出来,虽然有点闪烁,但对用户来说,程序还在执行,没有死机。而使用双缓冲后,图形是逐个绘制到内存DC中的,在此期间窗体内容不会随着绘制进程的进展而变化,变化的是用户看不到的内存DC。因此会给用户死机的感觉。
要避免这种情况的发生,就要尽量减少图形绘制的时间,一种方法是减少绘制工作量,即不需要重新绘制的图形部分直接拷贝到前台DC,只绘制需要更新的图形部分。但实际上,很多情况下无法确定哪些图形不需要绘制,哪些需要绘制。将前台DC划分为多个小区域内存DC进行拼接,可以逐个小DC进行绘制,这有点类似于GoogleMap中的图像显示。
另一种方法是充分利用已显示的图形显示操作后的部分结果,待用户操作结束后再重新全部图形,例如,在图像的平移操作或地图漫游操作中,可以实时计算出当前已经显示图形在平移或漫游操作后的新位置,直接将其复制到pDC的新位置,用户拖拽或漫游操作结束后,再在内存DC中重新绘制移动后的所有图形,绘制完成后再复制到当前DC,由于新绘制的图像与原图像移动后有部分完全重合,因此不影响用户的观看结果。但在用户操作结束后同样要有一段时间的无响应。