ダブルバッファリング

広告

アプレットでスレッドを使う際に重要なテクニックにダブルバッファリングがあります。これは描画に時間がかかる場合に、その途中経緯が見えてしまうのを防ぐ目的で利用します。

Graphicsクラスのオブジェクトへの描画処理は記述された順に行われて行きます。その為、例えばpaintメソッドが一度呼ばれる度に多数の処理を行うような場合、ちらつきの原因になってしまいます。

簡単な例で試してみます。下記は意味もなく大量の線を描画したボックスを移動させていくだけのアプレットです。

ThreadTest6.html

画面がちらついて見えるかと思います。1回paintメソッドが呼び出された時の処理が一瞬で終わっているように見えるのですが、実際に画面をキャプチャーしてみると下記のように、線を描画している処理の経緯が見えるのが原因です。

画面ちらつき

画面ちらつき

対応策としてよく用いられているのがダブルバッファリングです。これは描画処理用のイメージ領域を別に作成し、まず必要な描画を全てそのイメージ領域に行っておいてから、最後にイメージ領域をアプレットの領域にdrawImageメソッドでまとめて転送表示します。

このようにすることで、1回のpaintメソッドで必要な描画処理を全て完了した状態でアプレットの画面を更新しますので、アプレット上では描画処理の途中経過がまったく見えませんのでちらつきが起きなくなります。

まずダブルバッファリングを適用したものを見ていただいて、先ほどのものと比較して下さい。

ThreadTest7.html

ほとんどちらつきが無いと思います。

では手順を見ていきます。まず従来の方法は下記のようなものです。

public void paint(Graphics g){
  gに対する描画処理1
  gに対する描画処理2
  gに対する描画処理3
}

ダブルバッファリングを行う場合は下記のようになります。

public void paint(Graphics g){
  バッファイメージに描画処理1
  バッファイメージに描画処理2
  バッファイメージに描画処理3

  gにバッファイメージを描画
}

理屈としては、Gprahicsクラスのオブジェクトに対する描画処理を繰り返し行うとちらつきが起こるので、必要な描画処理は他でやっておいてから、1回でまとめて描画しましょうというものです。

ここで利用するバッファイメージには、外部の画像ファイルの処理にも使ったImageクラスのオブジェクトを使います。今回は外部ファイルからImageクラスのオブジェクトを作成するのではなく、アプレットに表示可能で必要な大きさを持つ空のイメージを作成しますので、Appletクラスの親クラスであるComponentクラスで用意されているcreateImageメソッドを使います。

ダブルバッファリングのために使用されるオフスクリーン描画イメージを生成し
ます。

パラメータ:
  width - 指定された幅
  height - 指定された高さ 
戻り値:
  ダブルバッファリングに使用できるオフスクリーン描画イメージ。コンポ
    ーネントが表示不可能な場合は null を返す。
    GraphicsEnvironment.isHeadless() が true を返す場合は常に null を返す

大きさはアプレットの領域の大きさだけ必要となりますので、実際には下記のように記述します。

Dimension size = getSize();
Image back = createImage(size.width, size.height);

これでアプレットの領域と同じ大きさのバッファイメージが作成できました。では、今度はこのイメージに描画を行っていきます。

イメージに対する描画は簡単です。今まではアプレットに描画するために用意されたGraphicsオブジェクトに対して描画していましたが、イメージ領域に描画するためのGraphicsオブジェクトを取得して、そのオブジェクトに対して描画するように変更するだけです。

イメージ領域に描画するためのGraphicsオブジェクトは、ImageクラスのgetGraphicsメソッドで取得できます。

オフスクリーンイメージに描画するためのグラフィックスコンテキストを作成し
ます。このメソッドは、オフスクリーンイメージにだけ呼び出すことができます。

戻り値:
  オフスクリーンイメージに描画するグラフィックスコンテキスト 
例外: 
  UnsupportedOperationException - オフスクリーンイメージ以外に対し
    て呼び出された場合

具体的には下記のような感じです。

Dimension size = getSize();
Image back = createImage(size.width, size.height);

Graphics buffer = back.getGraphics();

buffer.drawLine(10, 10, 100, 50);
buffer.drawOval(10, 10, 100, 50);

最後にイメージ領域を、アプレットの領域にまとめて表示します。外部の画像ファイルを表示する時にも使ったdrawImageメソッドを使います。

public void paint(Graphics g){
  Dimension size = getSize();
  Image back = createImage(size.width, size.height);

  Graphics buffer = back.getGraphics();

  buffer.drawLine(10, 10, 100, 50);
  buffer.drawOval(10, 10, 100, 50);

  g.drawImage(back, 0, 0, this);
}

これで完了です。実際に使う時は、アプレットの領域の取得やイメージ領域の作成、イメージ領域に対するGraphicsオブジェクトの取得は一度行って保存しておけばいいので、paintメソッドではなくinitメソッドなどで行っておけばいいかと思います。

サンプルプログラム

では先ほど見ていただいたアプレットのサンプルコードを見てください。

ThreadTest7.java

import java.applet.Applet;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;

/*
<applet code="ThreadTest7.class" codebase="class" width="150" height="150">
</applet>
*/

public class ThreadTest7 extends Applet implements Runnable{
  Thread thread = null;
  int x;
  Dimension size;
  Image back;
  Graphics buffer;

  public void init(){
    x = 10;

    size = getSize();
    back = createImage(size.width, size.height);
    buffer = back.getGraphics();

    thread = new Thread(this);
    thread.start();
  }

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

  public void paint(Graphics g){
    buffer.setColor(getBackground());
    buffer.fillRect(0, 0, size.width, size.height);

    for (int j = 0 ; j < 100 ; j++){
      for (int i = 0 ; i < 255 ; i++){
        buffer.setColor(new Color(i, 0, 0));
        buffer.drawLine(x + i, 10, x + i, 265);
      }

      for (int i = 0 ; i < 127 ; i++){
        buffer.setColor(new Color(0, 0, i * 2));
        buffer.drawLine(x, 10 + i * 2, x + 255, 10 + i * 2);
      }
    }

    g.drawImage(back, 0, 0, this);
  }

  public void run(){
    while(true){
      x += 1;
      if (x >= 100){
        x = 10;
      }

      repaint();

     /* 200ミリ秒待機する */
      try{
        Thread.sleep(200);
      }catch (InterruptedException e){
      }
    }
  }
}

実際の結果は先に紹介したとおりです。

ちなみにダブルバッファリングを行わなかった方のJavaプログラムも添付しておきます。

ThreadTest6.java

( Written by Tatsuo Ikura )