用MFC如何高效地绘图

来源:互联网 发布:windows临时账户登录 编辑:程序博客网 时间:2024/04/30 07:58


  为什么WIN32 API的的图形绘制算法比自己写的算法快很多?

  自己用SetPixel封装的Bresnham直线生成算法与GDI的LineTo进行对比,发现慢了几十倍,然后自己用Breshnham封装的多边形扫描转换区域CPolygon::draw()与GDI的Polygon填充进行对比, 发现慢了几百倍。其中因为封装了Breshnham的关系,所以总的来说算法要慢上几十倍。为什么GDI那么快?是因为直接访问了物理硬件设备的关系吗?求各路大神指导。

------解决思路----------------------
使用SetPixel来实现LineTo等GDI函数不慢才怪,每个点调用一次SetPixel,光函数调用开销就好大的,真要实现自己的GDI函数,建议直接内存操作。CreateDibsection
------解决思路----------------------
CreateDibsection然后操作指针,然后将窗口的数据拷贝出来, 往内存位置填数据
另外,你还可以使用光栅操作,指定一个背景颜色,BitBlt回去的时候,没有操作过得像素不会变化,具体lz研究一下ROP2
------解决思路----------------------
除了显示驱动程序,其他程序都不可以直接操作显存,GDI 的 LineTo 是直接在窗口的缓存区绘图,在 Vista 之前的操作系统里,GDI 函数都是直接在桌面的缓存区绘图。
把直线绘制到自己创建的 BITMAP 上,再 BitBlt 到窗口的 hdc 里,这是在不借助 Direct3D 或 OpenGL 等手段的前提下最快的方式了。
每次 SetPixel 都会导致 hdc 的受影响区域 Invalid,LineTo 是在完成所有点之后再 Invalid,所以频繁 SetPixel 会影响效率。
------解决思路----------------------
“使hdc受影响区域Invalid” 指的是通知操作系统,被修改的部分需要刷新到显存。
setPixel 改变窗口缓存位置的内存值,但这个改变还没有反映到显存里,甚至还没通知到操作系统。
GDI 直接操作窗口缓存只能通过 hdc 来完成。
BitBlt 跟 SetPixel 速度肯定是天壤之别的。
首先,BitBlt 一次性实现了 n 个点的颜色变化,SetPixel 只是一个,BitBlt复制数据->Invalid。SetPixel (复制数据->Invalid)要重复 n 次。
另外,BitBlt 具体是由显示驱动完成的,也就是说,这个功能有可能会(基本上就是了)利用硬件加速,借由 PCI 等接口的高带块实现更高效率的内存拷贝,效率比 memcpy 还要快。

D3d或者GL应该是处理最快的方法,直接在显示缓冲区上绘图,并可得到硬件加速(如何芯片支持),其他的方法,即使你的处理算法再高效,你都绕不过BitBlt这关
------解决思路----------------------
有别的方法当然是好的。
但是我觉得你更应该从现有的GDI上下手,去优化你的代码,找到GDI中比较耗费时间的地方。
比如说:
1.绘图的CBitmap以及其对应的内存DC不要每次都创建,而是把他们设定为全局变量,同样,字体、画笔等能够不临时创建的都采用全局性的。
2.减少不必要的区域性绘图,比如绘制矩形、绘制圆形等等。
3.使用SetClip设定绘图区域,超出边界的尽量不绘制。
4.能够同时工作的尽量多开线程。
5.使用GetTickCount之类的方法计算你代码中最耗费时间段部分,看能不能优化(也可以用其他工具,我只会用这个)。
6.协同工作时,能不能使用共享内容等方式传递图像数据,而不是内次都拷贝或赋值,多用指针工作。

------解决思路----------------------
opencv? opengl?
http://blog.csdn.net/guanchanghui/article/details/1640597

用MFC如何高效地绘图

显示图形如何避免闪烁,如何提高显示效率是问得比较多的问题。而且多数人认为MFC的绘图函数效率很低,总是想寻求其它的解决方案。MFC的绘图效率的确不高但也不差,而且它的绘图函数使用非常简单,只要使用方法得当,再加上一些技巧,用MFC可以得到效率很高的绘图程序。我想就我长期(呵呵当然也只有2年多)使用MFC绘图的经验谈谈我的一些观点。

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)中:

 CDCMemDC; //首先定义一个显示设备对象
 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();

上面的注释应该很详尽了,废话就不多说了。


