Java2D と JavaFX でリフレクション効果

今、読んでいる Filthy Rich Client の中から、すごいなあと感じたテクニックの一つ、リフレクション効果について。
リフレクションと言っても、java.lang.Class クラスや java.lang.reflect パッケージの API の話ではありません。
鏡や水面に物体が映るリフレクションです。

以下のプログラムは Java の Java2D API で、Swing の JPanel にイメージとそのリフレクションを描画します。

ReflectionDemoJava2D.java

package fooami.effect;

import java.awt.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;

public class ReflectionDemoJava2D extends JFrame {
    public ReflectionDemoJava2D() {
        super("Reflection Effect in Java");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        add(new ReflectionPanel());
        setSize(300, 400);
        setLocationRelativeTo(null);
    }

    public static void main(String... args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new ReflectionDemoJava2D().setVisible(true);
            }
        });
    }
}

class ReflectionPanel extends JPanel {
    private BufferedImage image;

    public ReflectionPanel() {
        try {
            image = ImageIO.read(getClass().getResource("snowboarders.jpg"));
            image = createReflection(image);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        setOpaque(false);
    }

    private BufferedImage createReflection(BufferedImage image) {
        int w = image.getWidth();
        int h = image.getHeight();

        BufferedImage result = new BufferedImage(w, h*2, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = result.createGraphics();

         // まずは普通にイメージを描画
        g2.drawImage(image, 0, 0, null);

        // 下に映るイメージを描画
        g2.scale(1.0, -1.0);
        g2.drawImage(image, 0, -h - h, null);
        g2.scale(1.0, -1.0);

        g2.translate(0, h);

        // アルファ(グラデーション)処理
        Color startColor = new Color(1.0f, 1.0f, 1.0f, 0.5f);
        Color endColor = new Color(1.0f, 1.0f, 1.0f, 0.0f);
        GradientPaint mask;
        mask = new GradientPaint(0, 0, startColor, 0, h*0.75f, endColor);
        Paint oldPaint = g2.getPaint();
        g2.setPaint(mask);
        g2.setComposite(AlphaComposite.DstIn);
        g2.fillRect(0, 0, w, h);

        g2.setPaint(oldPaint);
        g2.dispose();
        return result;
    }

    @Override
    protected void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;

        g2.translate(5, 5);
        g2.drawImage(image, 0, 0, null);
        g2.translate(-5, -5);
    }
}

このプログラムを動かすと、通常の写真と、その下に反射して映っているような効果を作成できます。
上でやっている主要部分は createReflection() です。以下のことをしています。

  1. タイプ TYPE_INT_ARGB で、元のイメージの倍の高さの BufferedImage を作成する
  2. 作成した BufferedImage の Graphics2D オブジェクトを作成する
  3. 最初は普通に元のイメージを描画する
  4. Graphics2D の scale() の2番目の引数に -1.0 を設定して座標を上下反対にする
  5. リフレクションとなるイメージを描画する。
  6. scale() を元に戻した後 translate() で座標の原点を移動する
  7. GradientPaint でリフレクション部分のグラデーションを作成する
  8. リフレクションを AlphaComposite.DstIn で合成する
  9. 合成された矩形で塗りつぶす

実行結果です。

格好良いですねー。
因みにリフレクションのグラデーションは上から 0.5 の半透明で始まり、下がるにつれて透明になってゆき、3/4下がると完全な透明になります。


では、これと同じことを JavaFX でやってみます。
以下は JavaFX 版のソースです。

package fooami.effect;

import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.scene.*;
import javafx.stage.*;

Stage {
    title: "Reflection Effect in JavaFX"
    width: 300
    height: 400
    scene: Scene {
        content: [
            ImageView {
                translateX: 5
                translateY: 5
                image: Image {
                    url: "{__DIR__}snowboarders.jpg"
                }
                effect: Reflection{}
            }
        ]
    }
}

プログラムはこれだけです。
主要部分は、ImageView の effect に Reflection を設定しています。
Reflection は見てわかるように空で、デフォルトのまま設定しています。必要な場合は何かしら設定値を明示的に設定しても良いです。

実行結果です。

めちゃくちゃ簡単ですねー。これで同じ効果です。


さすが JavaFX はリッチなクライアントアプリケーションを作成するためのドメイン特化言語として開発されただけのことはありますね。
対して最初の例はそれなりにコード量は多いですが、汎用言語である Java の性格であるため仕方ないです。JavaFX の礎となっている Java2D は奥が深く様々なことが出来ます。