目次


Standard Widget Toolkit でのJava 2D画像

Eclipseプラグインに2D画像のパワーをもたらす

Comments

SWT (Standard Widget Toolkit)はEclipseプラットフォームで使われるウィジェット・ツールキットですが、Swing/AWTの本格的な置き換えとして、任意のJava GUIアプリケーションの構築にも使用することができます。Eclipseプラットフォームが過去2年間で次第に普及してきたことから、SWTも注目を浴びるようになり、最近では一部のアプリケーションではSwing/AWTに置き換わるようにもなってきました。SWT普及の理由は、SWTがネイティブのウィジェットを利用したクロス・プラットフォームのツールキットであることと、Swingやその他最近のツールキットと同レベルの機能性を持っているという点にあります。SWTを使えば、移植性、機能性、パフォーマンスのうちどれかを選ぶしかない、ということがありません。

実際にはSwing/AWTの方が明らかにSWTよりも優れている領域があり、それがJava 2Dなのです。Java 2DはJDK 1.2で導入された強力なAPIです。これを使うことによってAWTコンポーネント上に描画する時に、複雑な2D変換(変換、回転、スケーリング、シアリング)が使えるようになります。残念ながらJava 2DはAWTまたはSwingツールキットでのみ使えるようにできており、SWTはまだそこまで強力な2D機能を提供していません。その結果多くの開発者は、Javaプラットフォーム上でJava 2Dを使うか、SWTを使いたいがために素晴らしい2D機能をあきらめるか、という選択をせざるを得ませんでした。

この記事では、両者の良い所を組み合わせて使うにはどうすべきかを学びます。ここでは簡単な手法でSWTコンポーネントやDraw2D図形上にJava 2D画像を描画できることを説明します。説明を理解するにはJava 2DやAWT、SWTに慣れている必要があります。EclipseプラットフォームでのGEF(Graphical Editing Framework)の経験も少しあれば、さらに理解がしやすいでしょう。

スクリーン外画像の手法

この記事ではJava 2D機能を使って、任意のSWTウィジェットまたはDraw2D図形上に描画するための簡単な手法を説明します。SWTにはJava 2D機能が無いので、これを補うためにスクリーン外AWT画像(offscreen AWT image)を使ってJava 2Dペイント操作を受け取り、ツールキットに依存しないピクセル値に変換します。このピクセル情報は次に、(今度はSWTツールキットが生成した)別のスクリーン外画像を使って任意のSWTコンポーネントにペイントされます。図1はAWTスクリーン外画像がSWT画像に変換され、SWTウィジェットにペイントされる過程を示しています。

図1. SWTでJava 2Dを使うためのスクリーン外画像の手法
SWTでJava 2Dを使うためのスクリーン外画像の手法
SWTでJava 2Dを使うためのスクリーン外画像の手法

図1で示したスクリーン外AWT画像は透明な背景で初期化されています。次にスクリーン外画像のグラフィックス・コンテキストに対してJava 2Dメソッドが呼び出されます。他のAWT画像と同様スクリーン外画像も、(自動的にJava 2D対応になっている)グラフィックス・コンテキストを持っています。Java 2D固有のペイントが全て終了するとAWT画像のピクセル値は抽出され、スクリーン外SWT画像に渡されます。次にこのSWT画像は通常の画像の場合と同様に、ツールキットのGC.drawImage(...)メソッドでSWTコンポーネント上にペイントされます。

この手順の各ステップをこれから説明します。

スクリーン外AWT画像を生成する

AWTスクリーン画像に対してはjava.awt.image.BufferedImageのインスタンスを使います。BufferedImageは画像であり、そのAPIからピクセル・データにアクセスすることができます。画像のピクセル値にアクセスできることによって、後でその画像をSWT画像に変換することができるのです。

