Java雙緩衝在畫板程序中的應用

 
       
1.用雙緩衝解決畫板程序中的刷新問題
 
        我們用Java編制畫板程序的時候,總是存在一個刷新的問題:當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版本,在Win98的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;
}

///:~

 
        運行一下,看是不是不一樣了.這一次你想讓你畫的東西消失都不可能了.爲了將這個原理說清楚,以上的代碼我都沒有編寫的太複雜,下一次我們會創建更加複雜,更加完善的畫板程序.
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章