11 April 2015

(Java) SWTで画像表示

JavaのSWTライブラリを用いて画像表示をする方法2種

この記事で説明しているプログラムのEclipseプロジェクトファイル(zipエクスポート)はこちらからダウンロード(20150411-javaimageviewer.zip)

Labelに画像表示

これが、最も単純な方法。画像のリサイズ等は行われず、表示領域より画像が大きい場合は中央付近のみが表示される。

20150411-label-image.jpg

public class ImageViewerTest extends Shell {
    /**
     * @param args
     */
    public static void main(String[] args) {
        try {
            Display display = new Display();
            Shell shell = new ImageViewerTest(display, SWT.SHELL_TRIM);
            shell.open();
            while (!shell.isDisposed()) {
                if (!display.readAndDispatch()) {
                    display.sleep();
                }
            }
            display.dispose();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * @param display
     * @param style
     */
    public ImageViewerTest(Display display, int style) {
        super(display, style);
        Shell shell = this.getShell();
        // レイアウトの定義
        shell.setSize(450, 350);
        this.setText("イメージ ビューワ");
        shell.setLayout(new GridLayout(2, false));
        // レイアウトに配置する要素(ヴィジェット)
        Label label1 = new Label(shell, SWT.NONE);
        label1.setText("Image");
        
        Label labelImage = new Label(shell, SWT.BORDER);
        GridData gridData = new GridData();
        gridData.horizontalAlignment = SWT.FILL;
        gridData.grabExcessHorizontalSpace = true;
        gridData.verticalAlignment = SWT.FILL;
        gridData.grabExcessVerticalSpace = true;
        labelImage.setLayoutData(gridData);
        labelImage.setText("まだ画像が読み込まれていません");
        
        Composite composite = new Composite(shell, SWT.NONE);
        composite.setLayout(new FillLayout(SWT.HORIZONTAL));
        Button btnFileOpen = new Button(composite, SWT.NULL);
        btnFileOpen.setText("ファイルを開く");
        Button btnClose = new Button(composite, SWT.NULL);
        btnClose.setText("閉じる");
        gridData = new GridData(GridData.HORIZONTAL_ALIGN_END);
        gridData.horizontalSpan = 2;
        composite.setLayoutData(gridData);
        
        // 「ファイルを開く」ボタンの処理
        btnFileOpen.addSelectionListener(new SelectionListener() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                // 読み込み用ダイアログを開く
                FileDialog fileDlg = new FileDialog(shell, SWT.OPEN);
                String[] exts = { "*.jpg", "*.*" };
                fileDlg.setFilterExtensions(exts);
                String[] filterNames = { "jpegファイル(*.jpg)", "全てのファイル(*.*)" };
                fileDlg.setFilterNames(filterNames);
                fileDlg.setText("画像ファイルを開く");
                String selectedFilename = fileDlg.open();
                if (selectedFilename != null && !selectedFilename.isEmpty()) {
                    ReadFromImageFile_Label(labelImage, selectedFilename);
                }
            }
            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
            }
        });
        
        // 「閉じる」ボタンの処理
        btnClose.addSelectionListener(new SelectionListener() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                shell.dispose();
            }
            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
            }
        });
    }
    
    /**
     * 画像ファイルを読み込み、Labelヴィジェットに表示する
     * @param label
     * @param filename
     */
    private void ReadFromImageFile_Label(Label label, String filename) {
            try {
                Image image = new Image(this.getDisplay(), filename);
                label.setImage(image);
            } catch (Exception e) {
                e.printStackTrace();
            }
    }
    
    /*
     * (非 Javadoc)
     * 
     * @see org.eclipse.swt.widgets.Decorations#checkSubclass()
     */
    @Override
    protected void checkSubclass() {
        // super.checkSubclass();       // 自動作成後、手動でコメントアウト
    }
}

Canvasに画像表示(リサイズ有り)

Canvasに画像表示する。画像はウインドウの伸縮に合わせ、縦横比率を維持して自動的にリサイズされる。まずは完成形を示す。

20150411-canvas-image.jpg