AWTでバッファーした画像を構成する一番単純な方法は、コンストラクターpublic BufferedImage(int width, int height, int imageType)を使うことです。初めの2つのパラメーターは画像が持つサイズを表します。3番目のパラメーターは、生成する画像のタイプを規定する定数を受け取ります。画像タイプが取り得る値はBufferedImageクラスで宣言される定数TYPE_XXXの中の一つですが、ピクセル・データが画像中でどのように保存されるかを表します。カラー画像で一般的に使われる画像タイプのうち、最も重要なものは次のようなものです。

  • TYPE_BYTE_INDEXED: この画像タイプは、インデックス・カラーモデルを使うことを表します。インデックス・カラーモデルは、画像で使う各色が色の配列としてインデックス化されていることを意味します。ピクセルの値は(カラーモデル中にある、そのピクセルの色の)インデックスにすぎません。このタイプの画像の色数は、カラーモデルのサイズである256色に限定されます。各ピクセルは1バイトでコード化されるので、ピクセル情報をこの方法で保存すると画像をコンパクトに表現することができます。
  • TYPE_INT_RGB: この画像タイプはダイレクト・カラーモデルを使うことを表します。ダイレクト・カラーモデルは、各ピクセルの値がそのピクセルの色に関して完全な情報を持っているということを意味します。TYPE_INT_RGBは各ピクセルが一つの整数(4バイト)でコード化されていることを表します。各ピクセルにコード化されている情報はそのピクセルが使う色の赤、緑、青(RGB)要素です。各色要素は単一バイトでコード化されます。ピクセル値全体が4バイトでコード化されているので、1バイトは使われません。この内部表現ではインデックス・カラーモデルを使った場合よりも4倍メモリを消費しますが、画像が使用できる色数は約16万色(256 x 256 x 256)にまで増加します。
  • TYPE_INT_ARGB:TYPE_INT_RGBの場合と同じく、このタイプの画像はダイレクト・カラーモデルを使い各ピクセルを4バイトでコード化します。違いは、TYPE_INT_RGBでは使われていない4番目のバイトがこのタイプでは使用され、ピクセルの透明度を表します(透明度は、時には色のアルファ要素と言われる場合もあります)。このタイプの画像は背景を透明にしたり、画像自身を半透明にしたりすることができます。

スクリーン外画像の手法を生かすには、Java 2Dペイントで影響を受けるピクセルのみをSWTコンポーネント表面に持ってくるようにします。これを確実に行うには、初めの画像が透明な背景を持っている必要があります。ですからAWTスクリーン外画像にはTYPE_INT_ARGBタイプでバッファーした画像を使います。

画像をペイント、抽出する

次のステップはJava 2Dを使って画像をペイントすることです。まずGraphics2Dコンテキストを取得します。これにはメソッドcreateGraphics2D()を使うか、またはgetGraphics()を呼び出します。このコンテキストでペイントすると自動的に画像のピクセル・データが変わります。ペイントが終わると、画像のピクセル値はメソッドgetRGB(int startX, int startY, int w, int h, int rgbArray, int offset, int scansize)を使って、簡単かつ効率的に抽出できます。このメソッドを使うと、画像の長方形領域のピクセル・データを整数の配列に転送することができます。getRGB()メソッドのパラメーターは次の通りです。

  • startXstartYは画像から抽出する領域の左上部コーナーの座標です。
  • whは抽出する領域の幅と高さです。
  • rgbArrayはピクセル値を受け取る整数の配列です。
  • offsetは配列中のインデックスで、最初のピクセル値がコピーされる所を指します。
  • scansize は、2つのピクセルが画像中で連続した2本のライン上にあり、それぞれのラインで同じインデックスを持つ場合に、(配列中で)その2つのピクセルが持つインデックス・オフセットです。もしこの値が抽出する領域の幅と同じ場合には、あるラインの最初のピクセルは、その前のライン最後のピクセルの次を指すインデックスが指定する(配列中の)場所に保存されます。もしこの値が抽出する領域の幅よりも大きい場合には、あるラインの終わりとその次のラインの初めの間で、配列中の一部のインデックスは使われずに残ります。

ピクセル・データを保存する

図2はAWT画像の長方形領域を抽出した時にBufferedImage.getRGB(...)がどのように整数バッファーを満たすかを示しています。図の下側は整数バッファーを表します。各ボックスは1ピクセルの4バイトARGB値を含むバッファーの値を表しています。カッコ内の数字はピクセルの座標を表します。

図2. AWT画像からピクセル値を抽出する
AWT画像からピクセル値を抽出する
AWT画像からピクセル値を抽出する

