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);
}