イメージファイルの描画
簡単な図形であれば、Graphicsのメソッドで描画することができますが、より複雑なグラフィックとなると、描画メソッドですべて描くのはかなり大変です。こうした場合には、あらかじめイメージファイルを用意しておき、これを読み込んでコンポーネントに表示するのが一般的でしょう。
Graphicsには、イメージ描画のためのメソッドも用意されています。これは、java.awtパッケージのImageというクラスを描画するものです。このImageは、Javaでビットマップイメージを扱う際の基本となるもので、ファイルからビットマップイメージを読み込んでこのImageインスタンスとして取り出し、描画するのがJavaにおけるイメージ描画の基本となります。
では、実際にやってみましょう。ここでは、クラスファイルと同階層にある「sample.jpg」というイメージファイルを読み込み、表示してみます。
class MyPanel extends JPanel { Image image; public MyPanel(){ image = Toolkit.getDefaultToolkit().getImage("sample.jpg"); } public void paintComponent(Graphics g){ if (image != null){ g.drawImage(image, 0, 0, this); } } }

これもMyPanelのソースコードのみ掲載しておきます。その他は先のサンプルと同じです。ここでは、あらかじめ「sample.jpg」という名前のファイルを用意しておいてください。
イメージファイルの読み込みは、java.awtパッケージのDefaultToolkitというクラスを利用します。これは、Javaのコンポーネント類とネイティブな環境(Java仮想マシン外、つまりWindowsなどのOSの環境)との橋渡しをするクラスです。getDefaultToolkitというメソッドを使ってデフォルトのToolkitインスタンスを取得し、そこからメソッドを呼び出して利用します。
イメージファイルの読み込みは、このToolkitクラスにあるgetImageというメソッドを使います。これは引数に指定したファイルを読み込んでImageインスタンスを生成し返すものです。Toolkitの役割は、ある程度Javaの仕組みがわかってこないと理解するのは難しいでしょうから、とりあえずは「Toolkit.getDefaultToolkit().getImageと書けば指定のイメージをImageとして取り出せる」とだけ覚えてけばよいでしょう。
ただし、注意したいのは、「ファイルがうまく読み込めなくてもエラーにならない」という点です。このため、paintComponentでは、imageがnullかどうかをチェックし、nullでない場合に描画を行うようにしています。
描画は、drawImageというメソッドを使って行います。これは、描画するImage、描画位置(横位置と縦位置)、そして「イメージオブザーバ(ImageObserver)」と呼ばれるオブジェクトを引数に指定します。このイメージオブザーバというのは、イメージの状態などに関する状況を受け取るクラスです。といってもよくわからないでしょうが、イメージというのは大きなデータですので、読み込みに時間がかかったりと、通常の描画とは異なる部分があるのです。そこで、そうした状況を管理するクラスが必要となる、と考えてください。
AWT/Swingのコンポーネントは、すべてこのイメージオブサーバとしての機能を備えていますので、今の段階では「特に理由がない限り、イメージオブザーバにはthisでコンポーネント自身を指定する」とだけ理解しておけばよいでしょう。
drawImageによる変形、拡大縮小
このdrawImageは、引数の異なるものが多数用意されています。ここで、ざっと整理しておくことにしましょう。
[Graphics].drawImage( [Image],横位置,縦位置,[ImageObserver] );
[Graphics].drawImage( [Image],横位置,縦位置,[Color],[ImageObserver] );
[Graphics].drawImage( [Image],横位置,縦位置,横幅,縦幅,[ImageObserver] );
[Graphics].drawImage( [Image],横位置,縦位置,横幅,縦幅,
[Color],[ImageObserver] );
[Graphics].drawImage( [Image],横位置1,縦位置1,横位置2,縦位置2,
横位置A,縦位置A,横位置B,縦位置B,[ImageObserver] );
[Graphics].drawImage( [Image],横位置1,縦位置1,横位置2,縦位置2,
横位置A,縦位置A,横位置B,縦位置B,[Color],[ImageObserver] );
非常に多くの書き方が用意されていますが、それは描画元/描画先の位置と大きさの違いです。整理すると、次のような描画が用意されていることになります。
横位置,縦位置のみを指定した場合は、指定の場所にイメージをそのまま描画します。横位置,縦位置,横幅,縦幅を指定した場合は、指定した位置と場所に描画もとのイメージを変形し描画します。横位置1,縦位置1,横位置2,縦位置2,横位置A,縦位置A,横位置B,縦位置Bを指定した場合には、描画先の横位置1,縦位置1から横位置2,縦位置2までの領域に、描画元の横位置A,縦位置Aから横位置B,縦位置Bの領域を描画します。
ざっと見れば、「そのまま描画」「元の絵を拡大縮小して描画」「元の絵の一部を切り出して描画」という3通りの描画を行うためのものであることが分かります。ちょっと分かりにくいので、実際にサンプルを作成してみましょう。まずは、図形の拡大縮小(変形)からです。
class MyPanel extends JPanel { Image image; public MyPanel(){ image = Toolkit.getDefaultToolkit().getImage("sample.jpg"); } public void paintComponent(Graphics g){ if (image != null){ int w = image.getWidth(this); int h = image.getHeight(this); for(int i = 10; i > 0; i--){ g.drawImage(image, 0, 0, w / 10 * i, h / 10 * i, this); } } } }