この場合にはoffsetは何も使っていないので、最初のピクセルはバッファーのインデックス0に保存されます。scansizeの場合は抽出する領域の幅を使うので、抽出したラインの最初のピクセルは(バッファー中にある)その前のラインの最終ピクセルの後に続くことになります。こうしたパラメーターがあるので、整数バッファーはw*hの整数を含むほどに十分大きい必要があります。

各ピクセルの色情報が単純な整数バッファーに保存されれば、この情報をSWTスクリーン外画像に転送することができます。

SWT画像を生成する

SWTImageは、ピクセル・データを直接読み書き操作できるようにするという意味ではAWTBufferedImageと似ています。これはつまり、画像のデータを直接読み取るか修正することによって、画像内の任意のピクセル、または一群のピクセルのカラー値を取得・設定できるということを意味します。ただしこのSWT APIは対応するAWTのAPIとは大きく違っており、ずっと簡単に使えるようになっています。

SWTのImageクラスにはいくつかのコンストラクターがあり、これを使うと次のようなことができます。

  • ファイル名またはInputStreamをパラメーターとしてコンストラクターに渡すことで、既存の画像をロードする。画像のフォーマットはサポートされているもの、つまりBMP、GIF、JPG、PNG、Windows ICO等の内どれかである必要があります。
  • 規定サイズを持つ空の画像を構成する。この画像をペイントするには、そのピクセル値を変えるか、またはSWTグラフィックス・コンテキスト(GC)の内容をコピーします。
  • 既にバッファーに入っているピクセル値で初期化した画像を構成する。

ペイントされたAWT画像のコピーであるSWT画像を作るには、3番目のコンストラクターを使います。

ImageDataクラスについて

画像のピクセル・データに関する情報は、その画像のImageDataに含まれています。ImageDataは画像のサイズ、カラーパレット、色の値、透明度に関する情報を持つクラスです。ImageDataの持つフィールドの内、次のようなフィールドが特に注目すべきものです。

  • width and height は画像のサイズを規定します。
  • depth は画像のカラー諧調(color depth)を規定します。取り得る値としては1、2、4、8、16、24または32で、各ピクセル値のコード化に使うビット数を表します。
  • palette は画像のカラーモデルに関する情報を保存するPaletteDataオブジェクトを持っています。SWTのカラーモデルはAWTのカラーモデルと同様、インデックス・カラーモデルまたはダイレクト・カラーモデルです。インデックス・カラーモデルの場合には、PaletteDataは色のインデックスを持ちます。ダイレクト・カラーモデルの場合には、PaletteDataはピクセルの整数値から色のRGB要素をどのように抽出するかを表す、シフト情報を持ちます。
  • data はピクセル値を持つ、バイトのバッファーを持っています。AWTバッファーとは異なり、SWTバッファーは各ピクセルに対して一つのカラー値を持つ整数の配列ではなく、バイト値を持ちます。このバイトをどのようにコード化するかは使用する色諧調に依存します。8ビットの画像であれば、画像の1ピクセルの値はちょうど配列中の1バイトで表されます。16ビットの画像であれば、各ピクセル値はバッファーの2バイトにコード化されます。この2バイトは最小重みバイト順(least significant byte order)で保存されます。24ビットまたは32ビット画像の場合には各ピクセル値は、バッファーの3または4バイトに最大重みバイト順(most significant byte order)で保存されます。
  • bytesPerLine は、画像中のライン1本に対する全ピクセル値をコード化するのにバッファーの何バイトを使用するかを示します。
  • transparentPixel は画像中で透明色として使うピクセル値を表します。

ここでは透明色チャンネルが1つある、24ビット画像を使います。各ピクセルは配列中の3バイトに、RGB要素の順でコード化されます。

画像を変換する

画像のデータを知っているとAWT画像からSWT画像への変換が簡単にできます。バッファーにある整数(AWT画像がgetRGB(...)で返すものです)を単純に、SWT画像が受け取りを用意しているバイトのバッファーに移すのです。図3はSWT画像のバッファーでは値をどのように保存するかを示しています。

図3. ピクセル値をSWT画像に書く
ピクセル値をSWT画像に書く
ピクセル値をSWT画像に書く

図2の場合と同様、図3の下の方は画像バッファーの内部を表しています。ピクセルのカラー値がバッファーの中で表現されており、カッコ内の数字はピクセルの座標です。

