Go to Contents Go to Java Page

実験室

 
 

Java 2D AffineTransform (2)

 
 

ところで AffineTransform とは何なのか

 
 

今まで、AffineTransform クラスを使ってきましたが、さて AffineTransform とは何なのでしょうか。

ここから少し数学の話になるので、いやな人は次の節に飛んでいただいて結構です。

結論から先にいえば、AffineTransform クラスはアフィン変換を行うクラスです。ところでアフィン変換って何でしょう。アフィン変換は平行移動、拡大・縮小、回転、シャーリングを行う変換です。ということは、今までプログラム作っているから分かっているって。そりゃ、ごもっとも。

これらの変換を数式であらわすとどうなるでしょう。(x, y) の点が変換されて (x', y') になるとします。詳しくは高校の数学の教科書を引っ張り出してもらうか、Computer Graphics の本などを読んでもらうとして、ここでは簡単に説明を加えておきます。

  1. (tx, ty) だけ平行移動する場合

    x 座標に txy 座標に ty を加えます。

    x' = x +tx, y' = y +ty

    これを行列であらわして見ましょう。途中経過ははしょって、結果だけ表すと次のようになります。

    平行移動

  2. x 軸方向に sx, y 軸方向に sy だけ拡大(縮小) する場合

    x 座標を sx を掛け、同様に y 座標に sy を掛けます。

    x' =sxx, y' = syy

    これも行列であらわして見ます。これは簡単ですね。

    スケーリング

  3. 原点を中心にθだけ回転する場合

    回転行列を知っている方は話が早いのですが...。三角関数の加法定理を使うことで次の式が導くことができます。

    x' =x cosθ-y sinθ, y' = x sinθ+y cosθ

    行列にすると

    平行移動

    となります。

  4. シャーリングの場合

    y 座標に shx 乗じて x 座標に加え、同様に x 座標に shy 乗じて y 座標に加えます。シャーリングはなじみがないと思うので、こういうものだと思ってください。

    x' =x +shxy, y' = y +shyx

    行列であらわすと次のようになります。

    平行移動

このようにそれぞれの変換は 3×3 の行列で表すことができます。それも一番下の行は常に 0, 0, 1 なので、実質的には上の 2 つの行の 6 個の要素だけで表せます。この行列を使用した座標変換をアフィン変換とよびます。式で書くと

アフィン変換

となります。

AffineTransform オブジェクトを生成するには static なメソッドを使っていましたが、この行列の 6 個の要素を指定して生成することもできます。また、後から行列をセットすることも可能です。

AffineTransform コンストラクタ
    AffineTransform(double m00, double m10,
                    double m01, double m11,
                    double m02, duuble m12)

 
    AffineTransform(float m00, float m10,
                    float m01, float m11,
                    float m02, float m12)
 
setTransform メソッド
    setTransform(double m00, double m10,
                 double m01, double m11,
                 double m02, duuble m12)

この 3 種類の方法を用いて同じ変換を行ってみました。どれも同じ結果になることが分かると思います。

Applet の HTML   RotateTest1.html
Applet のソース getRotateInstance メソッドを使用したもの RotateTest1.java
コンストラクタで直接行列を指定したもの RotateTest2.java
setToRotation を使用したもの RotateTest3.java
setTransform を使用したもの RotateTest4.java

AffineTransform オブジェクトを生成する部分を抜き出してみると次のようになります。

RotateTest1.java

    rotate = AffineTransform.getRotateInstance(Math.toRadians(30));

RotateTest2.java

    rotate = new AffineTransform(Math.cos(Math.toRadians(30)), Math.sin(Math.toRadians(30)),
                                -Math.sin(Math.toRadians(30)), Math.cos(Math.toRadians(30)),
                                 0.0,                          0.0);

RotateTest3.java

    rotate = new AffineTransform();
    rotate.setToRotation(Math.toRadians(30));

RotateTest4.java

    rotate.setTransform(Math.cos(Math.toRadians(30)), Math.sin(Math.toRadians(30)),
                       -Math.sin(Math.toRadians(30)), Math.cos(Math.toRadians(30)),
                        0.0,                          0.0);

 
 

実験 1 変換の組み合わせ

 
 

まずは単純に AffineTransform クラスで座標系の変換をしてみましょう。ここでは座標軸を回転させてみます。

Applet の HTML AffineTransformTest2.html
Applet のソース AffineTransformTest2.java

AffineTransorm クラスは普通にコンストラクタを使用して生成することもできますが、生成のための static メソッドも用意されています。今回はそれを使ってみました。AffintTransform オブジェクトも四角形と同じように init メソッドで生成します。

    private Shape rect1;
    private Shape rect2;
    private AffineTransform rotate;
 
    public void init(){
        rect1 = new Rectangle2D.Double(0, 0, 500, 400);
        rect2 = new Rectangle2D.Double(200, 100, 200, 150);
        rotate = AffineTransform.getRotateInstance(Math.toRadians(45), 0.0, 0.0);
    }

この AffineTransform オブジェクトは (0, 0) を中心にして 45°の回転を行うものです。このほかにも次のような生成のためのメソッドがあります。