4、如何提高绘图的效率
   我主要做的是电力系统的网络图形的CAD软件,在一个窗口中往往要显示成千上万个电力元件,而每个元件又是由点、线、圆等基本图形构成。如果真要在一次重绘过程重画这么多元件,可想而知这个过程是非常漫长的。如果加上了图形的浏览功能,鼠标拖动图形滚动时需要进行大量的重绘,速度会慢得让用户将无法忍受。怎么办?只有再研究研究MFC的绘图过程了。
    实际上,在OnDraw(CDC *pDC)中绘制的图并不是所有都显示了的,例如:你
在OnDraw中画了两个矩形,在一次重绘中虽然两个矩形的绘制函数都有执行,但是很有可能只有一个显示了,这是因为MFC本身为了提高重绘的效率设置了裁剪区。裁剪区的作用就是:只有在这个区内的绘图过程才会真正有效,在区外的是无效的,即使在区外执行了绘图函数也是不会显示的。因为多数情况下窗口重绘的产生大多是因为窗口部分被遮挡或者窗口有滚动发生,改变的区域并不是整个图形而只有一小部分,这一部分需要改变的就是pDC中的裁剪区了。因为显示(往内存或者显存都叫显示)比绘图过程的计算要费时得多,有了裁剪区后显示的就只是应该显示的部分,大大提高了显示效率。但是这个裁剪区是MFC设置的,它已经为我们提高了显示效率,在进行复杂图形的绘制时如何进一步提高效率呢?那就只有去掉在裁剪区外的绘图过程了。可以先用pDC->GetClipBox()得到裁剪区,然后在绘图时判断你的图形是否在这个区内,如果在就画,不在就不画。
如果你的绘图过程不复杂,这样做可能对你的绘图效率不会有提高。

 

GDI绘制界面注意的几点

GDI绘图和openGL等其他绘图方式都是将图片绘制在指定屏幕的像素点上,不同的是GDI没有也不需要3D硬件加速等功能!所以GDI理论上的速度只和硬件有关。GDI刷子等概念并没有显卡硬件加速功能,所以要使用双缓冲区的概念。GDI+在这方面有所改进但也不太乐观。所谓双缓冲简单的说就是把图片绘制完成处理后形成一张定格的图片后在输出到显卡。

    当使用GDI绘制图片时(GDI本来就是绘制简单图片使用的)理论上只是期待最后形成的图片,其间处理的过程所用的时间用户的能够忍受的。当使用GDI绘制界面就完全不同了,用户是不能忍受任何界面的延时。

    所以GDI绘制界面要尽量使用绘制好的图片,不要使用刷子等工具现画。这不是水平高低的问题,前面说过GDI是没有硬件加速功能使用刷子等绘图工具会占用cpu使用量。界面又是用户反复操作,当操作速度过快时就会大量占用cpu使机器性能下降。

    是不是使用双缓冲,贴图片这样就ok了呢,答案是否定的。win32编程并不很好的支持gdi绘图的方式绘制界面。无论是mfc,还是atl或wtl,他们支持的都是以窗口为单位绘制界面的方式。在实践应用中发现窗口方式本身并不是效率很高的方式。消息机制的不确定性决定了窗口方式会出现大量的耦合因素。简单的说不要让任何的消息处理卡驻消息循环泵!否则在高效的界面都会看起来奇慢比。这里的矛盾是消息机制本身就要求了消息处理方式是分散式的,当分散的甚至是离散的或有相当隐蔽性消息处理方式点滴的速度延时累计起来就直接反映在了你的界面延时上了。界面是一个整体这和消息的分散处理方式是很矛盾的,分散的东西给人的印象就是可以越来越多,又不相互影响,但延时的时间却统一的反映在了一个界面上。

    这里又说道了界面设计的问题,最理想的方式是所有界面功能都立即返回近似于异步处理。但你想想用户点击了一个按钮立即返回了,而他的功能还在后面处理没有结果,用户是什么反映?软件有问题吗我点击了为什么没有反映?但这个时候锁住界面吗?当然不行。没个几秒钟延迟都要锁住界面用户还不恼怒?

    说的有点跑题了,我不会在这里提供一个万能的解决方案。软件大小不一样解决的方式也多种多样。这已经远远超出了一个界面的问题。变成1+1!=2的软件工程问题,软件还是没有银弹的。那么窗口既然会带来这么多问题在使用gdi绘制界面的时候就要格外小心使用窗口。尽量不要在同一窗口绘制所有功能元素比如一大堆的按钮,tab页,子窗口等等。祝你绘制界面愉快!