プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

Java7で透明ウィンドウ、非矩形ウィンドウの作り方が変わってた。

驚きですね。なんと、

window.setBackground(new Color(0,0,0,0));

で背景が透明なウィンドウが作れるようになってました。jre1.6ならcom.sun.awt.AWTUtilitiesのsetWindowOpaqueでなんかなぁ、的な感じで透過していたのに。

そのほか、ウィンドウ全体の不透明度を設定するWindow.setOpacity(float)ウィンドウの形を変更できるWindow.setShape(Shape)が使えるようになっています。


Java7でSwingの改善と言えばJLayerぐらい、というイメージですが、こんな微妙だけど、使いにくかった場所が整備されてるんですね。



あと、カラーチューザーに待望のHSV形式が追加されたましたよ。細けぇ。



背景透明化の実行結果例とコードの例です。(結果画像は実行環境を変えただけで、ソースは弄っていません)

import java.awt.Color;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class TransparentFrame extends JFrame{

	public static void main(final String[] args) {
		//OSのウィンドウ装飾を無くして、Look&Feelの装飾にしておきます。
		JFrame.setDefaultLookAndFeelDecorated(true);

		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				TransparentFrame t = new TransparentFrame(args[0]);
				t.setVisible(true);
			}
		});
	}

	public TransparentFrame(String title) {
		super(title);

		//背景色を透明にします。
		//ウィンドウ装飾を無くしておかないとjre1.7からはエラーが発生します。
		setBackground(new Color(0,0,0,0));

		//jre1.7からisWindowTranslucencySupportedが追加されました。
		//以下の様にして透過に対応しているかどうか調べることができます。
//		GraphicsEnvironment ge=GraphicsEnvironment.getLocalGraphicsEnvironment();
//		GraphicsDevice gd = ge.getDefaultScreenDevice();
//		boolean supported = gd.isWindowTranslucencySupported(WindowTranslucency.PERPIXEL_TRANSLUCENT);

		setSize(400, 400);
		setDefaultCloseOperation(3);
	}

}




そのほかの例はHow to Create Translucent and Shaped Windowsを参考にしてみてください。



さて、中身はいったいどうなっているのだろう?
というわけで、実際に背景透過について覗いてみた。

まずはFrame.javaのsetBackground

public void setBackground(Color bgColor) {
    synchronized (getTreeLock()) {
        if ((bgColor != null) && (bgColor.getAlpha() < 255) && !isUndecorated()) {
            throw new IllegalComponentStateException("The frame is decorated");
        }
        super.setBackground(bgColor);
    }
}

ウィンドウ装飾がなされているかどうかの判定が追加されていますね。



お次にWindow.javaのsetBackgroundです。背景透過と無関係で不要な部分や些細な部分はカット。

public void setBackground(Color bgColor) {
    super.setBackground(bgColor);
    int alpha = bgColor.getAlpha();

    //Fullウィンドウの場合エラーを出す処理(略)
    //透過ウィンドウに対応していない場合エラーを出す処理(略)

    //コンテナーやルートパネルを透明に設定する。
    setLayersOpaque(this, false);

    WindowPeer peer = (WindowPeer)getPeer();
    if (peer != null) {
        peer.setOpaque(false);
    }
}

どうも、com.sun.awt.AWTUtilitiesではなく、WindowPeerが立役者のようです。が、JavaDocにThe peer interfaces are intended only for use in porting the AWT.としか述べてくれてないので細かいことはJDKのソースコードを読みでもしない限りは、名前から何となく想像するしかなさそうです。(すみません、そこまでする気はないです………)



じゃぁ、最後に描画はどうなってるのだろう?Window.paintを覗いてみました。

public void paint(Graphics g) {
    if (!isOpaque()) {
        Graphics gg = g.create();
        try {
            if (gg instanceof Graphics2D) {
                gg.setColor(getBackground());
                ((Graphics2D)gg).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC));
                gg.fillRect(0, 0, getWidth(), getHeight());
            }
        } finally {
            gg.dispose();
        }
    }
    super.paint(g);
}

へ〜。普通にAlphaComposite使ってるだけなんだ。



とくに落ちもまとめもないですが、これで終わります。