public class ImageViewerTest extends Shell {
    /**
     * @param args
     */
    public static void main(String[] args) {
        try {
            Display display = new Display();
            Shell shell = new ImageViewerTest(display, SWT.SHELL_TRIM);
            shell.open();
            while (!shell.isDisposed()) {
                if (!display.readAndDispatch()) {
                    display.sleep();
                }
            }
            display.dispose();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * @param display
     * @param style
     */
    public ImageViewerTest(Display display, int style) {
        super(display, style);
        Shell shell = this.getShell();
        // レイアウトの定義
        shell.setSize(450, 350);
        this.setText("イメージ ビューワ");
        shell.setLayout(new GridLayout(2, false));
        // レイアウトに配置する要素(ヴィジェット)
        Label label1 = new Label(shell, SWT.NONE);
        label1.setText("Image");
        
        Canvas canvas = new Canvas(shell, SWT.BORDER);
        GridData gridData = new GridData();
        gridData.horizontalAlignment = SWT.FILL;
        gridData.grabExcessHorizontalSpace = true;
        gridData.verticalAlignment = SWT.FILL;
        gridData.grabExcessVerticalSpace = true;
        canvas.setLayoutData(gridData);
        
        Composite composite = new Composite(shell, SWT.NONE);
        composite.setLayout(new FillLayout(SWT.HORIZONTAL));
        Button btnFileOpen = new Button(composite, SWT.NULL);
        btnFileOpen.setText("ファイルを開く");
        Button btnClose = new Button(composite, SWT.NULL);
        btnClose.setText("閉じる");
        gridData = new GridData(GridData.HORIZONTAL_ALIGN_END);
        gridData.horizontalSpan = 2;
        composite.setLayoutData(gridData);
        
        // 「ファイルを開く」ボタンの処理
        btnFileOpen.addSelectionListener(new SelectionListener() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                // 読み込み用ダイアログを開く
                FileDialog fileDlg = new FileDialog(shell, SWT.OPEN);
                String[] exts = { "*.jpg", "*.*" };
                fileDlg.setFilterExtensions(exts);
                String[] filterNames = { "jpegファイル(*.jpg)", "全てのファイル(*.*)" };
                fileDlg.setFilterNames(filterNames);
                fileDlg.setText("画像ファイルを開く");
                String selectedFilename = fileDlg.open();
                if (selectedFilename != null && !selectedFilename.isEmpty()) {
                    if(canvas.isListening(SWT.Paint)){
                        // 既に PaintListener が存在する場合は、それを削除する
                        canvas.removeListener(SWT.Paint, (TypedListener)canvas.getListeners(SWT.Paint)[0]);
                    }
                    ReadFromImageFile_Canvas(canvas, selectedFilename);
                }
            }
            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
            }
        });
        
        // 「閉じる」ボタンの処理
        btnClose.addSelectionListener(new SelectionListener() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                shell.dispose();
            }
            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
            }
        });
    }
    
    /**
     * 画像ファイルを読み込み、Canvasヴィジェットに表示する
     * @param canvas
     * @param filename
     */
    private void ReadFromImageFile_Canvas(Canvas canvas, String filename) {
        Image image;
        try {
            image = new Image(canvas.getDisplay(), filename);
        } catch (Exception e1) {
            e1.printStackTrace();
            return;
        }
        
        canvas.addPaintListener(new PaintListener() {
            @Override
            public void paintControl(PaintEvent e) {
                float imageRatio = (float)image.getImageData().width / (float)image.getImageData().height;
                if(canvas.getSize().y * imageRatio > canvas.getSize().x){
                    e.gc.drawImage(image, 0, 0, image.getImageData().width, image.getImageData().height,
                            0, 0, canvas.getSize().x, (int)(canvas.getSize().x / imageRatio));
                } else {
                    e.gc.drawImage(image, 0, 0, image.getImageData().width, image.getImageData().height,
                            0, 0, (int)(canvas.getSize().y * imageRatio), canvas.getSize().y);
                }
            }
        });
        canvas.redraw();
    }
    
    /*
     * (非 Javadoc)
     * 
     * @see org.eclipse.swt.widgets.Decorations#checkSubclass()
     */
    @Override
    protected void checkSubclass() {
        // super.checkSubclass();       // 自動作成後、手動でコメントアウト
    }
}

試行錯誤 : PaintListenerの多重登録で過去の画像が残存する

新たな画像を読み込んだ時に、過去のPaintListenerが自動的に削除されるわけではないため、「明示的に」PaintListenerを削除しないといけないという教訓。下の例は、PaintListenerが多重登録され、2つの画像が同時に表示されてしまっている。

20150411-canvas-image-error.jpg

下記ソースコード抜粋部分で緑着色部分が、既に存在しているPaintListenerを検知して削除する部分。

                String selectedFilename = fileDlg.open();
                if (selectedFilename != null && !selectedFilename.isEmpty()) {
                    if(canvas.isListening(SWT.Paint)){
                        canvas.removeListener(SWT.Paint, (TypedListener)canvas.getListeners(SWT.Paint)[0]);
                    }
                    ReadFromImageFile_Canvas(canvas, selectedFilename);
                }

試行錯誤 : PaintListenerを使わない場合

ウインドウを伸縮すると、再描画されない。ソースコードの抜粋を示す。

    /**
     * 画像ファイルを読み込み、Canvasヴィジェットに表示する
     * @param canvas
     * @param filename
     */
    private void ReadFromImageFile_Canvas(Canvas canvas, String filename) {
        Image image;
        try {
            image = new Image(canvas.getDisplay(), filename);
        } catch (Exception e1) {
            e1.printStackTrace();
            return;
        }
        GC gc = new GC(canvas);
        gc.drawImage(image, 0, 0, image.getImageData().width, image.getImageData().height,
                0, 0, canvas.getSize().x, canvas.getSize().y);
        gc.dispose();    // GCを使い終わったら、必ず明示的に破棄
    }