提高二维矢量绘图效率之一般做法

作者:朱金灿
来源:http://blog.csdn.net/clever101

这个问题很普遍。最近在研究这个问题,在网上搜了一些资料,再结合自己的经验,谈谈自己的一些想法。

一、双缓存能提高绘图效率吗?

网上有篇文章:绘图效率完整解决方案——三种手段提高GDI/GDI+绘图效率,其中提到一种方法是:缓存——Bitmap或者DoubleBuffer。缓存就是先把绘制的图形绘制到一张内存位图上,然后在一次性的贴位图,他可以提高绘图速度,也能避免闪烁。DoubleBuffer=true是C#窗体的属性,设置了此属性估计系统本身会起用无效区的内存位图缓存,而不需要程序员Bitmap处理。

这里对双缓存的通常做法不作介绍,网上的相关资料很多。说实话,我对使用双缓存能提升绘图效率表示怀疑,理由很简单,同是DC,同是绘制1000条线段,有什么理由内存DC就比窗口DC快(当然这个我没有作具体的测试,这个有空可以测试下)。我还稍微怀疑使用双缓存绘图比直接使用窗口DC绘图还慢一些,理由有二:一是使用双缓存需要增加创建内存DC和内存位图的操作;二是使用双缓存还需要增加一个把内存DC拷贝到窗口DC的操作。那么双缓存的主要作用是什么?其实就是解决绘图过程的闪烁问题,改善绘图效果。

二、Windows环境下二维绘图引擎的选取

和绘图效率的一个重要相关因素是绘图引擎。Windows环境下二维绘图引擎有多种选择:GDI、GDI+、DirectDraw、QT、Agg、Cairo、skia、Direct2D、Direct3D、OpenGL等。下面我逐一作一个简单的分析:

【GDI】
微软原生的二维绘图引擎。
优点:微软的全力支持,作为操作系统核心层效率方面不用担心,支持多种开发框架(含语言):Win SDK、MFC、Delphi等。
缺点:基于过程,缺乏面向对象,使用起来不太方便,不支持反锯齿,不支持复杂的绘图效果(这个相对于GDI+而言)。

【GDI+】
微软后来推出的二维绘图引擎。
优点:微软的全力支持,支持多种开发框架(含语言):Win SDK、MFC、Delphi等,可以实现复杂的绘图效果,如反锯齿、路径画刷等;面向对象的架构,使用起来比较方便。
缺点:绘图效率较GDI稍低,绘图交互性不如GDI(缺少GDI的支持位运算的绘图模式),开启反锯齿后效率不如QT。

有关GDI和GDI+的详细比较,请看我以前写的一篇文章:GDI和GDI+的应用场合思考

【DirectDraw】
从GDI、GDI+到Direct 2D的一个过渡产品,微软已明确表示不推荐使用,在MicrosoftDirectX SDK (June 2010)已看不到它的身影,在此不作介绍。

【QT】
开源跨平台(基于LGPL协议),面向对象的方式组织,使用起来较为方便。

【Agg】
C++编写的开源绘图引擎(基于GPL协议)

【Cairo】
C编写的开源绘图引擎(基于LGPL协议),大名鼎鼎的FireFox就是用这个绘图引擎的。

有关Agg和Cairo请参考这篇文章:Agg vs. Cairo 二维绘图引擎之比较和选择。该文作者比较推崇Cairo,但据我公司的一个同事介绍:Cairo的绘图效率很慢。具体我没有做过测试。

【Skia】
Google的Android的绘图引擎。

【Direct2D】
微软在Windows Vista及之后的Windows版本推出的意在取代GDI、GDI+的二维绘图引擎,支持硬件加速。

【Direct3D】
微软开发的3D绘图引擎。

【OpenGL】
SGI开发的3D绘图引擎。

上面简单对 Windows下的二维绘图引擎作了一个简单介绍。我的推荐是:开发商业产品一般情况下在Windows XP及以下Windows版本使用GDI和GDI+,在Windows Vista及其之后的Windows版本(如Win 7)使用Direct2D。理由是:跨平台的绘图引擎如Agg、Cairo之类的,出于跨平台封装的需要,必然会牺牲一部分性能,就是说它本来就是封装GDI的,怎么可能超出GDI的绘图效率呢?还有就是诸如Agg还有开源协议的限制,这样就排除了开源二维绘图引擎。我也不推荐使用Direct3D、OpenGL等三维绘图引擎进行二维绘图,理由是三维绘图可以利用硬件加速,绘图速度应该比GDI、GDI+快,但三维绘图引擎一般是基于三维的数据结构进行组织的,对二维绘图并不合适,比如以前我们曾利用OpenGL进行二维绘图,发现OpenGL在二维一些操作并不合适,如二维中的点、线捕捉、自定义图例的添加、打印的支持等等。所以我倾向于使用GDI、GDI+。GDI的一大缺点是由于不是面向对象组织的,使用起来较为繁琐。这个我觉得可以参考GDI+的面向对象封装的方式对GDI进行封装。Direct 2D是微软在后XP时代开发的开发二维绘图引擎。微软出于兼容性的考虑还会继续对GDI、GDI+进行支持,但毫无疑问微软的策略是要Direct 2D取代GDI和GDI+的,因此在WindowsVista及其之后的Windows上进行二维绘图开发我建议是直接使用Direct2D。Direct 2D支持硬件加速,在绘图效率应有一定程度的提升。

