利用GDI+的双缓冲技术来提高绘图效率

专栏作品
利用GDI+的双缓冲技术来提高绘图效率
卢彦

前言

进入.NET时代,Windows的绘图技术也从GDI升级到了GDI+,从名字就能知道GDI+是对以前传统GDI绘图技术的一次升级,不过在微软几乎把所有的新技术都冠之.NET的情况下,GDI+竟然不叫做GDI.NET,还真让我感到有点意外了。 :)

GDI+在一种与设备无关的环境下提供了一套统一的绘图编程模型,极大的提高了Windows绘图编程的方便性,我们再也不用创建什么各种各样复杂的设备环境了,说实话,我现在想起来都头疼。

题归正传,关于如何进行GDI+的基本编程,我不能过多的加以描述,如果有对此概念还不太清楚的朋友,建议先去了解一下相关的资料,我们在这里主要讨论的是一种提高绘图效率(主要是动画效率)的双缓冲技术在GDI+中的应用和实现。

实现目的

为了能清楚的对比应用双缓冲技术前后的效果,我编写了一段程序来进行测试。首先,我创建了一个普通的Windows Application,在主Form中,我放置了一个定时器:timer1,然后将它的Interval属性设置为10,然后在Form上放置两个按纽,分别用来控制定时器的开启和关闭,最后,我还放置了一个label控件,用来显示绘图的帧数。

测试程序

在timer1的timer1_Tick事件中,我写下了如下的代码(其中flag是一个bool型标志变量):

DateTime t1 = DateTime.Now;
Graphics g = this.CreateGraphics();
if(flag)
{
	brush = new LinearGradientBrush(new PointF(0.0f, 0.0f), 
		new PointF(700.0f, 300.0f), Color.Red, Color.Blue);
	flag = false;
}
else
{
	brush = new LinearGradientBrush(new PointF(0.0f, 0.0f), 
		new PointF(700.0f, 300.0f), Color.Blue, Color.Red);
	flag = true;
}
for(int j = 0; j < 60; j ++)
{
	for(int i = 0; i < 60; i++)
	{
		g.FillEllipse(brush, i * 10, j * 10, 10, 10);
	}
}
DateTime t2 = DateTime.Now;
TimeSpan sp = t2 - t1;
float per = 1000 / sp.Milliseconds;
this.label1.Text = "速度:" + per.ToString() + "帧/秒";

运行后,我点击“开始”按纽,效果如下图所示:

应用双缓冲以前的效果图(帧数:5帧/秒)

正如大家所看到的,我在程序中使用循环画了几百个圆形,然后在每次的定时器脉冲事件中使用不同方向的线性渐变来对它们进行填充,形成了一个动画效果。不过不幸的是,程序运行起来闪烁很严重,几乎每次刷新的时候都可以看到一条很明显的扫描线从上慢慢的刷到下来完成整幅图形的刷新动作。如果你不是要模拟老式雷达的区域扫描的话,这种速度不会满足你的要求。

改进代码

下面是我改进以后的代码:

DateTime t1 = DateTime.Now;
Bitmap bmp = new Bitmap(600, 600);
Graphics g = Graphics.FromImage(bmp);
if(flag)
{
	brush = new LinearGradientBrush(new PointF(0.0f, 0.0f), 
			new PointF(700.0f, 300.0f), Color.Red, Color.Blue);
	flag = false;
}
else
{
	brush = new LinearGradientBrush(new PointF(0.0f, 0.0f), 
			new PointF(700.0f, 300.0f), Color.Blue, Color.Red);
	flag = true;
}
for(int j = 0; j < 60; j ++)
{
	for(int i = 0; i < 60; i++)
	{
		g.FillEllipse(brush, i * 10, j * 10, 10, 10);
	}
}
this.CreateGraphics().DrawImage(bmp, 0, 0);
DateTime t2 = DateTime.Now;
TimeSpan sp = t2 - t1;
float per = 1000 / sp.Milliseconds;
this.label1.Text = "速度:" + per.ToString() + "帧/秒";

运行后,我点击“开始”按纽,效果如下图所示:

 

