17 May 2015

(Java) Swingでグラフィックスを使うサンプル

Swingでグラフィックスを用いるサンプル

グラフを描く

実数座標 (x,y) を、画面上のピクセル座標に変換してグラフをプロットする例。 ウインドウを伸縮させると、描画エリアのサイズも変化するので、それに合わせてpaintComponentメソッドが呼ばれて自動的に画面が再描画される。

20150517-swing-graph.jpg

メイン クラスのコード

public class SwingGraphics01 {
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setTitle("Swing Graphicsのテスト");
        frame.setSize(450, 350);
        // 親コンテナ(この中に、子コンテナとしてpanel2つを上下配置する)
        Container container = frame.getContentPane();
        container.setLayout(new BorderLayout());
        frame.setContentPane(container);
        // ダイアログ上部のグラフィックエリアの定義
        GraphicsJPanel01 panel_1 = new GraphicsJPanel01();
        panel_1.SetXyRange(-3.14*2, 3.14*2, -0.8, 1.1);
        container.add(panel_1, BorderLayout.CENTER);
        // ダイアログ下部のボタン エリアの定義
        JPanel panel_2 = new JPanel();
        panel_2.setLayout(new FlowLayout(FlowLayout.RIGHT, 10, 5));
        JButton buttonGraphicDraw = new JButton("sin/cos切替");
        panel_2.add(buttonGraphicDraw);
        JButton buttonClose = new JButton("終了");
        panel_2.add(buttonClose);
        container.add(panel_2, BorderLayout.PAGE_END);
        // 「終了」ボタンの処理
        buttonClose.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                frame.setVisible(false);
                frame.dispose();
            }
        });
        // 「sin, cos 切替」ボタンの処理
        buttonGraphicDraw.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                panel_1.ToggleSinCos();
                // グラフの強制再描画
                panel_1.repaint();
            }
        });
        
        // 実行環境OSにあったUIを適用する
        try {
            // UIManager.setLookAndFeel("com.sun.java.swing.plaf.gtk.GTKLookAndFeel");
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            // SwingUtilities.updateComponentTreeUI(panel);
            SwingUtilities.updateComponentTreeUI(frame);
        } catch (Exception e) {
        }
        
        // メイン ダイアログの表示
        frame.setVisible(true);
    }
}

グラフィックス描画のためのクラス

public class GraphicsJPanel01 extends JPanel {
    
    // sin または cos のグラフ描画の切替用フラグ
    private int mode = 0;
    // 描画領域の x, y 表示範囲
    private double x1 = 0; // x_min
    private double x2 = 1; // x_max
    private double y1 = 0; // y_min
    private double y2 = 1; // y_max
    // 描画領域の上下・左右両端のボーダー幅
    private int x_border = 10;
    private int y_border = 10;
    
    public GraphicsJPanel01() {
        super();
        this.setBackground(Color.WHITE);
    }
    
    @Override
    protected void paintComponent(Graphics g) {
        // TODO 自動生成されたメソッド・スタブ
        super.paintComponent(g);
        DrawAxis(g); // 軸線を描く
        DrawBorder(g); // グラフ外枠線を描く
        g.setColor(Color.BLUE);
        for (double i = -3.14 * 2; i <= 3.14 * 2; i += 0.05) {
            DrawPoint(g, i, this.mode == 0 ? Math.sin(i) : Math.cos(i));
        }
    }
    
    public void ToggleSinCos() {
        if (this.mode == 0)
            this.mode = 1;
        else
            this.mode = 0;
    }
    
    /**
     * 描画領域の x, y 表示範囲を設定する
     * 
     * @param new_x1
     *            : (x min), xの表示範囲最小値
     * @param new_x2
     *            : (x max), xの表示範囲最大値
     * @param new_y1
     *            : (y min), yの表示範囲最小値
     * @param new_y2
     *            : (y max), yの表示範囲最大値
     */
    public void SetXyRange(double new_x1, double new_x2, double new_y1,
            double new_y2) {
        this.x1 = new_x1;
        this.x2 = new_x2;
        this.y1 = new_y1;
        this.y2 = new_y2;
    }
    
