アフィン変換のポイント
アフィン変換のポイントは、「Graphics2Dに用意されている変換用メソッドによる座標軸の操作」と、「java.awt.geom.AffineTransformクラスによる変換情報の作成」の2点となります。AffineTransformクラスは、変換に関する行列情報をまとめたもので、これは変換行列の個々の値を手作業で設定して作ることもできますし、あらかじめ用意されているメソッドを使い、平行移動・回転・拡大縮小のためのインスタンスを簡単に用意することもできます。
では、アフィン変換の流れについてざっと整理してみましょう。
- まず、
Graphics2Dに設定されているAffineTransformを変数などに保管します。これは、初期状態のAffineTransformのバックアップです。元の状態に戻すのに必要です。 - 変換のための
AffineTransformインスタンスを作成します。これは通常、AffineTransformに用意されているメソッドを使ってインスタンスを生成します(これは、用意しなくともいい場合もあります)。 Graphics2Dに用意されている変換用メソッドを呼び出して座標軸変換を行います。これは、単に数値を指定するだけで行なえるものと、AffineTransformインスタンスを適用して行うものがあります。- 描画を行います。変換された座標軸に従って図形が描画されます。
- すべての描画が終わったら、バックアップしてあった
AffineTransformを使い、座標軸を元に戻します。
作成したサンプルでは、座標軸の平行移動にAffineTransformを適用する方法を使い、座標軸の回転に直接数値で変換操作を行うメソッドを使う方法を使っています。これは、別に「平行移動と回転でやり方が違う」ということではなく、2通りの操作のサンプルとして挙げただけで、平行移動・回転・拡大縮小ともにいずれの方法でも行うことができます。――では、サンプルのソースコードを見てみましょう。
AffineTransform defaultAT = g.getTransform();
まず、デフォルトのAffineTransformインスタンスをバックアップします。Graphics2DのAffineTransformは、getTransformというメソッドで取得することができます。
g.setTransform(AffineTransform.getTranslateInstance(127, 127));
座標軸を平行移動しています。ここでは、AffineTransformインスタンスを設定する方式で操作をしています。AffineTransformにある「getTranslateInstance」により、平行移動のためのインスタンスを作成し、それを「setTransform」でGraphics2Dに設定しています。AffineTransformには、平行移動・回転・拡大縮小のためのインスタンスを生成する次のようなメソッドが用意されています。
getTranslateInstance(double dx, double dy)
getRotateInstance(double theta)
getRotateInstance(double theta, double x, double y)
getScaleInstance(double sx, double sy)
回転が2種類ありますが、1つはゼロ座標を中心とした回転、もう1つはアンカーポイントとなる座標を指定し、その地点を中心とした回転のためのものです。これらのメソッドを使ってAffineTransformインスタンスを取得し、それをsetTransformすればよいわけです。
さて、もう1箇所、変換を行なっているところがあります。for内にある、座標軸を回転させている変換部分です。
g.rotate(Math.PI / 8);
こちらはAffineTransformインスタンスを使わず、Graphics2Dに用意されている変換用メソッドを呼び出して行なっています。Graphics2Dには、座標軸変換を行うメソッドとして次のようなものが用意されています。
translate(double tx, double ty)
rotate(double theta)
rotate(double theta, double x, double y)
scale(double sx, double sy)
これらは、ただ単にメソッドを呼び出すだけで使えてしまいます。これらのメソッドは、先ほどのsetTransformと同じような働きをします。どちらを使っても基本的には同じですから、その時々で使いやすいほうを使えばよいでしょう。
こうして座標変換を行なって描画を行い、すべて終わったら、最後に座標軸をバックアップの状態に戻します。
g.setTransform(defaultAT);
これですべての作業は完了です。意外と簡単でしょう? 座標軸のバックアップ関係は、しなくても何かに影響が出ることはありません。その後でまた描画などを行うような場合には、予想しなかった形で描画されてしまうと困るので「変換したら最後に元に戻す」というのを習慣づけておく、ということでしょう。
実際にサンプルを見れば分かることですが、このアフィン変換は「変換がどんどん蓄積されていく」という点に注意してください。例えば「右に100移動する」という変換を2度適用すると、座標軸は右に200移動した状態になります。変換した座標軸にさらに変換が適用されていくわけです。
また、Graphics2Dの座標軸そのものを変更するわけですから、Shapeによる描画に限らず、旧来の描画用メソッドも含めて、すべての描画がこの影響を受けます。
この座標軸変換は、組み合わせていくことで非常に複雑な図形を簡単に描くことができます。特に、回転を組み合わせた描画は、すべて座標を計算して描画することを考えるとずいぶんと楽です。もう少し複雑なサンプルをあげておきましょう。
public void paint(Graphics2D g){ g.setColor(new Color(0,0,0)); g.fillRect(0, 0, 255, 255); Ellipse2D.Double elps = new Ellipse2D.Double(0d,0d,25d,25d); g.setStroke(new BasicStroke(1.5f)); AffineTransform defaultAT = g.getTransform(); g.setTransform(AffineTransform.getTranslateInstance(127, 127)); AffineTransform tempAT = g.getTransform(); for(int i = 0;i < 32;i++){ g.setTransform(tempAT); g.rotate(-Math.PI / 8 * i); g.scale(1d - (1d / 32 * i), 1d - (1d / 32 * i)); g.transform(AffineTransform.getTranslateInstance(64, 64)); for(int j = 0;j < 16;j++){ Color c = new Color(i * 8,0,255 - i * 8); g.setColor(c); g.draw(elps); g.rotate(Math.PI / 8); } } g.setTransform(defaultAT); }

回転して表示される円が縮小しながらスパイラルを描いていきます。これを、座標変換を使わずに描くとなると、少々面倒くさそうです。こうした「決まった手順に沿って位置や大きさ、向きなどを変更しながら描画していく」という方式では、座標変換は威力を発揮するのです。