各ピクセルは3バイトでコード化されていますが、24ビット画像の場合はピクセル中の1ラインのピクセル・サイズは必ずしも3*widthではありません。2ラインのピクセル間で、一部のインデックスは使われずに残る場合もあります。(次のラインがどのインデックスで始まるかを知るために)各ラインのピクセルに対して実際に何バイト使われているかを知るには、ImageDatabytesPerLineフィールドの値を使う必要があります。

SWT to Java 2Dレンダラー

リスト1はスクリーン外画像の手法を実装する、汎用レンダラーのソースコードです。このレンダラーではSWTコンポーネントまたはDraw2D図形上にペイントする場合に、Java 2Dルーチンを使っていることが分からないような使い方をすることができます。

リスト1. SWT/Draw2D Java 2Dレンダラー
package swtgraphics2d;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.widgets.Display;
/**
 * Helper class allowing the use of Java 2D on SWT or Draw2D graphical
 * context.
 * @author Yannick Saillet
 */
public class Graphics2DRenderer {
  private static final PaletteData PALETTE_DATA =
    new PaletteData(0xFF0000, 0xFF00, 0xFF);
  private BufferedImage awtImage;
  private Image swtImage;
  private ImageData swtImageData;
  private int[] awtPixels;
  /** RGB value to use as transparent color */
  private static final int TRANSPARENT_COLOR = 0x123456;
  /**
   * Prepare to render on a SWT graphics context.
   */
  public void prepareRendering(GC gc) {
    org.eclipse.swt.graphics.Rectangle clip = gc.getClipping();
    prepareRendering(clip.x, clip.y, clip.width, clip.height);
  }
  /**
   * Prepare to render on a Draw2D graphics context.
   */
  public void prepareRendering(org.eclipse.draw2d.Graphics graphics) {
    org.eclipse.draw2d.geometry.Rectangle clip =
      graphics.getClip(new org.eclipse.draw2d.geometry.Rectangle());
    prepareRendering(clip.x, clip.y, clip.width, clip.height);
  }
  /**
   * Prepare the AWT offscreen image for the rendering of the rectangular
   * region given as parameter.
   */
  private void prepareRendering(int clipX, int clipY, int clipW, int clipH) {
    // check that the offscreen images are initialized and large enough
    checkOffScreenImages(clipW, clipH);
    // fill the region in the AWT image with the transparent color
    java.awt.Graphics awtGraphics = awtImage.getGraphics();
    awtGraphics.setColor(new java.awt.Color(TRANSPARENT_COLOR));
    awtGraphics.fillRect(clipX, clipY, clipW, clipH);
  }
  /**
   * Returns the Graphics2D context to use.
   */
  public Graphics2D getGraphics2D() {
    if (awtImage == null) return null;
    return (Graphics2D) awtImage.getGraphics();
  }
  /**
   * Complete the rendering by flushing the 2D renderer on a SWT graphical
   * context.
   */
  public void render(GC gc) {
    if (awtImage == null) return;
    org.eclipse.swt.graphics.Rectangle clip = gc.getClipping();
    transferPixels(clip.x, clip.y, clip.width, clip.height);
    gc.drawImage(swtImage, clip.x, clip.y, clip.width, clip.height,
                 clip.x, clip.y, clip.width, clip.height);
  }
  /**
   * Complete the rendering by flushing the 2D renderer on a Draw2D
   * graphical context.
   */
  public void render(org.eclipse.draw2d.Graphics graphics) {
    if (awtImage == null) return;
    org.eclipse.draw2d.geometry.Rectangle clip =
      graphics.getClip(new org.eclipse.draw2d.geometry.Rectangle());
    transferPixels(clip.x, clip.y, clip.width, clip.height);
    graphics.drawImage(swtImage, clip.x, clip.y, clip.width, clip.height,
                       clip.x, clip.y, clip.width, clip.height);
  }
  /**
   * Transfer a rectangular region from the AWT image to the SWT image.
   */
  private void transferPixels(int clipX, int clipY, int clipW, int clipH) {
    int step = swtImageData.depth / 8;
    byte[] data = swtImageData.data;
    awtImage.getRGB(clipX, clipY, clipW, clipH, awtPixels, 0, clipW);
    for (int i = 0; i < clipH; i++) {
      int idx = (clipY + i) * swtImageData.bytesPerLine + clipX * step;
      for (int j = 0; j < clipW; j++) {
        int rgb = awtPixels[j + i * clipW];
        for (int k = swtImageData.depth - 8; k >= 0; k -= 8) {
          data[idx++] = (byte) ((rgb >> k) & 0xFF);
        }
      }
    }
    if (swtImage != null) swtImage.dispose();
    swtImage = new Image(Display.getDefault(), swtImageData);
  }
  /**
   * Dispose the resources attached to this 2D renderer.
   */
  public void dispose() {
    if (awtImage != null) awtImage.flush();
    if (swtImage != null) swtImage.dispose();
    awtImage = null;
    swtImageData = null;
    awtPixels = null;
  }
  /**
   * Ensure that the offscreen images are initialized and are at least
   * as large as the size given as parameter.
   */
  private void checkOffScreenImages(int width, int height) {
    int currentImageWidth = 0;
    int currentImageHeight = 0;
    if (swtImage != null) {
      currentImageWidth = swtImage.getImageData().width;
      currentImageHeight = swtImage.getImageData().height;
    }
    // if the offscreen images are too small, recreate them
    if (width > currentImageWidth || height > currentImageHeight) {
      dispose();
      width = Math.max(width, currentImageWidth);
      height = Math.max(height, currentImageHeight);
      awtImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
      swtImageData = new ImageData(width, height, 24, PALETTE_DATA);
      swtImageData.transparentPixel = TRANSPARENT_COLOR;
      awtPixels = new int[width * height];
    }
  }
}