    /**
     * 実数 x を、描画領域の横座標(ピクセル)に変換する
     * 
     * @param x
     *            : 実数
     * @return
     */
    private int ConvertX2W(double x) {
        // 描画領域幅から、両端のボーダー幅を引く
        int w = this.getWidth() - (x_border * 2);
        // 実数を描画領域のピクセル座標に変換した値を返す
        return ((int) ((x - x1) / (x2 - x1) * w) + x_border);
    }
    
    /**
     * 実数 y を、描画領域の縦座標(ピクセル)に変換する
     * 
     * @param y
     *            : 実数
     * @return
     */
    private int ConvertY2H(double y) {
        // 描画領域高さから、上下のボーダー幅を引く
        int h = this.getHeight() - (y_border * 2);
        // 実数を描画領域のピクセル座標に変換した値を返す
        return (h - (int) ((y - y1) / (y2 - y1) * h) + y_border);
    }
    
    /**
     * 描画領域の外枠線を描く
     * 
     * @param g
     */
    private void DrawBorder(Graphics g) {
        // 描画領域・高さから、それぞれボーダー幅を引く
        int w = this.getWidth() - (x_border * 2);
        int h = this.getHeight() - (y_border * 2);
        // 描画領域の枠線を描く
        g.setColor(new Color(200, 200, 255));
        g.drawRect(x_border, y_border, w, h);
    }
    
    /**
     * x=0およびy=0の軸線を描く
     * 
     * @param g
     */
    private void DrawAxis(Graphics g) {
        g.setColor(new Color(200, 200, 255));
        // x軸(横軸)を描く
        g.drawLine(this.ConvertX2W(x1), this.ConvertY2H(0),
                this.ConvertX2W(x2), this.ConvertY2H(0));
        // y軸(縦軸)を描く
        g.drawLine(this.ConvertX2W(0), this.ConvertY2H(y1),
                this.ConvertX2W(0), this.ConvertY2H(y2));
    }
    
    /**
     * 指定した実数座標(x,y)を、描画領域に点としてプロットする
     * 
     * @param g
     * @param x
     * @param y
     */
    private void DrawPoint(Graphics g, double x, double y) {
        // 描画領域外のチェック
        if (x < x1 || x2 < x || y < y1 || y2 < y)
            return;
        // 実数座標(x,y)を、画面描画する
        g.drawRect(this.ConvertX2W(x), this.ConvertY2H(y), 1, 1);
    }
}

ビットマップ画像を表示する

ウインドウの伸縮に合わせて、最適な縦横比を保持したスケーリング表示を行う。スケーリングは自動ではなく、ユーザが手動で計算してスケーリング結果の画像サイズをdrawImageで指定してやる必要がある。

20150517-swing-image.jpg

メイン クラスのコード