应用双缓冲以后的效果图(帧数:9帧/秒)

经过改进后,画面刷新速度大大加快,绝对看不到任何的“扫描线”,帧数也从5帧一下就提高到了9帧,几乎是两倍于前的速度。这究竟是什么原因呢?让我来讲述其中的道理。

因为圆是要一个一个画上去,所以每画一个圆,系统就要做一次图形的绘制操作,图形的重绘是很占用资源的,当需要重绘的图形数量很多的时候,所造成的系统开销就特别大,造成我们看到的那种刷新缓慢的情况。那么如何来解决这个问题呢?

答案就是双缓冲,何谓“双缓冲”?它的基本原理就是:先在内存中开辟一块虚拟画布,然后将所有需要画的图形先画在这块“虚拟画布”上,最后在一次性将整块画布画到真正的窗体上。因为所有的单个图形的绘制都不是真正的调用显示系统来“画”,所以不会占用显示系统的开销,极大的提高的绘图效率。

实现双缓冲的具体步骤

我再来详细解释一下刚才实现双缓冲的具体步骤:

1、在内存中建立一块“虚拟画布”:

Bitmap bmp = new Bitmap(600, 600);

2、获取这块内存画布的Graphics引用:

Graphics g = Graphics.FromImage(bmp);

3、在这块内存画布上绘图:

g.FillEllipse(brush, i * 10, j * 10, 10, 10);

4、将内存画布画到窗口中

this.CreateGraphics().DrawImage(bmp, 0, 0);

总结

怎么样?是不是很简单?但是正是这个简单的操作大大提高了绘图效率,所以如果你需要进行GDI+图形编程,双缓冲技术一定要掌握,特别是在进行大量图形绘制刷新的情况下要尽量采用。

2004年1月6日 0:14 - (阅读:10243;评论:25)
href="http://blog.joycode.com/5drush/Services/Pingback.aspx" rel="pingback" />

反馈

可不可以发个源代码给我
Email: ffoieoi@hotmail.net

 小心GDI 资源泄漏 2004-8-19 2:36
Ping Back来自:blog.csdn.net

 re: 利用GDI+的双缓冲技术来提高绘图效率 2004-8-19 2:38
您的例子中双缓冲部分会引起内存泄漏,详情请见。

 小心GDI 资源泄漏 2004-8-19 15:56
Ping Back来自:blog.csdn.net

 re: 利用GDI+的双缓冲技术来提高绘图效率 2004-8-20 17:06
用这个好多了,测试后证明的。

this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.DoubleBuffer, true);
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);


 re: 利用GDI+的双缓冲技术来提高绘图效率 2004-9-24 13:15
=======================================
WWW.ITZYK.NET IT资源库 ----> 专业的DoNet技术论坛
=======================================
www.itzyk.net 是一个刚刚创建起来的专业DoNet技术论坛,讨论与交流各种关于DoNet技术方面的信息,由于论坛正在起步阶段,需要大量高手,牛人们前来相助,特此诚邀各位兄弟姐妹们来参与论坛的建设,一起来打造属于我们的程序员们的专业技术论坛。

 re: 利用GDI+的双缓冲技术来提高绘图效率 2004-10-30 17:00
这种在内存中绘图的技术最多用在桌面应用程序中,对于游戏和真正的绘图引擎根本不能相提并论,所以提高什么绘图效率没有意义。再说在内存中的点阵不能直接与显存中的direct点阵相提并论。
现在显卡都是用了<5。0nm显存agp接口,以块传输方式,让显卡自己去读内存,一点都不耗cup而且速度不会小于1024*768*16*60。
所以大家一定记着绘图就用directx千万不要用gdi。

 利用GDI 的双缓冲技术来提高绘图效率(转) 2005-9-12 15:10
Ping Back来自:blog.csdn.net

 re: 利用GDI+的双缓冲技术来提高绘图效率 2005-9-16 22:49
手动创建交换链?
GDI+是用DD7做底层实现的,一定提供了自动交换链的方式。如果要手动创建交换链,效率怎么都不可能比硬件自动交换链快的。
而且GDI+绝不会因为画几百个圆性就下10帧的……
不过仍然顶一个,加油~

 re: 利用GDI+的双缓冲技术来提高绘图效率 2005-10-25 1:52
