mfc 绘图闪烁问题的探究

初学用windowsGDI进行界面绘制的时候经常会出现界面闪烁的问题,画面闪烁的直接原因是短时间内某部分的颜色的持续反差(这里的反差指肉眼可分辨的不同的颜色都称之彼此反差)变化,之所以要描述这么拗口,因为如果只是突然一两次的颜色反差变化,那只是颜色切换,画面只是变换了下,而颜色持续反差说的是闪烁部分的颜色在两个或多个差异较大的颜色之间来回变化,试想如果颜色一直变化但是缓慢过度,那么只是颜色渐变不会闪烁,如果颜色一直很快的变化,但大多数时间看到的是同一种颜色,那也看不出闪烁。

我们不要被显示的对象的复杂、颜色多样绚丽所蒙蔽,我们始终只需要关心单个像素的颜色变换,如果单个像素的颜色反复切换,那么就会闪烁,如果一个区域内有多个像素颜色反复变换,那么这个闪烁就会被观察到。

这里先看另一个广泛转载以至于没找到原帖的帖子中介绍的:

https://www.cnblogs.com/piggger/archive/2009/05/02/1447917.html

其实该博主对闪烁的解释和解决方法描述的很到位了,但是我还是想补充一下。

根据上面我总结的描述,然后我们看下平时遇到的几个典型场景分析,并用代码复现,代码说明见博文http://www.straka.cn/blog/encapsulate-gdi-draw-method/

一对于画面前景背景颜色一致(准确的说是肉眼无法分辨出颜色差别,下同)的而言,无论如何绘制(频率)都不会闪烁。这么说好像是废话,其实是说明闪烁不会因为系统自身的绘制造成,系统绘制是个覆盖的过程,而不是一个清空再重填的过程。

 
 
for(i=0;i<1000;i++){
	drawMethod.FillRect(rect,RGB(250,250,250),255);
	drawMethod.DrawOnScreen();
	Sleep(1); //change it to simulate the different frequences
}

二,只重复绘制一种颜色的区域,对于该区域每个像素始终只显示一个颜色,这部分也是不会闪烁的,这个其实和不停绘制背景色的情况一致。

三,对于同一个颜色(反差于背景色)在附近来回绘制,从像素角度,由于抖动造成该区域的边缘颜色来回变化,即在背景色和该前景色变化,而边缘显然是可见的,因而这样是会呈现闪烁的,

 
 
left = 0;
for(i=0;i<1000;i++){
	offset+= 1;
	offset %=7;
	rect1.left = offset +10;
	rect1.right = offset +40;
	rect1.top = offset +10;
	rect1.bottom = offset + 40; 
	drawMethod.FillRect(rect,RGB(0,0,0),255);
	drawMethod.FillRect(rect1,RGB(250,250,250),255);
	drawMethod.DrawOnScreen();
	Sleep(1);
}

四,前景和背景色反差大并来回切换,这种情况下是否看得出闪烁在于那种颜色的时间占比能否达到绝对高,如果两种颜色的显示时间达到1:50,那是看不出闪烁的,而1:10,那么就很容易看出闪烁,当然这个时间比也必须是段时间内的,比如1秒内,所以可以用下述代码测试:

 
 
for(i=0;i<1000;i++){
	drawMethod.FillRect(rect,RGB(0,0,0),255);
	drawMethod.FillRect(rect1,RGB(250,250,250),255);
	drawMethod.DrawOnScreen();
	Sleep(1);
	drawMethod.FillRect(rect,RGB(0,0,0),255);
	drawMethod.FillRect(rect1,RGB(170,0,0),255);
	drawMethod.DrawOnScreen();
	Sleep(50);//change this to simulate different proportions
}

五,对于一个显示对象(颜色区域,复杂图形等)缓慢移动的情况,快速移动其实等同于上面的情况四中比例很接近的情况,因为我们始终考察的是某个小范围的颜色变化,对于快速移动的目标区域而言,是从原先的显示对象切换成了当前的对象,如果该目标区域反复切换则也是闪烁,而缓慢移动,因为对于沿路的内容只会从原先显示对象的颜色变为背景色,因而并不会闪烁。

 
 
for(i=0;i<1000;i++){
	offset += 1;
	offset %= 500;
	rect1.left = offset;
	rect1.right = offset+40;

	drawMethod.FillRect(rect,RGB(0,0,0),255);
	drawMethod.FillRect(rect1,RGB(250,250,250),255);
	drawMethod.DrawOnScreen();
	Sleep(10); //change this to control the moving speed
}

综上,我们经常遇到的闪烁分解下无外乎上述几种情况,对于常见的控件绘制闪烁,是由于重绘时的背景色和前景图案反差较大,每次重绘先用背景色擦除就会造成闪烁,而解决的办法基本都是屏蔽背景重绘和靠双缓冲绘制。

屏蔽背景重绘很简单:

在强制重绘的时候Invalidate(FALSE);传入false不重绘背景,true会重绘背景,

或者实现WM_ERASEBKGND 消息的时候直接return true;

双缓冲原理也不复杂,就是每次绘制前先在内存中画好,然后直接将内存中的图案显示到显示设备上。由于我们要显示的画面内容经常是前后帧连续变化,差异较小的,也就是上述五缓慢移动的情形,因而是不容易闪烁的。双缓冲的代码我就直接引用前文链接中的代码,另外在我的另一篇博客里面也有封装好的代码利用了双缓冲减轻闪烁。

 
 
CDC MemDC; //首先定义一个显示设备对象
CBitmap MemBitmap;//定义一个位图对象
//随后建立与屏幕显示兼容的内存显示设备
MemDC.CreateCompatibleDC(NULL);
//这时还不能绘图,因为没有地方画 ^_^
//下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小
MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);
//将位图选入到内存显示设备中
//只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上
CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);
//先用背景色将位图清除干净,这里我用的是白色作为背景
//你也可以用自己应该用的颜色
MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));
//绘图
MemDC.MoveTo(……);
MemDC.LineTo(……);
//将内存中的图拷贝到屏幕上进行显示
pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);
//绘图完成后的清理
MemBitmap.DeleteObject();
MemDC.DeleteDC();

下载试验demo代码等见原博客:http://www.straka.cn/blog/flickering-in-mfc/


猜你喜欢

转载自blog.csdn.net/atp1992/article/details/80973007
MFC
今日推荐