このレンダラーは単一のユーティリティ・クラスに含まれています。このクラスはスクリーン外画像の手法で必要となるスクリーン外AWT画像、SWT画像への参照を保持、管理します。さらに言うと

  • フィールドswtImageDataawtPixelsはそれぞれ、SWT画像のピクセル値を持つバッファーと、ピクセル転送中にAWT画像のピクセル値を保持するために使われるバッファーです。
  • 定数TRANSPARENT_COLORはSWT画像で透明色として使用する色のRGB値を持ちます。背景をペイントするために透明チャンネルとして使う色を定義する必要があるので、このためにカラー値を1つ確保しておく必要があります。先のコードの中では、適当に選んだ値0x123456を使いました。このカラー値を使う任意のピクセルは透明なものとして扱われます。もしこの値がペイント操作に使うため必要に思われる場合には、透明を表すために他の色を使うこともできます。

レンダラーはどのように動作するのか

SWT/Draw2D Java 2Dレンダラーは次のように動作します。

  • レンダラーを使う前に、レンダラーのスクリーン外画像とバッファーを初期化しておく必要がありますが、そのサイズは少なくともペイントしようとしている領域と同じだけの大きさがある必要があります。これはメソッドprepareRendering(...)を呼び出した時に行われます。レンダリングをSWTグラフィックス・コンテキストに対して行うかDraw2Dグラフィックス・コンテキストに対して行うかにより、このメソッドはSWTGCまたはDraw2DGraphicsオブジェクトをパラメーターとして受け取ります。
  • 次にprepareRenderingは、グラフィックス・コンテキストからクリップ長方形(clip rectangle)を抽出します。クリップ長方形はピクセルの変更対象となる、最大の長方形領域です。
  • 次にプライベート・メソッドprepareRendering(int clipX, int clipY, int clipW, int clipH)が呼び出され、レンダリングするスクリーン外画像を用意します。このメソッドは使用するグラフィックス・コンテキストのタイプには依存せず、SWTでもDraw2Dでも使えます。prepareRendering()メソッドは次のように動作します。
    • 最初に、AWTまたはSWTスクリーン外画像がインスタンス化されているか、またペイントする領域を含むのに十分なだけ大きいかどうかをチェックします。これはメソッドcheckOffScreenImages(clipW, clipH)が行います。
    • スクリーン外画像が既にインスタンス化されていて大きさが十分でない場合には、その画像は捨てられ、要求のサイズで再生成されます。スクリーン外画像がペイントとする領域よりも大きい場合には再利用され、ペイントする領域に対応する部分のみが変更されます。
    • このチェックが終わると、ペイントする領域は透明チャンネルとして予約されている色、TRANSPARENT_COLORで塗りつぶされます。そうするとその画像はJava 2D操作で使えるようになります。
  • クリップ領域でJava 2Dペイント操作を受け付けるためのレンダラーの準備ができると、AWTBufferedImageからJava 2DGraphics2Dコンテキストが得られます。これがどのJava 2Dペイント・ルーチンでも使用するグラフィックス・コンテキストです。それぞれのペイント操作でAWTスクリーン外画像を変更します。
  • すべてのJava 2Dペイント操作が終了すると、ペイントした領域のピクセルはAWTスクリーン外画像からSWTスクリーン外画像に移され、次にSWTまたはDraw2Dグラフィックス・コンテキストにペイントされます。この操作はメソッドrender(GC)またはrender(Graphics)が行います。どちらのメソッドも内部的にプライベート・メソッドtransferPixels(...)を呼び出し、これがピクセルをAWTからSWTに移します。
  • レンダラーが必要なくなると、またはリソースを解放する必要があるときにはdispose()メソッドを呼びます。dispose()メソッドはレンダラーが使用したスクリーン外画像やバッファーを廃棄します。これによってリソースは解放されますが、レンダラーが再度必要になってバッファーを再生成する時には時間を無駄にすることになります。そのコンポーネントをどのくらい頻繁に再ペイントするか、再ペイントの領域がどのくらい大きいかにより、dispose()をいつ呼び出すべきかは変わってきます。

