1. 对android中的surfaceview的困惑,双缓冲区该怎么理解
最近开发一款小游戏,需要用到surfaceView,出于效率的考虑,需要使用脏矩形刷新技术。一开始怎么都不成功,到网上搜了很多有关脏矩形的使用的文章,但总是不懂。后来就只能到google上去搜索英文的相关文章,但是也收获甚微。后来,直接看Android
Developers上面的解释,也是一懂半懂的。canvas
= holder.lockCanvas(Rect
dirty);中定义脏矩形刷新。我的理解是,给定dirty之后,系统会自动把前一个画布中dirty矩形外的部分拷贝过来,然后把dirty矩形内部留给现在的canvas来绘制。但是在项目的运行中,我发现根本不是这样的,系统好像不会自动拷贝dirty之外的部分过来,因为我的背景图片在绘制了一次之后,直接被黑色背景覆盖了。我查了一下AndroidDevelopers上面有关这个脏矩形的讲解,上面这样介绍:Just
likelockCanvas()but allows
specification of a dirty rectangle. Every pixel within that
rectangle must be written; however pixels outside the dirty
rectangle will be preserved by the next call to
lockCanvas()。意思就是说:在矩形内的每一个像素必须都要被写入,然后dirty矩形外的像素将在下次调用的时候保留。我在想,这个dirty脏矩形是不是为下次的绘图而准备的?后来又仔细想了一会,结合网上的有关surfaceView的双缓冲实现,我觉得可能问题是这样的:第一次画背景是画在前景帧上,缓冲帧没有。而第二次画时,系统把缓冲帧与前景帧调换了,这样由于之前的缓冲帧里面没有画背景,就导致第二次绘画中背景没有画出来。而第二次绘画又是使用的脏矩形绘制,将在下一次绘制的时候保留脏矩形之外的部分,导致第三次绘画时,虽然调出了最开始画了背景的那一帧,但是脏矩形机制填充了脏矩形之外的部分,导致背景再次被覆盖。自此,背景彻底从缓冲的两帧中消失了。
找到了原因所在,解决就比较好办了。直接在一开始的时候,调用两遍绘制背景的函数,这样保证缓冲的两帧上面都有背景,那么之后再用脏矩形,就没有问题了。
当然,明白了这些,就不难解释网上那些闪烁的问题的根源了。只需要在一开始把背景绘制两遍即可。
在解决这个问题的过程中,通过所查的资料,以及我单步跟踪调试,也发现了一个android的隐藏操作:就是在每次定制好脏矩形之后,android系统会自动重置脏矩形的大小尺寸(一般重置为当前整个画布的大小),所以,为了能够在下次循环的时候能够继续调用之前的脏矩形对象,就需要重置一下脏矩形的大小;或者还有一个办法,就是直接写一个函数,这个函数返回一个脏矩形的拷贝给canvas,这样canvas就只能更改这个拷贝,而不能更改脏矩形对象本身了。
2. 怎么用双缓冲解决java中关于Applet程序让图片动起来后如何解决屏闪
package yb_8_115;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class JSG extends Frame {
// 定义游戏关数0代表开始1代表第一关。。。
int state = 0;
// 定义全局变量用于拿到图片系统路径
// 得到默认工具包
Toolkit tk = Toolkit.getDefaultToolkit();
// 通过工具包拿到图片
Image start = tk.getImage(
// 拿路径
JSG.class.getResource("/image/start.png"));
Image bj2 = tk.getImage(JSG.class.getResource("/image/bj_2.png"));
Image dyg = tk.getImage(JSG.class.getResource("/image/lesson_1.png"));
Image heart = tk.getImage(JSG.class.getResource("/image/heart.png"));
Image songshu = tk.getImage(JSG.class.getResource("/image/songshu.png"));
public void init() {
new MusicFor("/music/tsg.mp3").musicplay();
new myThread().start();// 启动线程
this.setSize(800, 600);
// 窗口大小
this.setVisible(true);
// 可见性
this.setLocationRelativeTo(null);
// 居中
this.setResizable(false);
// 禁用最大化
this.setTitle("松鼠快跑");
// 标题设置
this.addWindowListener(new WindowAdapter() {
@Override public void windowClosing(WindowEvent e) {
System.exit(0);
// 退出系统
}
});
this.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
// e.getKeyCode获得键盘的按键 KeyEvent.VK_ENTER java定义的回车标识符
if (state == 0 && e.getKeyCode() == KeyEvent.VK_ENTER) {
state = 1;
}
}
});
}
@Override public void paint(Graphics g) {
if (state == 0) {
System.out.println("开始");// 打印开始界面
g.drawImage(start, 0, 0, getWidth(), getHeight(), this);
} else if (state == 1) {
System.out.println("第一关");// 打印第一关背景
g.drawImage(bj2, 0, 0, getWidth(), getHeight(), this);
g.drawImage(dyg, 328, 20, 145,51 , this);
g.drawImage(heart, 50, 520, 40,40 , this);
g.drawImage(heart, 90, 520, 40,40 , this);
g.drawImage(heart, 130, 520, 40,40 , this);
g.drawImage(songshu, 400, 450, 98,119 , this);
this.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_S);{
g.
}
}
});
}
}
/**
* @param args
*/
public static void main(String[] args) {
// 实例化一个对象mainframe对象
JSG jsg = new JSG();
jsg.init();
}
class myThread extends Thread {
public void run() {
while (true) {
repaint();// 重绘方法 ,这个方法会自动调用paint方法
}
}
}
// 双缓冲技术
@Override
public void update(Graphics g) {
//创建和窗体一样大小的图片
Image temp=createImage(getWidth(),getHeight());
//拿到缓冲图片的画笔
Graphics gh=temp.getGraphics();
//将图片加入窗体
print(gh);
//吧缓冲区的图片画到窗体上
g.drawImage(temp, 0, 0, this);
}
}
//其实很简单就是把缓冲区的图片画到窗体的最上面
3. 在android中怎样用双缓冲机制实现背景图片的循环移动
不断的重绘z这一zhang张背景图.X的坐标不断减去一个偏移量 如果背景的x坐标小于0的时候在dang'qian当前背景的weightde的地方绘制新de的背景 ha还是原来的bei'ji背景.新bei'j背景的X坐标为原背景的kua宽度
4. 控制台如何实现双缓冲
Java的强大特性让其在游戏编程和多媒体动画处理方面也毫不逊色。在Java游戏编程和动画编程中最常见的就是对于屏幕闪烁的处理。本文从J2SE的一个再现了屏幕闪烁的Java Appilication简单动画实例展开,对屏幕闪烁的原因进行了分析,找出了闪烁成因的关键:update(Graphics g)函数对于前端屏幕的清屏。由此引出消除闪烁的方法——双缓冲。双缓冲是计算机动画处理中的传统技术,在用其他语言编程时也可以实现。本文从实例出发,着重介绍了用双缓冲消除闪烁的原理以及双缓冲在Java中的两种常用实现方法(即在update(Graphics g)中实现和在paint(Graphics g)中实现),以期读者能对双缓冲在Java编程中的应用能有个较全面的认识。关键词:Java 消除闪烁 双缓冲一、问题的引入在编写Java多媒体动画程序或用Java编写游戏程序的时候,我们得到的动画往往存在严重的闪烁(或图片断裂)。这种闪烁虽然不会给程序的效果造成太大的影响,但着实有违我们的设计初衷,也给程序的使用者造成了些许不便。闪烁到底是什么样的呢?下面的JavaApplication再现了这种屏幕闪烁的情况://代码段一[①],闪烁的再现import java.awt.*;import java.awt.event.*;public class DoubleBuffer extends Frame//主类继承Frame类{ public paintThread pT;//绘图线程 public int ypos=-80; //小圆左上角的纵坐标 public DoubleBuffer()//构造函数 { pT=new paintThread(this); this.setResizable(false); this.setSize(300,300); //设置窗口的首选大小 this.setVisible(true); //显示窗口 pT.start();//绘图线程启动 } public void paint(Graphics scr) //重载绘图函数 { scr.setColor(Color.RED);//设置小圆颜色 scr.fillOval(90,ypos,80,80); //绘制小圆 } public static void main(String[] args) { DoubleBuffer DB=new DoubleBuffer();//创建主类的对象 DB.addWindowListener(new WindowAdapter()//添加窗口关闭处理函数 { public void windowClosing(WindowEvent e) { System.exit(0); }}); }}class paintThread extends Thread//绘图线程类{ DoubleBuffer DB; public paintThread(DoubleBuffer DB) //构造函数 { this.DB=DB; } public void run()//重载run()函数 { while(true)//线程中的无限循环 { try{ sleep(30); //线程休眠30ms }catch(InterruptedException e){} DB.ypos+=5; //修改小圆左上角的纵坐标 if(DB.ypos>300) //小圆离开窗口后重设左上角的纵坐标 DB.ypos=-80; DB.repaint();//窗口重绘 } }}编译、运行上述例子程序后,我们会看到窗体中有一个从上至下匀速运动的小圆,但仔细观察,你会发现小圆会不时地被白色的不规则横纹隔开,即所谓的屏幕闪烁,这不是我们预期的结果。这种闪烁是如何出现的呢?首先我们分析一下这段代码。DoubleBuffer的对象建立后,显示窗口,程序首先自动调用重载后的paint(Graphics g)函数,在窗口上绘制了一个小圆,绘图线程启动后,该线程每隔30ms修改一下小圆的位置,然后调用repaint()函数。注意,这个repaint()函数并不是我们重载的,而是从Frame类继承而来的。它先调用update(Graphics g)函数,update(Graphics g)再调用paint(Graphics g)函数[②]。问题就出在update(Graphics g)函数,我们来看看这个函数的源代码:public void update(Graphics g){if (isShowing()){ if (! (peer instanceof LightweightPeer)){ g.clearRect(0, 0, width, height); } paint(g); }} 以上代码的意思是:(如果该组件是轻量组件的话)先用背景色覆盖整个组件,然后再调用paint(Graphics g)函数,重新绘制小圆。这样,我们每次看到的都是一个在新的位置绘制的小圆,前面的小圆都被背景色覆盖掉了。这就像一帧一帧的画面匀速地切换,以此来实现动画的效果。 但是,正是这种先用背景色覆盖组件再重绘图像的方式导致了闪烁。在两次看到不同位置小圆的中间时刻,总是存在一个在短时间内被绘制出来的空白画面(颜色取背景色)。但即使时间很短,如果重绘的面积较大的话花去的时间也是比较可观的,这个时间甚至可以大到足以让闪烁严重到让人无法忍受的地步。 另外,用paint(Graphics g)函数在屏幕上直接绘图的时候,由于执行的语句比较多,程序不断地改变窗体中正在被绘制的图象,会造成绘制的缓慢,这也从一定程度上加剧了闪烁。 就像以前课堂上老师用的旧式的幻灯机,放完一张胶片,老师会将它拿下去,这个时候屏幕上一片空白,直到放上第二张,中间时间间隔较长。当然,这不是在放动画,但上述闪烁的产生原因和这很类似。二、问题的解决知道了闪烁产生的原因,我们就有了更具针对性的解决闪烁的方案。已经知道update(Graphics g)是造成闪烁的主要原因,那么就从这里入手。(1) 尝试这样重载update(Graphics g)函数(基于代码段一修改):public void update(Graphics scr){ paint(scr);} 以上代码在重绘小圆之前没有用背景色重绘整个画面,而是直接调用paint(Graphics g)函数,这就从根本上避免了上述的那幅空白画面。 看看运行结果,闪烁果然消除了!但是更大的问题出现了,不同时刻绘制的小圆重叠在一起形成了一条线!这样的结果我们更不能接受了。为什么会这样呢?仔细分析一下,重载后的update(Graphics g)函数中没有了任何清屏的操作,每次重绘都是在先前已经绘制好的图象的基础上,当然会出现重叠的现象了。2)使用双缓冲:这是本文讨论的重点。所谓双缓冲,就是在内存中开辟一片区域,作为后台图象,程序对它进行更新、修改,绘制完成后再显示到屏幕上。 1、重载paint(Graphics g)实现双缓冲: 这种方法要求我们将双缓冲的处理放在paint(Graphics g)函数中,那么具体该怎么实现呢?先看下面的代码(基于代码段一修改):在DoubleBuffer类中添加如下两个私有成员:private Image iBuffer;private Graphics gBuffer;重载paint(Graphics scr)函数:public void paint(Graphics scr){ if(iBuffer==null) { iBuffer=createImage(this.getSize().width,this.getSize().height); gBuffer=iBuffer.getGraphics(); } gBuffer.setColor(getBackground()); gBuffer.fillRect(0,0,this.getSize().width,this.getSize().height); gBuffer.setColor(Color.RED); gBuffer.fillOval(90,ypos,80,80); scr.drawImage(iBuffer,0,0,this);} 分析上述代码:我们首先添加了两个成员变量iBuffer和gBuffer作为缓冲(这就是所谓的双缓冲名字的来历)。在paint(Graphics scr)函数中,首先检测如果iBuffer为null,则创建一个和屏幕上的绘图区域大小一样的缓冲图象,再取得iBuffer的Graphics类型的对象的引用,并将其赋值给gBuffer,然后对gBuffer这个内存中的后台图象先用fillRect(int,int,int,int)清屏,再进行绘制操作,完成后将iBuffer直接绘制到屏幕上。 这段代码看似可以完美地完成双缓冲,但是,运行之后我们看到的还是严重的闪烁!为什么呢?回想上文所讨论的,问题还是出现在update(Graphics g)函数!这段修改后的程序中的update(Graphics g)函数还是我们从父类继承的。在update(Graphics g)中,clearRect(int,int,int,int)对前端屏幕进行了清屏操作,而在paint(Graphics g)中,对后台图象又进行了清屏操作。那么如果保留后台清屏,去掉多余的前台清屏应该就会消除闪烁。所以,我们只要按照(1)中的方法重载update(Graphics g)即可:public void update(Graphics scr){ paint(scr);}这样就避开了对前端图象的清屏操作,避免了屏幕的闪烁。虽然和(1)中用一样的方法重载update(Graphics g),但(1)中没有了清屏操作,消除闪烁的同时严重破坏了动画效果,这里我们把清屏操作放在了后台图象上,消除了闪烁的同时也获得了预期的动画效果。2、重载update(Graphics g)实现双缓冲: 这是比较传统的做法。也是实际开发中比较常用的做法。我们看看实现这种方法的代码(基于代码段一修改):在DoubleBuffer类中添加如下两个私有成员:private Image iBuffer;private Graphics gBuffer;重载paint(Graphics scr)函数:public void paint(Graphics scr){ scr.setColor(Color.RED); scr.fillOval(90,ypos,80,80);}重载update(Graphics scr)函数:public void update(Graphics scr){ if(iBuffer==null) { iBuffer=createImage(this.getSize().width,this.getSize().height); gBuffer=iBuffer.getGraphics(); } gBuffer.setColor(getBackground()); gBuffer.fillRect(0,0,this.getSize().width,this.getSize().height); paint(gBuffer); scr.drawImage(iBuffer,0,0,this);} 分析上述代码:我们把对后台图象的创建、清屏以及重绘等一系列动作都放在了update(Graphics scr)函数中,而paint(Graphics g)函数只是负责绘制什么样的图象,以及怎样绘图,函数的最后实现了后台图象向前台绘制的过程。运行上述修改后的程序,我们会看到完美的消除闪烁后的动画效果。就像在电影院看电影,每张胶片都是在后台准备好的,播放完一张胶片之后,下一张很快就被播放到前台,自然不会出现闪烁的情形。 为了让读者能对双缓冲有个全面的认识现将上述双缓冲的实现概括如下:(1) 定义一个Graphics对象gBuffer和一个Image对象iBuffer。按屏幕大小建立一个缓冲对象给iBuffer。然后取得iBuffer的Graphics赋给gBuffer。此处可以把gBuffer理解为逻辑上的缓冲屏幕,而把iBuffer理解为缓冲屏幕上的图象。(2) 在gBuffer(逻辑上的屏幕)上用paint(Graphics g)函数绘制图象。(3) 将后台图象iBuffer绘制到前台。以上就是一次双缓冲的过程。注意,将这个过程联系起来的是repaint()函数。paint(Graphics g)是一个系统调用语句,不能由程序员手工调用。只能通过repaint()函数调用。三、问题的扩展1、关于闪烁的补充:其实引起闪烁的不仅仅是上文提到的那样,多种物理因素也可以引起闪烁,无论是CRT显示器还是LCD显示器都存在闪烁的现象。本文只讨论软件编程引起的闪烁。但是即使双缓冲做得再好,有时也是会有闪烁,这就是硬件方面的原因了,我们只能修改程序中的相关参数来降低闪烁(比如让画面动得慢一点),而不是编程方法的问题。2、关于消除闪烁的方法的补充: 上文提到的双缓冲的实现方法只是消除闪烁的方法中的一种。如果在swing中,组件本身就提供了双缓冲的功能,我们只需要进行简单的函数调用就可以实现组件的双缓冲,在awt中却没有提供此功能。另外,一些硬件设备也可以实现双缓冲,每次都是先把图象画在缓冲中,然后再绘制在屏幕上,而不是直接绘制在屏幕上,基本原理还是和文中的类似的。还有其他用软件实现消除闪烁的方法,但双缓冲是个简单的、值得推荐的方法。2、关于双缓冲的补充:双缓冲技术是编写J2ME游戏的关键技术之一。双缓冲付出的代价是较大的额外内存消耗。但现在节省内存已经不再是程序员们考虑的最首要的问题了,游戏的画面在游戏制作中是至关重要的,所以以额外的内存消耗换取程序质量的提高还是值得肯定的。3、双缓冲的改进:有时动画中相邻的两幅画面只是有很少部分的不同,这就没必要每次都对整个绘图区进行清屏。我们可以对文中的程序进行修改,使之每次只对部分屏幕清屏,这样既能节省内存,又能减少绘制图象的时间,使动画更加连贯!
5. 什么是双缓冲
可是,我却对双缓冲这个词感到莫名其妙
双缓冲听起来好像很深奥,其实其本质很简单:就是先生成一张位图,然后把所有的绘图工作都画的这张位图上,然后再将这张位图一次性画到屏幕中去。
它的关键技术就是: 不要直接在屏幕上画图,而是将所有的绘图工作先绘制到图片上
我们研究下从开始绘图到屏幕显示图像的过程
第一步: 将所有的绘图缓冲到位图中
第二步:将位图拷贝到屏幕上,此时相当于所有的绘图又跳跃到了屏幕
绘图进行了两次跳跃:第一次跳跃到位图中,第二次跳跃到屏幕上,所以这种技术可以称之为双缓冲
闪烁的原因就是新图和旧图直接的差别造成的。
1) 更新时,先使用背景刷 刷一下背景,然后再贴图 背景刷和图像之间的差别能造成闪烁,这点可通过响应察除背景刷消息来消除 OnEraseBkgnd(CDC* pDC)
2) 先画一张白色图片,然后再在白色图片上画红色矩形
此时,背景刷问题解决了,但是在屏幕上 首先贴一张白图,然后再白图上画一个红色矩形,二者颜色差别明显,如果频繁绘图时,便不可避免的产生闪烁。
那如果使用双缓冲,此种现象便可以消除,分析如下:
先把白色图画到内存位图中,然后再将红色矩形画到这个内存位图中,最后将这个位图拷贝到屏幕上。
由于原屏幕上的图像与位图中的图像差别很小(都是白色图和红矩形),因此当频繁绘图时,可有效降低闪烁
鄙视一下 那些讲不清道理,又不肯给出实例的家伙--0--
以下是伪代码:
6. 为什么用了双缓冲绘图,图像还是闪烁
双缓存只是用来避免窗口内的局部闪烁,但避免不了整个窗口的整体闪烁.
如果使用了双缓存还是闪烁的话,就要考虑一下优化图象算法,内存的释放等等.
7. 关于java Applet 双缓冲技术显示图片的疑问
1, Graphics2D 相比Graphics功能更强大,以后用这个功能强大。所以是向后兼容;
BufferImage 就是缓冲图片,作用就是缓冲。先把图形绘制到BufferImage,然后把整张图片BufferImage直接显示出来。好比在食堂吃饭的时候,BufferImage相当于一个大托盘,这个大托盘里面装了几十碗(碟)小菜, 小二上菜的时候直接把这个大托盘一次端到客人桌子上就好了,只跑一次,效率高,速度快啊。BufferImage就起一个缓冲作用。
2、BufferGraphics 只是把图形绘制到 BufferImage 这张图片里,但这张图片只是在内存里,并没有显示出来。
g.drawImage(BufferImage,0,0,this) 这一句才把整张图片显示出来(店小二才把掌托盘菜端上桌),不调用就不显示图片(没有端出来,装满菜的托盘还在厨房呢,客人当然没菜吃!)。
BufferImage 最开始确实为空,但是applet在显示之前会先做初始化,也就是会执行init()方法。很显然init()方法里调用了createImage(***)这个方法来初始化BufferImage。后面显示出来的时候BufferImage当然就不为空了。
明白了吧!!!
8. java中如何实现对图片的双缓冲
参看Java2D中的BufferedImage用法。
9. 双缓冲的C#
C#双缓冲解释:
简单说就是当我们在进行画图操作时,系统并不是直接把内容呈现到屏幕上,而是先在内存中保存,然后一次性把结果输出来,如果没用双缓冲的话,你会发现在画图过程中屏幕会闪的很厉害,因为后台一直在刷新,而如果等用户画完之后再输出就不会出现这种情况,具体的做法,其实也就是先创建一个位图对象,然后把内容保存在里面,最后把图呈现出来。
GDI+的双缓冲问题
一直以来的误区:.net1.1 和 .net 2.0 在处理控件双缓冲上是有区别的。
.net 1.1 中,使用:this.SetStyle(ControlStyles.DoubleBuffer, true);
.net 2.0中,使用:this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
导致画面闪烁的关键原因分析:
一、绘制窗口由于大小位置状态改变进行重绘操作时
绘图窗口内容或大小每改变一次,都要调用Paint事件进行重绘操作,该操作会使画面重新刷新一次以维持窗口正常显示。刷新过程中会导致所有图元重新绘制,而各个图元的重绘操作并不会导致Paint事件发生,因此窗口的每一次刷新只会调用Paint事件一次。窗口刷新一次的过程中,每一个图元的重绘都会立即显示到窗口,因此整个窗口中,只要是图元所在的位置,都在刷新,而刷新的时间是有差别的,闪烁现象自然会出现。
所以说,此时导致窗口闪烁现象的关键因素并不在于Paint事件调用的次数多少,而在于各个图元的重绘。
根据以上分析可知,当图元数目不多时,窗口刷新的位置也不多,窗口闪烁效果并不严重;当图元数目较多时,绘图窗口进行重绘的图元数量增加,绘图窗口每一次刷新都会导致较多的图元重新绘制,窗口的较多位置都在刷新,闪烁现象自然就会越来越严重。特别是图元比较大绘制时间比较长时,闪烁问题会更加严重,因为时间延迟会更长。
解决上述问题的关键在于:窗口刷新一次的过程中,让所有图元同时显示到窗口。
二、进行鼠标跟踪绘制操作或者对图元进行变形操作时
当进行鼠标跟踪绘制操作或者对图元进行变形操作时,Paint事件会频繁发生,这会使窗口的刷新次数大大增加。虽然窗口刷新一次的过程中所有图元同时显示到窗口,但也会有时间延迟,因为此时窗口刷新的时间间隔远小于图元每一次显示到窗口所用的时间。因此闪烁现象并不能完全消除!
所以说,此时导致窗口闪烁现象的关键因素在于Paint事件发生的次数多少。
解决此问题的关键在于:设置窗体或控件的几个关键属性。
使用双缓冲
解决双缓冲的关键技术:
1、设置显示图元控件的几个属性: 必须要设置,否则效果不是很明显!
this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw |
ControlStyles.AllPaintingInWmPaint, true);
2、窗口刷新一次的过程中,让所有图元同时显示到窗口。
可以通过以下几种方式实现,这几种方式都涉及到Graphics对象的创建方式。
具体实现
1、 利用默认双缓冲
(1)在应用程序中使用双缓冲的最简便的方法是使用 .NET Framework 为窗体和控件提供的默认双缓冲。通过将 DoubleBuffered 属性设置为 true。
this.DoubleBuffered=true;
(2)使用 SetStyle 方法可以为 Windows 窗体和所创作的 Windows 控件启用默认双缓冲。
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
2、 手工设置双缓冲
.netframework提供了一个类BufferedGraphicsContext负责单独分配和管理图形缓冲区。每个应用程序域都有自己的默认 BufferedGraphicsContext 实例来管理此应用程序的所有默认双缓冲。大多数情况下,每个应用程序只有一个应用程序域,所以每个应用程序通常只有一个默认 BufferedGraphicsContext。默认 BufferedGraphicsContext 实例由 BufferedGraphicsManager 类管理。通过管理BufferedGraphicsContext实现双缓冲的步骤如下:
(1)获得对 BufferedGraphicsContext 类的实例的引用。
(2)通过调用 BufferedGraphicsContext.Allocate 方法创建 BufferedGraphics 类的实例。
(3)通过设置 BufferedGraphics.Graphics 属性将图形绘制到图形缓冲区。
(4)当完成所有图形缓冲区中的绘制操作时,可调用 BufferedGraphics.Render 方法将缓冲区的内容呈现到与该缓冲区关联的绘图图面或者指定的绘图图面。
(5)完成呈现图形之后,对 BufferedGraphics 实例调用释放系统资源的 Dispose 方法。
完整的例子,在一个400*400的矩形框内绘制10000个随机生成的小圆。
BufferedGraphicsContext current = BufferedGraphicsManager.Current; //(1)
BufferedGraphics bg;
bg = current.Allocate(this.CreateGraphics(),this.DisplayRectangle); //(2)
Graphics g = bg.Graphics;//(3)
//随机 宽400 高400
System.Random rnd = new Random();
int x,y,w,h,r,i;
for (i = 0; i < 10000; i++)
{
x = rnd.Next(400);
y = rnd.Next(400);
r = rnd.Next(20);
w = rnd.Next(10);
h = rnd.Next(10);
g.DrawEllipse(Pens.Blue, x, y, w, h);
}
bg.Render();//(4)
//bg.Render(this.CreateGraphics());
bg.Dispose();//(5)
3、 自己开辟一个缓冲区(如一个不显示的Bitmap对象),在其中绘制完成后,再一次性显示。
完整代码如下:
Bitmap bt = new Bitmap(400, 400);
Graphics bg = Graphics.FromImage(bt);
System.Random rnd = new Random();
int x, y, w, h, r, i;
for (i = 0; i < 10000; i++)
{
x = rnd.Next(400);
y = rnd.Next(400);
r = rnd.Next(20);
w = rnd.Next(10);
h = rnd.Next(10);
bg.DrawEllipse(Pens.Blue, x, y, w, h);
}
this.CreateGraphics().DrawImage(bt, new Point(0, 0));
另外一个例子,差不多
Graphics对象的创建方式:
a、在内存上创建一块和显示控件相同大小的画布,在这块画布上创建Graphics对象。
接着所有的图元都在这块画布上绘制,绘制完成以后再使用该画布覆盖显示控件的背景,从而达到“显示一次仅刷新一次”的效果!
实现代码(在OnPaint方法中):
Rectangle rect = e.ClipRectangle;
Bitmap bufferimage = new Bitmap(this.Width, this.Height);
Graphics g = Graphics.FromImage(bufferimage);
g.Clear(this.BackColor);
g.SmoothingMode = SmoothingMode.HighQuality; //高质量
g.PixelOffsetMode = PixelOffsetMode.HighQuality; //高像素偏移质量
foreach (IShape drawobject in doc.drawObjectList)
{
if (rect.IntersectsWith(drawobject.Rect))
{
drawobject.Draw(g);
if (drawobject.TrackerState == config.Mole.Core.TrackerState.Selected
&& this.CurrentOperator == Enum.Operator.Transfrom)//仅当编辑节点操作时显示图元热点
{
drawobject.DrawTracker(g);
}
}
}
using (Graphics tg = e.Graphics)
{
tg.DrawImage(bufferimage, 0, 0);//把画布贴到画面上
}
b、直接在内存上创建Graphics对象:
Rectangle rect = e.ClipRectangle;
BufferedGraphicsContext currentContext = BufferedGraphicsManager.Current;
BufferedGraphics myBuffer = currentContext.Allocate(e.Graphics, e.ClipRectangle);
Graphics g = myBuffer.Graphics;
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.HighSpeed;
g.Clear(this.BackColor);
foreach (IShape drawobject in doc.drawObjectList)
{
if (rect.IntersectsWith(drawobject.Rect))
{
drawobject.Draw(g);
if (drawobject.TrackerState == config.Mole.Core.TrackerState.Selected
&& this.CurrentOperator == Enum.Operator.Transfrom)//仅当编辑节点操作时显示图元热点
{
drawobject.DrawTracker(g);
}
}
}
myBuffer.Render(e.Graphics);
g.Dispose();
myBuffer.Dispose();//释放资源
至此,双缓冲问题解决,两种方式的实现效果都一样,但最后一种方式的占有的内存很少,不会出现内存泄露!
接下来是对acdsee拖动图片效果的实现。开始不懂双缓冲,以为双缓冲可以解决这个问题,结果发现使用了双缓冲没啥效果,请教了高人,然后修改了些代码,完成这个效果。
图片是在pictureBox1里。
Bitmap currentMap;
bool first = true;
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (zoom == 0)
{
if (e.Button == MouseButtons.Left) //dragging
mousedrag = e.Location;
Image myImage = myMap.GetMap();
currentMap = new Bitmap(myImage);
first = false;
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (zoom == 0&&!first)
{
Image img = new Bitmap(Size.Width, Size.Height);
Graphics g = Graphics.FromImage(img);
g.Clear(Color.Transparent);//图片移动后显示的底色
g.SmoothingMode = SmoothingMode.HighQuality; //高质量
g.PixelOffsetMode = PixelOffsetMode.HighQuality; //高像素偏移质量
g.DrawImageUnscaled(currentMap, new System.Drawing.Point(e.Location.X - mousedrag.X, e.Location.Y - mousedrag.Y));//在g中移动图片,原图在(0,0)画的,所以直接用new System.Drawing.Point(e.Location.X - mousedrag.X, e.Location.Y - mousedrag.Y)就好。
g.Dispose();
pictureBox1.Image = img;//img是在鼠标这个位置时生成被移动后的暂时的图片
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (zoom == 0)
{
System.Drawing.Point pnt = new System.Drawing.Point(Width / 2 + (mousedrag.X - e.Location.X),
Height / 2 + (mousedrag.Y - e.Location.Y));
myMap.Center = myMap.ImageToWorld(pnt);
pictureBox1.Image = myMap.GetMap();
first = true;
}
}
说说思路,在鼠标点下时创建一个bitmap,currentMap,用它来存放当前图像。鼠标移动时,根据鼠标位置画图,最后,鼠标up时,重新画图。
10. GDI绘图(双缓冲)
void drawimg(HWND hWnd,char *img)
{
HDC shedc;//设备DC
HDC weidc;//位图DC
HDC huandc;//缓冲DC
RECT g_rect;
HBITMAP g_bkbitmap,g_mbbitmap,g_hbitmapL;
GetClientRect(hWnd,&g_rect);
shedc = GetDC(hWnd);//设备DC
huandc = CreateCompatibleDC(shedc);//缓冲DC
weidc = CreateCompatibleDC(shedc); //位图DC
g_bkbitmap = CreateCompatibleBitmap(shedc,g_rect.right,g_rect.bottom);
SelectObject(huandc,g_bkbitmap);
g_mbbitmap = CreateBitmap(300,300,1,1,NULL);
g_hbitmapL = (HBITMAP)LoadImage(NULL,img,IMAGE_BITMAP,
g_rect.right,g_rect.bottom,LR_LOADFROMFILE);
SelectObject(weidc,g_hbitmapL);
BitBlt(huandc,0,0,g_rect.right,g_rect.bottom,weidc,0,0,SRCCOPY);
BitBlt(shedc,0,0,g_rect.right,g_rect.bottom,huandc,0,0,SRCCOPY);
DeleteDC(shedc);
DeleteDC(weidc);
DeleteDC(huandc);
DeleteObject(g_bkbitmap);
DeleteObject(g_mbbitmap);
DeleteObject(g_hbitmapL);
}