Javaで非矩形・半透明ウィンドウ(Swing版)(その2)

先日作成したSwingによる非矩形ウィンドウはJWindowのみ可能で、JFrameでは対応できなかった。

その後、いろいろと調べて試行錯誤を繰り返してみた結果、JFrameでも同様に非矩形ウィンドウを作成することができた。以下、そのソースコード。

先日作成したソースコードを流用して、またいじくりまわしているので、汚いのはご容赦のほど。

package swing;

import java.awt.Component;
import java.awt.Container;
//import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.GeneralPath;
//import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;

import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
//import javax.swing.JPanel;
import javax.swing.JPopupMenu;
//import javax.swing.JWindow;
//import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

import com.sun.awt.AWTUtilities;

public class TransSwingWindow extends JFrame implements ActionListener {
    /**
     *
     */
    private static final long serialVersionUID = 1L;

    public static final String TITLE    = "Swing版 半透明ウィンドウテスト";
    public static final String VERSION    = "Ver0.01";
    public static final int DEFAULT_SIZE = 64;

    Window window = null;
    Shape shape = null;
    Image image = null;
    private Point start = null;
    private Point point = null;
    private JPopupMenu popupMenu = null;

    public static void main(String[] args) {
//        new TransSwingWindow();

//        SwingUtilities.invokeLater(new Runnable(){
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
//                javax.swing.JFrame.setDefaultLookAndFeelDecorated(true);
                new TransSwingWindow();
            }
        });
    }

    public TransSwingWindow() {
        super();
        getRootPane().setDoubleBuffered(true);
        ((JComponent)getContentPane()).setDoubleBuffered(true);

        //UIをWindowsに設定
        //右クリックメニューで使用
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
        } catch (ClassNotFoundException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        } catch (UnsupportedLookAndFeelException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        }

        window = this;

        //イメージファイルの読み込み
        ImageIcon icon = null;
        int width = DEFAULT_SIZE;
        int height = DEFAULT_SIZE;
        try {
            icon = new ImageIcon(getClass().getClassLoader().getResource("suiseiseki.png"));

            shape = getImageShape(icon);

            image = icon.getImage();
            width = Math.max(icon.getIconWidth(), DEFAULT_SIZE);
            height = Math.max(icon.getIconHeight(), DEFAULT_SIZE);
        } catch (Exception e) {
            System.out.println("ImageLoadError");
        }

        addPopupMenu();
        addMouseEvent();

        if (window instanceof javax.swing.JFrame) {
            ((JFrame)window).setTitle(TITLE + " " + VERSION);
        }
        this.setSize(width, height);

        if (window instanceof javax.swing.JFrame) {
            ((JFrame)window).setUndecorated(true);
        }

        Container contentPane = getContentPane();

        //JWindowに非矩形ウィンドウの元となる画像を描画させると
        //右クリックでJPopupMenuを起動した時に表示ががおかしくなるので、
        //JPanelに画像を描画させる
//        Canvas panel = new Canvas(image);
//        contentPane.add(panel);

        //今回はJWindowに画像を描画させないで、JLabelを使用してみた
        JLabel label = new JLabel(icon);
        contentPane.add(label);

//        pack();

        if (window instanceof javax.swing.JFrame) {
            ((JFrame)window).setResizable(false);
            ((JFrame)window).setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        }

//        this.setLocationRelativeTo(null);
        this.setLocation(100, 100);

