オフスクリーンイメージは GUI部品がスクリーン上に描画されている時に, 「createImage(幅,高さ)」を呼び出すことによって,作ることができる.コ ンストラクタの中で createImageを呼んでも スクリーン上にない時は null (オブジェクトが存在しないことを表す値)を返す.
これまでのところで,スクリーン上にあることが保証されているのは,メソッ ドpaint の中である.paintの中で,最初に呼ばれた時は20x20のオフスクリー ンイメージを作り正方形を2つ書き(100,100)からdrawImageを実行 し,2回目以降は,そのオフスクリーンイメージを書くだけというプログラム を見てみる.
import java.awt.*; import java.awt.event.*; // キー入力をおこなうので,インタフェースKeyListenerの実装とする public class OffScreen extends Frame implements KeyListener{ // Frameに表示するのと同じ内容を保持する Image 型の変数 imageの宣言 Image image; // コンストラクタ public OffScreen(){ // 親クラスであるFrameクラスのコンストトラクタの呼び出し super("OffScreen"); // キーボードイベントの受取り手を自分自身にする. addKeyListener(this); } public static void main(String [] args){ // OffScreenクラスのインスタンスを作る OffScreen frame=new OffScreen(); // サイズを300x300に設定 frame.setSize(300,300); // 表示する. frame.setVisible(true); } public void paint(Graphics g){ // image に対してこれまでに代入が実行されたか? if(image==null){ // 20x20のサイズのオフスクリーンイメージを作成 // imageに値を入れるので, 次回はif文は不成立する. image=createImage(20,20); // image に書き込むための Graphics オブジェクトを得る Graphics g1=image.getGraphics(); // imageに着込む際の色を黒に設定 g1.setColor(Color.black); // (0,0)から幅20高さ20で長方形(この場合はたまたま正方形)を塗り潰し g1.fillRect(0,0,20,20); // imageに着込む際の色を白に設定 g1.setColor(Color.white); // (0,0)から幅10高さ10で長方形(この場合はたまたま正方形)を塗り潰し g1.fillRect(0,0,10,10); } // image の内容を (100,100)からウィンドウに描画 g.drawImage(image,100,100,this); } public void keyPressed(KeyEvent e){ int key=e.getKeyChar(); if(key=='q') System.exit(0); } public void keyReleased(KeyEvent e){} public void keyTyped(KeyEvent e){} }
このちらつきを生じさせないために,次に表示しようとする画面全体をオフス クリーンイメージで作成しておいて,drawImageで表示する方法が用いられる. これをダブルバッファリングと呼ぶ.ダブルバッファリングは, Component クラスのupdate メソッドをオーバーライドすることで実現できる.
// AWTを使うため, import java.awt.*; // イベント駆動関係のクラスを用いるため import java.awt.event.*; // 線分のクラスを定義する. class Line{ // 始点,終点のX座標,Y座標を int で保持する. public int start_x,start_y,end_x,end_y; // Lineのコンストラクタ public Line(int x1,int x2,int x3,int x4){ start_x=x1; start_y=x2; end_x=x3; end_y=x4; } } // インタフェースの実装では 「implements インタフェース名」と書く // 複数書く場合は,カンマで区切る class MouseDraw extends Frame implements KeyListener, MouseListener, MouseMotionListener{ // Lineの配列を保持する変数 linesの宣言 public Line[] lines; // linesのどこまで使われているかを示す変数 lineCountの宣言 int lineCount; // マウスをドラッグ中かどうかを示す boolean型(真偽値)の変数draggingの宣言 boolean dragging; // 表示する色を保持する変数 Color lineColor; // コンストラクタの宣言 public MouseDraw(String title){ // 親クラス Frameのコンストラクタの宣言 super(title); // 大きさ10のLineの配列を作成してlinesに代入 lines=new Line[10]; // linesは何も使われていない lineCount=0; // ドラッグ中ではない dragging=false; // 線の色は黒に lineColor=Color.black; // GUI部品と,Event Listenerを関連づける addKeyListener(this); addMouseListener(this); addMouseMotionListener(this); } public static void main(String args[]) { // MouseDrawのインスタンスを作成 frameに代入 MouseDraw frame=new MouseDraw("MouseDraw"); // サイズを 600x400に設定 frame.setSize(600,400); // 表示する. frame.setVisible(true); } Image offScreenImage; Graphics offScreenGraphics; public void update(Graphics g){ if(offScreenImage==null){ offScreenImage=createImage(600,400); // オフスクリーンイメージを600x400のサイズで作成 offScreenGraphics=offScreenImage.getGraphics(); // オフスクリーンイメージに描画するための Graphics オブジェクト } paint(offScreenGraphics); // 次の画面のイメージを作る. g.drawImage(offScreenImage,0,0,this); // イメージを本物のスクリーンに書き込む } // offScreenImageの書き直しをする際に呼ばれる public void paint(Graphics g){ int i; // 白で(0,0)-(600,400)を塗り潰す g.setColor(Color.white); g.fillRect(0,0,600,400); // 色を設定 g.setColor(lineColor); // lineCount個,linesに登録されているLineを描画する for(i=0;i< lineCount;i++){ g.drawLine(lines[i].start_x,lines[i].start_y, lines[i].end_x,lines[i].end_y); } // マウスをドラッグ中の時は if(dragging){ // 赤い色で g.setColor(Color.red); // lines[lineCount] を描画する. g.drawLine(lines[i].start_x,lines[i].start_y, lines[i].end_x,lines[i].end_y); } } // KeyListenerを実装するためのメソッド public void keyPressed(KeyEvent e){ // イベントからキーのコードを取り出す int key=e.getKeyChar(); // デバッグ用の表示 System.out.println("keyPressed("+e+","+key+")"); // 入力が 'q'の時は終了する if(key=='q') System.exit(0); } // 要らないイベントに対応するメソッドも中身は空で書いておく必要がある. public void keyReleased(KeyEvent e){} public void keyTyped(KeyEvent e){} // MouseListenerを実装するためのメソッド public void mousePressed(MouseEvent e){ // 押された時のマウスカーソルの位置を得る int mx=e.getX(),my=e.getY(); // デバッグ用の表示 System.out.println("mousePressed("+e+","+mx+","+my+")"); // 配列linesのlineCount番目に線分を登録 lines[lineCount]=new Line(mx,my,mx,my); // ドラッグ中であることを示す dragging=true; // 再表示をおこなう repaint(); } // マウスのボタンが離された時のイベント public void mouseReleased(MouseEvent e){ // マウスカーソルの位置を得る int mx=e.getX(),my=e.getY(); // デバッグ用の表示 System.out.println("mouseUp("+e+","+mx+","+my+")"); // 配列linesのlineCount番目の始点を変更 lines[lineCount].end_x=mx; lines[lineCount].end_y=my; // lineCountを増やす lineCount++; dragging=false; // 再表示をおこなう repaint(); } public void mouseClicked(MouseEvent e){} public void mouseEntered(MouseEvent e){} public void mouseExited(MouseEvent e){} // MouseMotionListenerを実装するためのメソッド public void mouseDragged(MouseEvent e){ // マウスカーソルの位置を得る int mx=e.getX(),my=e.getY(); // デバッグ用の表示 System.out.println("mouseDrag("+e+","+mx+","+my+")"); // 配列linesのlineCount番目の始点を変更 lines[lineCount].end_x=mx; lines[lineCount].end_y=my; // 再表示をおこなう repaint(); } public void mouseMoved(MouseEvent e){} }だけである.これを(オフスクリーンイメージの大きさを変更する必要はある が)加えることにより,多くのGUI プログラムはちらつきを押さえることがで きる.
しかし,センターの環境(Java VMの走るマシンが SPARC版 Solaris 2.6で, 表示はNC-OS上のX Window server )ではdrawImageのオーバヘッドが大きいた め,マウスの動きに追従してくれないようだ.
public void update(Graphics g){ paint(g); // 次の画面のイメージを作る. }これでちらつきをかなり押さえることができるが,さらに押さえる方法とし て, repaintの際にクリッピング範囲を指定することにより, 再描画する範囲を指定する方法がある.
MouseDrawのプログラムでいうと,MousePressed, MouseDragged, MouseReleased から呼ばれるrepaintでは再描画する必要があるのは,描画中 の線分の前書かれていた範囲と新たに書く範囲の和集合部分だけである.した がって,以下のように書けば良い.
// AWTを使うため, import java.awt.*; // イベント駆動関係のクラスを用いるため import java.awt.event.*; // 線分のクラスを定義する. class Line{ // 始点,終点のX座標,Y座標を int で保持する. public int start_x,start_y,end_x,end_y; // Lineのコンストラクタ public Line(int x1,int x2,int x3,int x4){ start_x=x1; start_y=x2; end_x=x3; end_y=x4; } } // インタフェースの実装では 「implements インタフェース名」と書く // 複数書く場合は,カンマで区切る class MouseDraw extends Frame implements KeyListener, MouseListener, MouseMotionListener{ // Lineの配列を保持する変数 linesの宣言 public Line[] lines; // linesのどこまで使われているかを示す変数 lineCountの宣言 int lineCount; // マウスをドラッグ中かどうかを示す boolean型(真偽値)の変数draggingの宣言 boolean dragging; // 表示する色を保持する変数 Color lineColor; // コンストラクタの宣言 public MouseDraw(String title){ // 親クラス Frameのコンストラクタの宣言 super(title); // 大きさ10のLineの配列を作成してlinesに代入 lines=new Line[10]; // linesは何も使われていない lineCount=0; // ドラッグ中ではない dragging=false; // 線の色は黒に lineColor=Color.black; // GUI部品と,Event Listenerを関連づける addKeyListener(this); addMouseListener(this); addMouseMotionListener(this); } public static void main(String args[]) { // MouseDrawのインスタンスを作成 frameに代入 MouseDraw frame=new MouseDraw("MouseDraw"); // サイズを 600x400に設定 frame.setSize(600,400); // 表示する. frame.setVisible(true); } public void update(Graphics g){ paint(g); } public void paint(Graphics g){ int i; // 白で(0,0)-(600,400)を塗り潰す g.setColor(Color.white); g.fillRect(0,0,600,400); // 色を設定 g.setColor(lineColor); // lineCount個,linesに登録されているLineを描画する for(i=0;i< lineCount;i++){ g.drawLine(lines[i].start_x,lines[i].start_y, lines[i].end_x,lines[i].end_y); } // マウスをドラッグ中の時は if(dragging){ // 赤い色で g.setColor(Color.red); // lines[lineCount] を描画する. g.drawLine(lines[i].start_x,lines[i].start_y, lines[i].end_x,lines[i].end_y); } } // KeyListenerを実装するためのメソッド public void keyPressed(KeyEvent e){ // イベントからキーのコードを取り出す int key=e.getKeyChar(); // デバッグ用の表示 System.out.println("keyPressed("+e+","+key+")"); // 入力が 'q'の時は終了する if(key=='q') System.exit(0); } // 要らないイベントに対応するメソッドも中身は空で書いておく必要がある. public void keyReleased(KeyEvent e){} public void keyTyped(KeyEvent e){} // MouseListenerを実装するためのメソッド public void mousePressed(MouseEvent e){ // 押された時のマウスカーソルの位置を得る int mx=e.getX(),my=e.getY(); // デバッグ用の表示 System.out.println("mousePressed("+e+","+mx+","+my+")"); // 配列linesのlineCount番目に線分を登録 lines[lineCount]=new Line(mx,my,mx,my); // ドラッグ中であることを示す dragging=true; // 再表示をおこなう repaint(mx,my,1,1); } // マウスのボタンが離された時のイベント public void mouseReleased(MouseEvent e){ // マウスカーソルの位置を得る int mx=e.getX(),my=e.getY(); // デバッグ用の表示 System.out.println("mouseUp("+e+","+mx+","+my+")"); // 配列linesのlineCount番目の始点を変更 int oldx=lines[lineCount].end_x; int oldy=lines[lineCount].end_y; lines[lineCount].end_x=mx; lines[lineCount].end_y=my; // lineCountを増やす dragging=false; // 再表示をおこなう int minx=Math.min(oldx,Math.min(mx,lines[lineCount].start_x)); int maxx=Math.max(oldx,Math.max(mx,lines[lineCount].start_x)); int miny=Math.min(oldy,Math.min(my,lines[lineCount].start_y)); int maxy=Math.max(oldy,Math.max(my,lines[lineCount].start_y)); lineCount++; repaint(minx,miny,maxx-minx+1,maxy-miny+1); } public void mouseClicked(MouseEvent e){} public void mouseEntered(MouseEvent e){} public void mouseExited(MouseEvent e){} // MouseMotionListenerを実装するためのメソッド public void mouseDragged(MouseEvent e){ // マウスカーソルの位置を得る int mx=e.getX(),my=e.getY(); // デバッグ用の表示 System.out.println("mouseDrag("+e+","+mx+","+my+")"); // 配列linesのlineCount番目の始点を変更 int oldx=lines[lineCount].end_x; int oldy=lines[lineCount].end_y; lines[lineCount].end_x=mx; lines[lineCount].end_y=my; // 再表示をおこなう int minx=Math.min(oldx,Math.min(mx,lines[lineCount].start_x)); int maxx=Math.max(oldx,Math.max(mx,lines[lineCount].start_x)); int miny=Math.min(oldy,Math.min(my,lines[lineCount].start_y)); int maxy=Math.max(oldy,Math.max(my,lines[lineCount].start_y)); repaint(minx,miny,maxx-minx+1,maxy-miny+1); } public void mouseMoved(MouseEvent e){} }クリッピングをおこなうというテクニックはオフスクリーンイメージと組み 合わせた場合も有効である.しかし,画面の変更のあった範囲の設定を間違え ると,画面が正しく書き変わらないので注意が必要となる.