三.提高GDI绘图效率的常用做法

提高GDI绘图效率的一般原则可以简单概括为:尽量减少无效绘图区域,尽量减少不必要的绘图操作。

尽量减少绘图区域的通常做法是:

  • 设置裁剪区。

裁剪区的作用就是:只有在这个区内的绘图过程才会真正有效,在区外的是无效的,即使在区外执行了绘图函数也是不会显示的。因为多数情况下窗口重绘的产生大多是因为窗口部分被遮挡或者窗口有滚动发生,改变的区域并不是整个图形而只有一小部分,这一部分需要改变的就是pDC中的裁剪区了。因为显示(往内存或者显存都叫显示)比绘图过程的计算要费时得多,有了裁剪区后显示的就只是应该显示的部分,大大提高了显示效率。但是这个裁剪区是MFC设置的,它已经为我们提高了显示效率,在进行复杂图形的绘制时如何进一步提高效率呢?那就只有去掉在裁剪区外的绘图过程了。可以先用pDC->GetClipBox()得到裁剪区,然后在绘图时判断你的图形是否在这个区内,如果在就画,不在就不画。

  • 减少无效区域。

在GDI绘图中,被标记为无效矩形的区域直到WM_PAINT消息被处理完之后才会消失。因此在绘图中应尽量避免使用Invalidate函数(该函数使整个客户区设置为无效区域),而应在多使用InvalidateRect函数具体比如你想改变某条线的线型,应首先精确计算改线的屏幕范围,然后改变其线型后调用InvalidateRect函数进行局部更新。

尽量减少不必要的绘图操作。比如二维矢量绘图中一般有一个滑动鼠标滚轮进行全图缩放的操作。其实在缩放过程中,每一次WM_MOUSEWHEEL消息的处理都必须是先把所有的绘图对象重绘一次。这个其实并没有必要。一个优化方案是通过设置一个标记,在绘图循环中(一般在复杂绘图中都把绘图对象保存在一个数组或链表中),当这个标记为TRUE时,就停止绘图,当这个标记为FALSE则不影响绘图。在处理WM_MOUSEWHEEL消息是,先设置这个标记为TRUE,然后发送一个重绘消息,设置标记为FALSE进行重绘。然后在绘图循环时先判断这个标记是否TRUE,为TRUE则退出绘图,然后截取鼠标消息,优先WM_MOUSEWHEEL消息,大致代码如下:

MSG msg;   
// hView为绘图窗口句柄  
if(PeekMessage(&msg,hView,WM_MOUSEFIRST,WM_MOUSELAST,PM_REMOVE  
))   
{  
     TranslateMessage(&msg);   
     DispatchMessage(&msg);   
}  

这样可以造成这样的效果:就是假如当进行第二次滚动,还在进行第一次滚动时的操作,就立即退出绘图循环,这样就能大大减少不必要的绘图操作。

参考文献:

绘图效率完整解决方案——三种手段提高GDI/GDI+绘图效率,作者:fyhui
GDI和GDI+的应用场合思考,作者:朱金灿
Agg vs. Cairo 二维绘图引擎之比较和选择,作者:张亮
用MFC如何高效地绘图
GDI使用经验总结,作者:杨涛
DrawCli代码中双缓冲,裁剪区技术以及坐标变换等技术分析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,432评论 25 707
  • 学习到了两句话 < 因小失大 > <贪多嚼不烂> 看了范冰冰演得潘金莲的电影瞬间颜范转忠实粉丝,因此记住了不能因小...
    大溪的清晨岛屿阅读 131评论 0 1
  • “一旦走出了那些与“习惯”之间的纠缠和挣扎,自然也就明白,有时我们只是有太多迟疑,有时我们只是臆想出了太多的恐惧,...
    乐小檬阅读 187评论 0 0
  • 风云际会,万事兴。
    10086好阅读 166评论 0 0