使用の例

リスト2はSWTCanvas上に回転したテキストをペイントするために、レンダラーをどのように使うかを示しています。

リスト2. SWT Canvasでの使用例
Canvas canvas = new Canvas(shell, SWT.NO_BACKGROUND);
final Graphics2DRenderer renderer = new Graphics2DRenderer();
canvas.addPaintListener(new PaintListener() {
  public void paintControl(PaintEvent e) {
    Point controlSize = ((Control) e.getSource()).getSize();
    GC gc = e.gc; // gets the SWT graphics context from the event
    renderer.prepareRendering(gc); // prepares the Graphics2D renderer
    // gets the Graphics2D context and switch on the antialiasing
    Graphics2D g2d = renderer.getGraphics2D();
    g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
      RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    // paints the background with a color gradient
    g2d.setPaint(new GradientPaint(0.0f, 0.0f, java.awt.Color.yellow,
      (float) controlSize.x, (float) controlSize.y, java.awt.Color.white));
    g2d.fillRect(0, 0, controlSize.x, controlSize.y);
    // draws rotated text
    g2d.setFont(new java.awt.Font("SansSerif", java.awt.Font.BOLD, 16));
    g2d.setColor(java.awt.Color.blue);
    g2d.translate(controlSize.x / 2, controlSize.y / 2);
    int nbOfSlices = 18;
    for (int i = 0; i < nbOfSlices; i++) {
      g2d.drawString("Angle = " + (i * 360 / nbOfSlices) + "\u00B0", 30, 0);
      g2d.rotate(-2 * Math.PI / nbOfSlices);
    }
    // now that we are done with Java 2D, renders Graphics2D operation
    // on the SWT graphics context
    renderer.render(gc);
    // now we can continue with pure SWT paint operations
    gc.drawOval(0, 0, controlSize.x, controlSize.y);
  }
});

コードに関する注記:

  • レンダラーが一度生成され、キャンバスに再ペイントする必要のある度に再使用されます。
  • キャンバスがインスタンス化され、その上にPaintListenerが追加されてペイント操作が実行されます。
  • グラフィックス・コンテキストgcPaintEventから得られます。
  • レンダラーがグラフィックス・コンテキストに対して用意されます。これはペイント操作を受け取るためのスクリーン外画像が用意できたことを意味します。
  • 次のステップではJava 2Dグラフィックス・コンテキスト、g2dが得られます。
  • ここから一連のJava 2Dペイント操作が実行され、色勾配を持った背景をペイントし、20度毎に回転したテキストを描きます。このプロセスでは変換(translation)を使用し、またグラフィックス・コンテキストの回転を数回使用します。
  • 最後にメソッドrender(GC)が呼び出され、Java 2Dペイント操作をSWTグラフィックス・コンテキストに渡します。同じペイント・ルーチンで、Java 2Dペイント操作と純SWTペイント操作を使用することができます。

