JavaFXグラフィック特殊効果

JavaFXにはグラフィック作業用の機能がいろいろ用意されている。表示位置や方向を変換したり、図形を透過させたり、ぼかしや影の効果を適用したりする特殊効果について整理して説明する。

トランスフォーメーション

シェイプの基本形状はとても単純である。たとえば四角形なら、横線、縦線、水平、垂直の線だけである。「少し傾いた四角形」のようなコンポーネントはない。では、そのようなものはどう作ればよいだろうか。

そのような場合はトランスフォーメーションを利用する。これはシェイプの表示を移動、回転、拡大、縮小する機能である。シェイプのスーパークラスにあたるNodeクラスには、このためのメソッドが次のように用意されている。

平行移動

"Node".setTranslateX(移動量);
"Node".setTranslateY(移動量);
"Node".setTranslateZ(移動量);

回転

"Node".setRotate(回転角度);

拡大縮小

"Node".setScaleX(拡大率);
"Node".setScaleY(拡大率);
"Node".setScaleY(拡大率);

平行移動は、X、Y、Z軸のそれぞれの方向に移動することである。Z軸まであるのは、JavaFXには3Dグラフィック機能も含まれているためである。

回転では回転角度を指定する。逆方向に回転したい場合は、マイナスの角度を指定すればよい。

拡大縮小も同じくX、Y、Z軸の各方向に用意されている。これは拡大率なので、1.0より大きければ拡大し、小さければ縮小する。

簡単な使用例は次のとおりである。前回作成した図形生成メソッドcreateShapeを書き換える形で作成している。

public void createShape(Pane root){
    for(int i = 0;i < 100;i++){
        Rectangle r = new Rectangle(10, 10, 25, 25);
        r.setFill(Color.YELLOW);
        r.setStroke(Color.GREEN);
        r.setStrokeWidth(3);
        r.setRotate(10 * i);
        r.setTranslateX(5 * i);
        r.setTranslateY(2.5 * i);
        r.setScaleX(1 + 0.05 * i);
        root.getChildren().add(r);
    }
}

実行すると、四角形が少しずつ変化しながら描画されることがわかる。

Rectangleを作成し、平行移動、回転、拡大を行うことで図形の表示を少しずつずらしている。new Rectangleで作った図形の位置とサイズはすべて同じだが、表示は少しずつ変化していく。

図形の透過

これまでの図形は、すべて内部が決められた色で塗りつぶされていた。図形を重ねると、当然下にある図形は隠れて見えなくなる。では、図形を半透明にして下のものが透けて見えるように表示したい場合はどうすればよいだろうか。

そのような場合はsetOpacityメソッドを使う。これは図形の透過率を設定するメソッドで、次のように使用する。

"Node".setOpacity(透過率);

引数には0から1までの実数、つまりdoubleを指定する。0なら透明、1なら不透明である。

これで図形を半透明にすることは簡単にできる。ただし、実際に試すとわかるが、この方法では「輪郭線だけを描き、図形の内部だけを半透明にする」ことはできない。図形全体を同じように透過するため、内部だけでなく輪郭線も同じように透明になる。

図形の内部だけを透過したい場合は、setFillを使えばよい。ここでColor.TRANSPARENTを指定すると、内部が透明で輪郭線だけの図形が描画される。

次は使用例のソースコードである。

public void createShape(Pane root){
    for(int i = 0;i < 20;i++){
        Rectangle r = new Rectangle(10, 10, 50, 50);
        r.setFill(Color.BLUE);
        r.setStroke(Color.WHITE);
        r.setTranslateX(20 * i);
        r.setTranslateY(10 * i);
        r.setOpacity(1 - 0.05 * i);
        root.getChildren().add(r);
        Rectangle r2 = new Rectangle(10, 10, 50, 50);
        r2.setStroke(Color.RED);
        r2.setFill(Color.TRANSPARENT);
        r2.setTranslateX(20 * i);
        r2.setTranslateY(10 * i);
        root.getChildren().add(r2);
    }
}

実行すると、四角形が横に並んで表示される。輪郭線だけは赤で表示され、内部は少しずつ透明になる。

ここでは、setOpacityで全体を透過した図形と、setFill(Color.TRANSPARENT)で内部を透過した図形を重ねることで、「輪郭線はそのまま残し、内部だけを少しずつ透過する形」を表現している。

ぼかし効果

シェイプには視覚効果のための機能も含まれている。視覚効果というとわかりにくいかもしれないが、たとえば「ぼかし」などが代表的である。

視覚効果は、シェイプ、つまりNodeのサブクラスに含まれているsetEffectメソッドを使って設定する。これは次のように実行する。

"Node".setEffect("Effect");

引数には、視覚効果の内容を表すEffectクラス、またはそのサブクラスのインスタンスを指定する。視覚効果は多数用意されており、すべてEffectクラスのサブクラスとして使用できる。

ぼかし関連の視覚効果クラスには次のようなものがある。コンストラクタは、視覚効果を表現するために必要な設定値を引数に取る。

Gaussian Blur

new GaussianBlur(半径)

Gaussian Blurはガウス曲線を利用してぼかし効果を出すものである。広い範囲にぼかしを滑らかに適用したいときに使われる。引数にはぼかし半径となる実数を指定する。

Motion Blur

new MotionBlur(角度, 半径)

Motion Blurは高速に移動する様子を撮影したようなぼかし効果を出すものである。引数には、ぼかし方向を表す角度と、ぼかしの強さを表す半径をそれぞれ実数で指定する。