これは、図形を少しずつ縮小しながら重ねて描画した例です。drawImageのところを見ると、縦横幅の10分の1をi倍して描画していることが分かりますね。図形の縦横幅は、このようにして取得しています。
int w = image.getWidth(this); int h = image.getHeight(this);
getWidth/getHeightは、それぞれImageの横幅と縦幅を得るためのものです。引数には、イメージオブザーバ(this)が指定されます。Imageクラスには、このようにImageに関する情報を得るためのメソッドもいろいろと用意されているのです。
続いて、元絵の一部を切り出して描画するサンプルもあげておきましょう。これは元絵を縦横10分の1に切り出して、縦横位置の合計が偶数の箇所のみ描画した例です。
class MyPanel extends JPanel { Image image; public MyPanel(){ image = Toolkit.getDefaultToolkit().getImage("sample.jpg"); } public void paintComponent(Graphics g){ if (image != null){ int w = image.getWidth(this) / 10; int h = image.getHeight(this) / 10; for(int i = 0; i < 10; i++){ for(int j = 0; j < 10; j++){ if ((i + j) % 2 == 0) g.drawImage(image, w * i, h * j, w * (i + 1), h * (j + 1), w * i, h * j, w * (i + 1), h * (j + 1), this); } } } } }

drawImageで元絵の一部分を切り出すときには、切り出す部分とそれを描画する部分を細かく指定しなければいけません。注意したいのは、この場合は領域の指定は「位置と幅」ではなく「2点の位置」による指定になることです。先のサンプルとは、引数の示すものが微妙に異なりますので、よく両者を比べて確かめておきましょう。
まとめ
今回は、JavaのGUIコンポーネントにおける描画の基本について説明しました。描画関係では、何よりもまず「描画の仕組み」をしっかりと頭に入れておく必要があります。Graphicsクラスのメソッドは、実際に何度か使ってみれば、比較的簡単に利用できるようになるでしょう。それよりも「どういう仕組みでコンポーネントの表示が行われているのか」を理解することが重要です。
コンポーネントでは、背景、ボーダー、コンポーネント独自の表示などさまざまな要素によって表示が構成されており、それらがきちんと描画されるようにいくつものメソッドによって描画が行われていきます。どのメソッドで何が描かれるのか、どれの次にどれが呼び出されるのか。その流れを把握しないまま、適当なメソッドをオーバーライドして使ってしまうと、コンポーネントや、そのコンポーネントに組み込まれているものの表示がおかしくなってしまいます。
Swingでは、組み込まれているコンポーネントはお互いに連携して表示を行っています。その流れを途中で分断しないよう、しっかりと全体の仕組みを理解して描画を行うように心がけましょう。
