雙緩衝在畫板程式中的應用(一) (轉)

amyz發表於2007-08-14
雙緩衝在畫板程式中的應用(一) (轉)[@more@]

/文 14E.T.

1.用雙緩衝解決畫板中的重新整理問題

我們用編制畫板程式的時候,總是存在一個重新整理的問題:當Canvas所在的視窗最小化或者被其他應用程式遮擋後,再次恢復,Canvas上的圖形資料將被部分或者完全擦除掉.

通常解決這個問題的方法是在Canvas的paint()中重繪圖形,但是由於在繪圖的過程中產生了大量的資料,重新在Canvas上繪製這些資料將導致大量的開銷,還會產生閃爍,故該方法可行但並不可取.

利用雙緩衝技術可以很好的解決這個問題,其主要原理是開闢兩個圖形緩衝區,一個是前臺的顯示緩衝(也就是Canvas),一個是後臺的圖形緩衝(通常是Image).在繪製圖形時,我們對這兩個緩衝區進行同步,相當於為前臺的資料作了一個後臺.當前臺的圖形被遮蓋需要恢復的時候,我們就可以用這個後臺備份來恢復,具體方法是重寫paint()函式,將備份好的影像一次性的畫到螢幕上去.

為什麼是paint()?這裡需要先了解一下有關Java處理AWT繪圖的基礎知識:Java的顯示更新是由一個AWT執行緒來控制完成的.該執行緒主要負責兩種與顯示更新相關的情況.第一種情況稱為曝光,表示部分顯示區域毀壞或需要清除.這種情況發生時,系統直接paint()方法;第二種情況是程式決定重畫顯示區域,新增一些新的內容,此時需要程式呼叫成員的repaint()方法,repaint()方法呼叫成員的update(),update()再呼叫paint()方法.顯然我們所說的就是第一種.只要Canvas所在的視窗最小化或者被其他應用程式遮擋住,系統就會呼叫paint()對畫布進行重繪.如果我們在paint()方法中什麼都不做,就只能眼睜睜的看著辛辛苦苦畫的線條一旦被覆蓋就再也看不見了.

作為起點,請先看一個最簡單的畫板程式,請注意,以下程式使用的是j2sdk1.4.1版本,在的IE下(不是Tencent Explorer)全部測試透過:

//:WBApplet.java

import java.applet.*;

public class WBApplet extends Applet{
 
final static int DEFAULT_BOARDWIDTH=700;
final static int DEFAULT_BOARDHEIGHT=400;

public void init(){
  super.init();
  setLayout(null);
  whiteBoard = new WhiteBoard(this);
  whiteBoard.reshape(0,0,DEFAULT_BOARDWIDTH,DEFAULT_BOARDHEIGHT);
  add(whiteBoard);
}

WhiteBoard whiteBoard;

}

///:~

//:WhiteBoard.java

java.awt.*;
import java.awt.event.*;

public class WhiteBoard extends Canvas implements MouseMotionListener,MouseListener{

  final static int DEFAULT_BOARDWIDTH=700;
  final static int DEFAULT_BOARDHEIGHT=400;
  int x0,y0,x1,y1;

  WhiteBoard(WBApplet WBApplet1){
  parent = WBApplet1;
  addMouseMotionListener(this);
  addMouseListener(this);
  }


  synchronized public void update_buffer(Graphics g,DrawItem data) {
  g.drawLine(data.x0,data.y0,data.x1,data.y1);
  }
 
  public void mouseReleased(MouseEvent e){}
  public void mouseEntered(MouseEvent e){}
  public void mouseExited(MouseEvent e){}
  public void mouseClicked(MouseEvent e){}
  public void mouseMoved(MouseEvent e){}

  public void mouseDragged(MouseEvent e){
  x1=e.getX();
  y1=e.getY();
  Graphics g = getGraphics();
  update_buffer(g,new DrawItem(x0,y0,x1,y1));
  g.dispose();
  x0=x1;
  y0=y1; 
  }

  public void mousePressed(MouseEvent e){
  x0 =e.getX();
  y0 =e.getY(); 
  } 

 

  WBApplet parent;
 
}

class DrawItem{
  DrawItem(int x0,int y0,int x1,int y1){
  this.x0=x0;
  this.y0=y0;
  this.x1=x1;
  this.y1=y1;
 }
  int x0;
  int y0;
  int x1;
  int y1;
}