GDI+竟然不叫做GDI.NET,还真让我感到有点意外了。 :)

不是一个层面的东西,功力不够啊!


 re: 利用GDI+的双缓冲技术来提高绘图效率 2006-1-13 19:42
I don't understand.

 re: 利用GDI+的双缓冲技术来提高绘图效率 2006-4-10 14:55
64546546

 re: 利用GDI+的双缓冲技术来提高绘图效率 2006-5-29 23:19
请问如何用vb.net 或c#实现直接在桌面上画图?,或者打开一个winForm,要透明的,然后在上面画图,而画的图却不透明?

 re: 利用GDI+的双缓冲技术来提高绘图效率 2006-6-5 11:03
最后一个问题真的很菜

 re: 利用GDI+的双缓冲技术来提高绘图效率 2006-6-21 11:36
如果再配合多线程是不是能效果更好呢

 re: 利用GDI+的双缓冲技术来提高绘图效率 2006-8-15 20:25
to sanway:
我加了你说的那几个SetStyle方法,没用阿


 re: 利用GDI+的双缓冲技术来提高绘图效率 2006-8-28 0:28
xiao xue sheng

 re: 利用GDI+的双缓冲技术来提高绘图效率 2006-9-12 15:32
能否与GDI双缓冲对比一下?

 回复: 利用GDI+的双缓冲技术来提高绘图效率 2006-12-13 22:42
不懂。菜啊。。。。。。。。。。。。。。。

 回复: 利用GDI+的双缓冲技术来提高绘图效率 2006-12-13 22:42
不懂。菜啊。。。。。。。。。。。。。。。

 回复: 利用GDI+的双缓冲技术来提高绘图效率 2006-12-25 16:29
我看上面那位叫"郭龙"的先生对这个问题的分析很到位而且也很正确,有机会一起探讨探讨啊.

 回复: 利用GDI+的双缓冲技术来提高绘图效率 2007-1-6 9:40
不好意思今天才看到了效果。
双缓冲技术?
我有点怀疑了:
我验证了一下:
1。循环中的
for (int j = 0; j < 60; j++)
{
for (int i = 0; i < 60; i++)
{
g.FillEllipse(brush, i * 10, j * 10, 10, 10);
}
}
改为
for (int j = 0; j < 60; j++)
{
for (int i = 0; i < 60; i++)
{
gP.AddEllipse(i * 10, j * 10, 10, 10);
}
}
g.FillPath(brush, gP);
2再把用了Bitmap的方法来改为上面的(代码略)
结果如下:我电脑上源贴结果分别为4-6帧/秒和9-12帧/秒
我改后的结果均为16-21帧/秒
希望大家验证所谓的双缓冲技术。

 回复: 利用GDI+的双缓冲技术来提高绘图效率 2007-1-6 9:54
用了这个闪得更快,每秒帧数却没有变。
public void EnableDoubleBuffering()
{
// Set the value of the double-buffering style bits to true.
this.SetStyle(ControlStyles.DoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint,
true);
this.UpdateStyles();
}
初学,我不是太懂,所以请赐教!

 回复: 利用GDI+的双缓冲技术来提高绘图效率 2007-1-15 15:06
可以把源代码给我一份吗?
w81211984@126.com


 回复: 利用GDI+的双缓冲技术来提高绘图效率 2007-3-3 14:38
做了一个屏幕作图的软件,发现将image作为缓冲,当需要多次重绘而使用drawimage贴图特别的慢,耗资源,用GDI将一副位图选入memDC,然后graphics g(memDC.mhDC);然后在缓冲中作图,在重绘时用bitblt速度快,而且耗资源少
正像书上说的GDI+ 1.0使用于对速度要求不高的场合,我只好用GDI和GDI+来个拼接,不会OPENGL技术,不知道在GDI+中是不是由更好的方法了? 


 
#  回复: 利用GDI+的双缓冲技术来提高绘图效率 2004-6-24 16:54 good

你可能感兴趣的