Box Blur

new BoxBlur(, 高さ, 繰り返し)

Box Blurは隣接ピクセルの平均値をもとにぼかし効果を出すものである。引数にはボックスの横幅と高さ、視覚効果の繰り返し回数を指定する。幅と高さはdouble値、繰り返し回数はint値である。

簡単な使用例は次のとおりである。

public void createShape(Pane root){
    for(int i = 0;i < 10;i++){
        Rectangle r = new Rectangle(20, 20, 50, 50);
        r.setTranslateX(25 * i);
        r.setTranslateY(15 * i);
        r.setFill(Color.BLUE);
        r.setStroke(Color.RED);
        r.setStrokeWidth(5);
        r.setEffect(new GaussianBlur(2.0 * i));
        root.getChildren().add(r);
    }
}

ソースコードを実行すると、Gaussian Blurによるぼかし効果が少しずつ強くなりながら四角形が描画される。

影効果と反射

ぼかし効果とともによく使われる視覚効果といえば「影」効果である。影にはいくつかの種類がある。ここで簡単に整理しておく。

ドロップシャドウ

new DropShadow(半径, 横オフセット, 縦オフセット, );

図形の影がその下に落ちる効果を「ドロップシャドウ」と呼ぶ。これはDropShadowクラスとして提供される。コンストラクタはいくつかあるが、よく使われるのは、影が適用される半径、影の横方向と縦方向のずれ幅、影の色を引数に指定する形である。

内側のシャドウ

new InnerShadow(半径, 横オフセット, 縦オフセット, );

図形の内部がへこんだように内側へ影を描くものである。これもDropShadowと同じく、引数には半径、横縦のオフセット、影の色を指定する。

反射

new Reflection(上部オフセット, フラクション, 上部透過幅, 下部透過幅);

これは影とは少し異なるが、影のように光の効果として表現する。リフレクションは、氷や水の上に図形があるように、その下に反転した画像を表示するものである。引数には、元の図形と反射図形との間隔、反射図形が描かれる割合、図形の上部と下部を透過させるための設定などを指定する。

実際の使用例は次のとおりである。

public void createShape(Pane root){
    Rectangle r = new Rectangle(20, 20, 100, 50);
    r.setFill(Color.BLUE);
    r.setStroke(Color.RED);
    r.setStrokeWidth(5);
    r.setEffect(new DropShadow(20.0, 10.0, 5.0, Color.BLACK));
    root.getChildren().add(r);
     
    Ellipse e = new Ellipse(220, 50, 70, 30);
    e.setFill(Color.YELLOW);
    e.setStroke(Color.GREEN);
    e.setStrokeWidth(5);
    e.setEffect(new Reflection(5.0, 2.0, 0.5, 0));
    root.getChildren().add(e);
     
    Text t = new Text(50, 250, "Hello!");
    t.setFont(new Font(80));
    t.setFill(Color.YELLOW);
    t.setStroke(Color.GREEN);
    t.setStrokeWidth(1);
    t.setEffect(new InnerShadow(2.0, 2.0, 1.0, Color.BLACK));
    root.getChildren().add(t);
}

DropShadowInnerShadowはそれほど難しくないだろう。Reflectionは反射図形の表示幅や上下のかすれ具合などを設定できるため、それぞれの引数の役割がわかると面白い効果を得られる。

効果チェーンで複数の視覚効果を適用する

さまざまな視覚効果を紹介したが、これらはすべてNodesetEffectで設定していた。つまり、同時に複数のEffectを直接設定することはできない。

しかし実際には、複数の視覚効果を同時に使いたい場合がある。そのような場合はどうすればよいだろうか。

これは少し発想を変える必要がある。setEffectで設定できるEffectは1つだけであり、これは変えられない。注目すべきなのは適用するNodeではなく、視覚効果となるEffectクラスである。

Effectクラスは、入力された画像に効果を適用して出力する役割を持つ。つまり「入力画像」を変換して「出力画像」を生成する。したがって、ある視覚効果の出力を別の視覚効果の入力に渡せれば、複数の視覚効果を利用できる。

つまり、「視覚効果A」→「視覚効果B」→「視覚効果C」……のように順番に視覚効果を適用していき、最終結果をsetEffectで設定する。これを「効果チェーン」と呼ぶ。この効果チェーンはsetInputメソッドを使って設定できる。

"Effect".setInput("Effect");

setInputは視覚効果であるEffectクラスのメソッドである。引数には別のEffectインスタンスを指定する。つまり、引数に設定したEffectの結果をもとに、さらに視覚効果を設定できる。

実際の使用例は次のとおりである。

public void createShape(Pane root){
    InnerShadow is = new InnerShadow(10.0, 2.0, 2.0, Color.rgb(0, 100, 100));
    DropShadow ds = new DropShadow(20.0, 10.0, 5.0, Color.BLACK);
    ds.setInput(is);
    Reflection rf = new Reflection(0.0, 2.0, 0.5, 0);
    rf.setInput(ds);
 
    Rectangle r = new Rectangle(20, 20, 100, 50);
    r.setFill(Color.CYAN);
    r.setEffect(rf);
    root.getChildren().add(r);
}

ここではInnerShadowDropShadowReflectionの3つのEffectsetInputでつないで視覚効果を作成している。効果チェーンの使い方がわかれば、いくらでも視覚効果を組み合わせて利用できる。