本文共 3465 字,大约阅读时间需要 11 分钟。
作者 :树爱兵
邮箱 :spily365@163.com 在实现绘图的过程中,显示的图形总是会闪烁,笔者曾经被这个问题折磨了好久,通过向高手请教,搜索资料,问题已基本解决,现将文档整理出来以供大家参考.1.显示的图形为什么会闪烁? 我们的绘图过程大多放在OnDraw或者OnPaint函数中,OnDraw在进行屏幕显示时是由OnPaint进行调用的。当窗口由于任何原因需要重绘时,总是先用背景色将显示区清除,然后才调用OnPaint,而背景色往往与绘图内容反差很大,这样在短时间内背景色与显示图形的交替出现,使得显示窗口看起来在闪。如果将背景刷设置成NULL,这样无论怎样重绘图形都不会闪了。当然,这样做会使得窗口的显示乱成一团,因为重绘时没有背景色对原来绘制的图形进行清除,而又叠加上了新的图形。有的人会说,闪烁是因为绘图的速度太慢或者显示的图形太复杂造成的,其实这样说并不对,绘图的显示速度对闪烁的影响不是根本性的。例如在OnDraw(CDC *pDC)中这样写: pDC->MoveTo(0,0); pDC->LineTo(100,100); 这个绘图过程应该是非常简单、非常快了吧,但是拉动窗口变化时还是会看见闪烁。其实从道理上讲,画图的过程越复杂越慢闪烁应该越少,因为绘图用的时间与用背景清除屏幕所花的时间的比例越大人对闪烁的感觉会越不明显。比如:清楚屏幕时间为1s绘图时间也是为1s,这样在10s内的连续重画中就要闪烁5次;如果清楚屏幕时间为1s不变,而绘图时间为9s,这样10s内的连续重画只会闪烁一次。这个也可以试验,在OnDraw(CDC *pDC)中这样写: for(int i=0;i<100000;i++) { pDC->MoveTo(0,i); pDC->LineTo(1000,i); } 呵呵,程序有点变态,但是能说明问题。 说到这里可能又有人要说了,为什么一个简单图形看起来没有复杂图形那么闪呢?这是因为复杂图形占的面积大,重画时造成的反差比较大,所以感觉上要闪得厉害一些,但是闪烁频率要低。那为什么动画的重画频率高,而看起来却不闪?这里,我就要再次强调了,闪烁是什么?闪烁就是反差,反差越大,闪烁越厉害。因为动画的连续两个帧之间的差异很小所以看起来不闪。如果不信,可以在动画的每一帧中间加一张纯白的帧,不闪才怪呢。 2、如何避免闪烁 在知道图形显示闪烁的原因之后,对症下药就好办了。首先当然是去掉MFC提供的背景绘制过程了。实现的方法很多, * 可以在窗口形成时给窗口的注册类的背景刷付NULL * 也可以在形成以后修改背景 static CBrush brush(RGB(255,0,0)); SetClassLong(this->m_hWnd,GCL_HBRBACKGROUND,(LONG)(HBRUSH)brush); * 要简单也可以重载OnEraseBkgnd(CDC* pDC)直接返回TRUE 这样背景没有了,结果图形显示的确不闪了,但是显示也象前面所说的一样,变得一团乱。怎么办?这就要用到双缓存的方法了。双缓冲就是除了在屏幕上有图形进行显示以外,在内存中也有图形在绘制。我们可以把要显示的图形先在内存中绘制好,然后再一次性的将内存中的图形按照一个点一个点地覆盖到屏幕上去(这个过程非常快,因为是非常规整的内存拷贝)。这样在内存中绘图时,随便用什么反差大的背景色进行清除都不会闪,因为看不见。当贴到屏幕上时,因为内存中最终的图形与屏幕显示图形差别很小(如果没有运动,当然就没有差别),这样看起来就不会闪。3、如何实现双缓冲 首先给出实现的程序,然后再解释,同样是在OnDraw(CDC *pDC)中: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();禁止系统擦掉原来的图象 可以重载OnEraseBkgnd()函数,让其直接返回TRUE就可以了。如BOOL CMyWin::OnEraseBkgnd(CDC* pDC) { return TRUE; //return CWnd::OnEraseBkgnd(pDC);//把系统原来的这条语句注释掉。}上面的注释应该很详尽了,废话就不多说了。4、如何提高绘图的效率 实际上,在OnDraw(CDC *pDC)中绘制的图并不是所有都显示了的,例如:你在OnDraw中画了两个矩形,在一次重绘中虽然两个矩形的绘制函数都有执行,但是很有可能只有一个显示了,这是因为MFC本身为了提高重绘的效率设置了裁剪区。裁剪区的作用就是:只有在这个区内的绘图过程才会真正有效,在区外的是无效的,即使在区外执行了绘图函数也是不会显示的。因为多数情况下窗口重绘的产生大多是因为窗口部分被遮挡或者窗口有滚动发生,改变的区域并不是整个图形而只有一小部分,这一部分需要改变的就是pDC中的裁剪区了。因为显示(往内存或者显存都叫显示)比绘图过程的计算要费时得多,有了裁剪区后显示的就只是应该显示的部分,大大提高了显示效率。但是这个裁剪区是MFC设置的,它已经为我们提高了显示效率,在进行复杂图形的绘制时如何进一步提高效率呢?那就只有去掉在裁剪区外的绘图过程了。可以先用pDC->GetClipBox()得到裁剪区,然后在绘图时判断你的图形是否在这个区内,如果在就画,不在就不画。如果你的绘图过程不复杂,这样做可能对你的绘图效率不会有提高。其他: GDI下的双缓冲: 我们前面做过一些小例子都有一个致命缺陷:当不管是由于某种原因使得窗口重新绘制的时候上面就只有背景了,其它内容都没有了。为什么呢?因为系统并没有为我们以前所绘制的数据保留一个副本以恢复。那么我们自己是不是可以弄一个副本呢?当然可以了,下面我们就来做这个工作: 我们需要另外一个缓冲区,把我们所需要的数据都暂时存到这个缓冲区里面,具体说我们需要一个内存设备环境,还有一张位图,用于存放我们的窗口数据。当我们改变数据的时候直接操作内存设备环境中的数据,而不是屏幕数据,但我们为了看到效果必须把内存设备环境中的内容拷贝到屏幕上去,在拷贝的时候就有说法了,这里我们有两种选择:一,把所有的数据都拷贝一遍。二,我们只选择那些需要重绘的区域中的数据来拷贝,因为窗口每次刷新并不是傻乎乎的全部更新的,而是首先看一下是哪些区域需要更新,然后把需要更新的数据拷贝过去即可,这样可以提高系统的整体性能。我们当然是选择第二种做法了。 怎么来实现这个好的想法呢? 利用更新矩形。具体该怎样工作呢?我们把需要更新的数据放在一个更新矩形里面,如果这个更新矩形是个空的,那我们就什么也不用做了;如果更新矩形不为空,那么我们也只是需要将更新矩形里面的数据拷贝到屏幕上即可。那么我们如何来实现这个更新矩形呢? 当更新矩形是空的时,任何加过来的矩形就变成了更新矩形;当更新矩形不为空时,更新矩形就要跟与之相加的矩形合并从而得到新的更新矩形UnionRect. |
转载地址:http://kczvb.baihongyu.com/