//        AWTUtilities.setWindowShape(this, new RoundRectangle2D.Double(0, 0, getWidth(), getHeight(), 100, 100));
        AWTUtilities.setWindowShape(this, shape);
        AWTUtilities.setWindowOpacity(this, (Float)0.70f);

        this.setVisible(true);
    }

    private void addPopupMenu() {
        popupMenu = new JPopupMenu();
        JMenuItem menuItem = new JMenuItem();
        menuItem.setText("終了" );
        menuItem.addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                System.exit(0);
            }
        });
        popupMenu.add(menuItem);
    }

    private void addMouseEvent() {
        this.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                Component com = (Component)e.getComponent();

                switch (e.getButton()) {
                case MouseEvent.BUTTON1:
                    popupMenu.setVisible(false);
                    break;
                case MouseEvent.BUTTON3:
                    popupMenu.show(com, e.getX(), e.getY());
                    break;
                default:
                    popupMenu.setVisible(false);
                    break;
                }
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                // TODO 自動生成されたメソッド・スタブ
                Component c = (Component) e.getComponent();
                point = c.getLocation();
                c.setLocation(point);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                // TODO 自動生成されたメソッド・スタブ
                start = e.getPoint();
                point = e.getPoint();

//                if (e.getClickCount() >= 2) {
//                    System.exit(0);
//                }
            }
        });

        this.addMouseMotionListener(new MouseMotionListener() {

            @Override
            public void mouseMoved(MouseEvent e) {
                // TODO 自動生成されたメソッド・スタブ

            }

            @Override
            public void mouseDragged(MouseEvent e) {
                // TODO 自動生成されたメソッド・スタブ
                Component c = (Component) e.getComponent();
                point = c.getLocation(point);
                int x = point.x - start.x + e.getX();
                int y = point.y - start.y + e.getY();
                c.setLocation(x, y);
            }
        });
    }

    /**
     * ImageIconからアルファ値が0でない画素からなるShapeを得る
     * */
    public Shape getImageShape(ImageIcon icon){
        GeneralPath shape = new GeneralPath();

        final BufferedImage bi = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
        icon.paintIcon(null, bi.createGraphics(), 0, 0);

        ColorModel cm = bi.getColorModel();

        //元(上)のループが縦方向にスキャンしていたので、横方向にスキャンするように変更してみた
        //いまどきのOS/PCではグラフィック処理にラスターを意識しなくても良い?
        for (int y = 0; y < bi.getHeight(); y++) {
            for (int x = 0; x < bi.getWidth(); x++) {

                //完全に透過していなければshapeに追加
                if (cm.getAlpha(bi.getRGB(x, y)) > 0) {
                    int start = x;
                    int width = 0;
                    while (x < bi.getWidth() && cm.getAlpha(bi.getRGB(x++, y)) > 0) {    //forをwhileに変更
                        width++;
                    }

                    shape.append(new Rectangle(start, y, width, 1), true);
                }
            }
        }

        return shape;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // TODO 自動生成されたメソッド・スタブ
    }

//    private class Canvas extends JPanel {
//        /**
//         *
//         */
//        private static final long serialVersionUID = 1L;
//
//
//        private Image image = null;
//
//
//        public Canvas(Image image) {
//            super();
//            this.image = image;
//        }
//
//
//        public void paintComponent(Graphics g) {
//            if (image == null) {
//                return;
//            }
//
//            g.drawImage(image, 0, 0, this);
//        }
//    }
}

今回は、先日のものをJFrameに対応させた上で半透明処理を施してみた。

ソースコード中でウィンドウを非矩形、半透明にしている部分は、以下の部分。

        AWTUtilities.setWindowShape(this, shape);
        AWTUtilities.setWindowOpacity(this, (Float)0.70f);

実際に動作している様子を動画にしたものがこちら。動画の中で実行しているjarファイルはこれ

動画サイズを抑えた結果、画質がやや荒くなってしまっているが、翠星石の形状にウィンドウが作られて、かつ半透明になっているのが確認できると思う。

ただ1点問題があって、ポップアップメニューなどの表示も非矩形・半透明の設定の影響を受けてしまい、表示が欠けてしまう(下図参照)。

この点については、私の実装方法に問題があるのか、JFrameで非矩形ウィンドウを実装した際に起きる不具合なのかは不明。

とりあえず、SwingでJWindowだけでなくJFrameでも半透明+非矩形ウィンドウが実装できたので、よしとしよう。