///:~

我們將白板需完成的所有邏輯操作封裝在了一個WhiteBoard類中,以方便主程式的Applet呼叫.同時,定義了一個繪圖的資料類DrawItem,用來封裝圖形資料.繪圖的操作都寫在update_buffer中.顯然,這個程式無法實現重新整理後的恢復,我們需要使用雙緩衝技術.


為了實現雙緩衝,首先定義圖形緩衝區如下

private Image off_screen_buf;
private Graphics off_screen_gc;

並在WhiteBoard類的建構函式中對其進行初始化

off_screen_buf =parent.createImage(DEFAULT_BOARDWIDTH,DEFAULT_BOARDHEIGHT);
off_screen_gc = off_screen_buf.getGraphics();


在處理使用者繪製圖形的函式中,我們使用update_buffer對顯示緩衝和圖形緩衝同時進行更新

 Graphics g = getGraphics();
 update_buffer(g,new DrawItem(x0,y0,x1,y1));//前臺更新畫布
 update_buffer(off_screen_gc,new DrawItem(x0,y0,x1,y1));//後臺更新緩衝
 g.dispose();

顯然,後臺的更新操作是不可視的,所以是off-screen.

最後,重寫paint()方法,呼叫copy_from_offscreen_buf(g)將圖形緩衝區的影像畫到螢幕上去.

 public void paint(Graphics g){
  copy_from_offscreen_buf(g);
 }


 void copy_from_offscreen_buf(Graphics g){
  if(g != null)
  g.drawImage(off_screen_buf, 0, 0, null);
 }


就是這麼簡單的幾行程式碼,就可以讓我們完全的避免圖形不能恢復的問題.下面是WhiteBoard經改進後的完整程式碼.

//:WhiteBoard.java

import java.awt.*;
import java.awt.event.*;

public class WhiteBoard extends Canvas implements MouseMotionListener,MouseListener{

  final static int DEFAULT_BOARDWIDTH=700;
  final static int DEFAULT_BOARDHEIGHT=400;
  int x0,y0,x1,y1;

  WhiteBoard(WBApplet WBApplet1){
  parent = WBApplet1;
  off_screen_buf =parent.createImage(DEFAULT_BOARDWIDTH,DEFAULT_BOARDHEIGHT);
  off_screen_gc = off_screen_buf.getGraphics();
  addMouseMotionListener(this);
  addMouseListener(this);
  }


  synchronized public void update_buffer(Graphics g,DrawItem data) {
  g.drawLine(data.x0,data.y0,data.x1,data.y1);
  }
 
  public void mouseMoved(MouseEvent e){} 
  public void mouseReleased(MouseEvent e){}
  public void mouseEntered(MouseEvent e){}
  public void mouseExited(MouseEvent e){}
  public void mouseClicked(MouseEvent e){}
 
  public void mouseDragged(MouseEvent e){
  x1=e.getX();
  y1=e.getY();
  Graphics g = getGraphics();
  update_buffer(g,new DrawItem(x0,y0,x1,y1));
  update_buffer(off_screen_gc,new DrawItem(x0,y0,x1,y1)); 
  g.dispose();
  x0=x1;
  y0=y1; 
  }
 
  public void mousePressed(MouseEvent e){
  x0 =e.getX();
  y0 =e.getY(); 
  } 


  public void paint(Graphics g){
  copy_from_offscreen_buf(g);//把這句話遮蔽掉,就不能恢復使用者繪製的圖形了
  } 

  void copy_from_offscreen_buf(Graphics g){
  if(g != null)
  g.drawImage(off_screen_buf, 0, 0, null);
  }
 

  private Image off_screen_buf;
  private Graphics off_screen_gc;
  WBApplet parent;
 
}

class DrawItem{
  DrawItem(int x0,int y0,int x1,int y1){
  this.x0=x0;
  this.y0=y0;
  this.x1=x1;
  this.y1=y1;
 }
  int x0;
  int y0;
  int x1;
  int y1;
}

///:~

執行一下,看是不是不一樣了.這一次你想讓你畫的東西消失都不可能了.為了將這個原理說清楚,以上的程式碼我都沒有編寫的太複雜,下一次我們會建立更加複雜,更加完善的畫板程式.

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-956253/,如需轉載,請註明出處,否則將追究法律責任。

相關文章