public class SwingGraphics01 {
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setTitle("Swing Graphicsのテスト");
        frame.setSize(450, 350);
        // 親コンテナ(この中に、子コンテナとしてpanel2つを上下配置する)
        Container container = frame.getContentPane();
        container.setLayout(new BorderLayout());
        frame.setContentPane(container);
        // ダイアログ上部のグラフィックエリアの定義
        GraphicsJPanel02 panel_1 = new GraphicsJPanel02();
        container.add(panel_1, BorderLayout.CENTER);
        // ダイアログ下部のボタン エリアの定義
        JPanel panel_2 = new JPanel();
        panel_2.setLayout(new FlowLayout(FlowLayout.RIGHT, 10, 5));
        JButton buttonGraphicDraw = new JButton("jpeg読込");
        panel_2.add(buttonGraphicDraw);
        JButton buttonClose = new JButton("終了");
        panel_2.add(buttonClose);
        container.add(panel_2, BorderLayout.PAGE_END);
        // 「終了」ボタンの処理
        buttonClose.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                frame.setVisible(false);
                frame.dispose();
            }
        });
        // 「画像読込」ボタンの処理
        buttonGraphicDraw.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JFileChooser dialog = new JFileChooser();
                dialog.setFileFilter(new FileNameExtensionFilter(
                        "pngファイル (*.png)", "png", "PNG"));
                dialog.setFileFilter(new FileNameExtensionFilter(
                        "jpegファイル (*.jpg)", "jpg", "jpeg", "JPG", "JPEG"));
                dialog.setDialogTitle("画像ファイルを開く");
                if (dialog.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) {
                    panel_1.ReadImageFile(dialog.getSelectedFile().getPath());
                    panel_1.repaint();
                }
            }
        });
        
        // 実行環境OSにあったUIを適用する
        try {
            // UIManager.setLookAndFeel("com.sun.java.swing.plaf.gtk.GTKLookAndFeel");
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            // SwingUtilities.updateComponentTreeUI(panel);
            SwingUtilities.updateComponentTreeUI(frame);
        } catch (Exception e) {
        }
        
        // メイン ダイアログの表示
        frame.setVisible(true);
    }
}

ビットマップ描画のためのクラス

public class GraphicsJPanel02 extends JPanel {
    private Image image;
    
    public GraphicsJPanel02() {
        super();
        // ダミーの初期画像
        image = CreateBlankImage();
    }
    
    @Override
    protected void paintComponent(Graphics g) {
        // TODO 自動生成されたメソッド・スタブ
        super.paintComponent(g);
        // 画面縦サイズを固定してスケーリングした幅を算出
        int h = (int) ((double) image.getHeight(null)
                / (double) image.getWidth(null) * this.getWidth());
        // 画面横サイズを固定してスケーリングした高さを算出
        int w = (int) ((double) image.getWidth(null)
                / (double) image.getHeight(null) * this.getHeight());
        // 画面サイズにマッチする、縦横比を維持したスケーリングで画像を表示する
        if (h > this.getHeight())
            g.drawImage(image, 0, 0, w, this.getHeight(), this);
        else
            g.drawImage(image, 0, 0, this.getWidth(), h, this);
    }
    
    public void ReadImageFile(String filename) {
        image.flush();
        // 指定されたファイル(filename)から画像を読み込んで image に格納する
        try {
            ImageIcon imageIcon = new ImageIcon(filename);
            image = imageIcon.getImage();
        } catch (Exception e) {
            // エラーの場合(ファイル不存在や、フォーマット異常では例外発生しないようだ…)
            image.flush();
            image = CreateBlankImage();
        } finally {
            // 読み込んだ画像のサイズが異常(=エラー)の場合、ダミー初期画像に差し替える
            if (image.getWidth(null) <= 0 || image.getHeight(null) <= 0)
                image = CreateBlankImage();
        }
    }
    
    /**
     * ダミー初期画像を生成する
     * 
     * @return Image
     */
    private Image CreateBlankImage() {
        Image imageTemp = new BufferedImage(320, 280,
                BufferedImage.TYPE_INT_RGB);
        Graphics g = imageTemp.getGraphics();
        g.setColor(Color.CYAN);
        g.fillRect(0, 0, imageTemp.getWidth(null), imageTemp.getHeight(null));
        g.dispose();
        return imageTemp;
    }
}

画像ファイルの読み込みに、ImageIconではなくImageIOを使う場合は、該当部分のソースコードを次のように書き換える。

    public void ReadImageFile(String filename) {
        image.flush();
        try{
            image = ImageIO.read(new File(filename));
        } catch (IOException | IllegalArgumentException e) {
            image.flush();
            image = CreateBlankImage();
        } finally {
            // 読み込んだ画像のサイズが異常(=エラー)の場合、ダミー初期画像に差し替える
            if (image == null || image.getWidth(null) <= 0 || image.getHeight(null) <= 0)
                image = CreateBlankImage();
        }
    }