(0, 0) を中心にした回転 getRotateInstance(double theta)
任意の点を中心にした回転 getRotateInstance(double theta, double x, double, y)
拡大、縮小 getScaleInstance(double sx, double sy)
シャーリング (傾き) getShearInstance(double shx, double shy)
平行移動 getTranslateInstance(double tx, double ty)

それでは描画してみましょう。座標変換を行うには Graphics2D#setTransform メソッドを使用します。

    public void paint(Graphics g){
        Graphics2D g2D = (Graphics2D)g;
 
        g2D.setTransform(rotate);
 
        g2D.draw(rect1);
        g2D.draw(rect2);
    }

実行させるとどうなりましたか。長方形が回転して描画されているのが確認できたでしょうか。

ユーザ空間を回転させるということがどういうことかを図 2 で示してみました。

ユーザ空間の回転
図 2 ユーザ空間の回転

デバイス空間というのぞき窓の位置は変わらないため、ユーザ空間とデバイス空間が回転によってずれてしまうわけです。

 

 
 

実験 2 複数のユーザ空間

 
 

次の実験は、複数のユーザ空間を設定できるかどうかについてです。実験 1 と同様に回転変換で試してみます。

Applet の HTML AffineTransformTest3.html
Applet のソース AffineTransformTest3.java

長方形 rect1 オブジェクトはデフォルトのユーザ空間、rect2 が回転変換をほどこされるユーザ空間に描画します。

    public void paint(Graphics g){
        Graphics2D g2D = (Graphics2D)g;
 
        g2D.draw(rect1);              // デフォルトのユーザ空間 (=デバイス空間)
 
        g2D.setTransform(rotate);     // ユーザ空間に回転をほどこす

        g2D.draw(rect2);
    }

これを実行すると、rect1 はそのまま描画され、rect2 は回転した形で描画されます。

ということは、次のようなことがいえます。

  • 常に AffineTransform はいつでもユーザ空間に対して変換を行うことができる
  • AffineTransform をほどこす前のユーザ空間に描画されたものは、AffineTransform によって変換されない

 

 
 

実験 3 ユーザ空間にほどこされた変換をもとにもどす

 
 

座標変換されてしまったユーザ空間は元に戻せるかやってみましょう。

Applet の HTML AffineTransformTest4.html
Applet のソース AffineTransformTest4.java

ユーザ空間を元に戻すには、g2D オブジェクトにセットされた AffineTransform オブジェクトをチャラにしてしまえばいいと考えられます。そこで、ためしに setTransform メソッドの引数に null を入れてみましょう。

    public void paint(Graphics g){
        Graphics2D g2D = (Graphics2D)g;
 
        g2D.setTransform(rotate);     // ユーザ空間に回転をほどこす
        g2D.draw(rect1);              // デフォルトのユーザ空間 (=デバイス空間)
 
        g2D.setTransform(null);     // ユーザ空間に元に戻す

        g2D.draw(rect2);
    }

これを実行すると、g2D.setTransform(null) のところで NullPointerException が発生してしまいました。

null がだめだとすると、なにもしない AffineTransform オブジェクトを引数にしてみましょう。

    public void paint(Graphics g){
        Graphics2D g2D = (Graphics2D)g;
 
        g2D.setTransform(rotate);     // ユーザ空間に回転をほどこす
        g2D.draw(rect1);              // デフォルトのユーザ空間 (=デバイス空間)
 
        g2D.setTransform(new AffineTransform());     // ユーザ空間に元に戻す

        g2D.draw(rect2);
    }

引数がないコンストラクタを使用して、AffineTransform オブジェクトを生成すると、なにも変換しないオブジェクトが生成されます。これを setTransform の引数にしてみました。

実行してみると、期待通りユーザ空間は元に戻りました。

この実験からは、次のようなことがわかりました。

  • 変換前のユーザ空間に戻すには変換前の AffineTransform オブジェクトを setTransform メソッドで指定する
  • setTransform メソッドには null を入れることはできない

 

 
 

実験 4 AffineTransform を使わない座標変換

 
 

Graphics2D の API を見ていると、下に示したようなメソッドがあります。これらのメソッドを用いても座標変換ができそうです。

rotate(double theta) (0, 0) を中心に theta 回転する
rotate(double theta, double x, double y) (x, y) を中心に theta 回転する
scale(double sx, double sy) x 軸方向に sx, y 軸方向に sy だけ拡大 (縮小) する
shear(double shx, double shy) シャーリング (傾き)
translate(double tx, double ty) x 軸方向に tx, y 軸方向に ty だけ平行移動する
translate(int x, int y) ユーザ空間の原点を (x, y) に平行移動する

さっそく、試してみましょう。

Applet の HTML AffineTransformTest5.html
Applet のソース AffineTransformTest5.java

これまで、AffineTransform オブジェクトを生成していましたが、直接 Graphics2D オブジェクトに対して回転変換をセットします。

    public void paint(Graphics g){
        Graphics2D g2D = (Graphics2D)g;
 
        g2D.draw(rect);
 
        g2D.rotate(Math.toRadians(45.0));
        g2D.setPaint(Color.red);
        g2D.draw(rect);
    }

この実験からは、次のようなことがわかりました。

  • AffineTransform を直接使用することなく、Graphics2D のメソッドによって座標変換は可能

 

 
 
Go to Contents Go to Java Page