レンダリング操作の結果

レンダリング操作の結果を図4に示します。この例ではレンダラーは廃棄されず、スクリーン外画像と内部バッファーはCanvasのペイント毎にリサイクルされるので、インストールとガーベジ・コレクションの時間が節約できます。思い出して欲しいのですが、キャンバスが頻繁に再ペイントされることがない場合、またはレンダラーが消費するリソースがあまりにも重要な場合には、各ペイント操作の後でレンダラーを廃棄することもできるのです。

図4. 使用例:Java 2Dルーチンの助けを借りてペイントしたSWTキャンバス
使用例:Java 2Dルーチンの助けを借りてペイントしたSWTキャンバス
使用例:Java 2Dルーチンの助けを借りてペイントしたSWTキャンバス

リスト3は同じ例をDraw2D図形に対して行うにはどうするかを示しています。Figureのペイントは、ここではFigureのメソッドpaintClientArea(Graphics)をオーバーライドすることで実現しています。Graphics2DRendererの使い方は先の例の場合と全く同じです。唯一の違いは、今回はSWTGCではなくDraw2DGraphicsをパラメーターとして、メソッドprepareRenderingrenderを呼び出している点です。

リスト3. Draw2D図形での使用例
final Graphics2DRenderer renderer = new Graphics2DRenderer();
IFigure figure = new Figure() {
  protected void paintClientArea(org.eclipse.draw2d.Graphics graphics) {
    Dimension controlSize = getSize();
    renderer.prepareRendering(graphics); // prepares the Graphics2D renderer
    // gets the Graphics2D context
    Graphics2D g2d = renderer.getGraphics2D();
    (...) // does the Java 2D painting
    // now that we are done with Java 2D, renders Graphics2D operation
    // on the Draw2D graphics context
    renderer.render(graphics);
    // now we can continue with pure Draw2D paint operations
    graphics.drawOval(0, 0, controlSize.width, controlSize.width);
  }
};

まとめ

この記事で説明した簡単な手法でSWTやGEFアプリケーションにJava 2D機能が単に組み込めるというだけではなく、その組み込みもごく簡単にできるのです。ここではSWTとJava 2Dの最高機能を組み合わせるにはどうすべきか、そしてSWTコンポーネントまたはGEF Draw2D図形にペイントするにはどうするかを、順を追って説明しました。ここで説明した手法はコード例からも分かるように、ごく単純なものです。外部ライブラリに依存することもなく、AWTスレッドを1本も開始することもなく、数行のコードで実現することができます。

この記事に挙げた実装コードは、任意のSWTまたはGEFアプリケーションに適用することができます。スクリーン外画像の手法はどんなプラットフォームでも動作します。SWTとAWTが同じアプリケーション上に共存できないLinux/Motifのようなプラットフォームでも同様です。この手法ではAWT画像からSWT画像にピクセル値を転送する以上に複雑なことはしていないので、SWTにおける他のAWTベースの画像APIを変換する場合にも同じ手法を適用することができます。


ダウンロード可能なリソース


関連トピック

  • この記事で説明したレンダラーのソースコードをダウンロードしてください。
  • Java 2D APIについて詳しく読んでみてください。
  • SWT画像についての記事としては他に、Joe Winchesterによる「Taking a look at SWT Images」があります。
  • Joe Winchesterによる「Graphics Context -- Quick on the draw」でSWTのペイント機能についてさらに詳しく学んでください。
  • SWT画像で2D変換を使う上での別の手法について、Chengdong Liが記事「A Basic Image Viewer」で説明しています。
  • Eclipse.orgのWebサイトにはEclipseやSWT、GEF/Draw2Dなどに関する豊富な記事や情報が用意されています。
  • developerWorksのJava technologyゾーンには、Javaプログラミングのあらゆる面に関する記事や情報が豊富に用意されています。
  • 無料で提供されているJavaベース・プログラミングのチュートリアル・リストについてはdeveloperWorksのJava tutorials pageを見てください。
  • Developer BookstoreにはJava関連の書籍を含め広範な話題の技術書が豊富に取り揃えられています。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=211749
ArticleTitle=Standard Widget Toolkit でのJava 2D画像
publish-date=06012004