首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java SWT 顯示圖片完整範例:從載入到縮放、雙緩衝渲染

     編輯:Java 相關知識

前言

標準 JDK 未內建 SWT (Standard Widget Toolkit),需額外引入平台相關 JAR。本文提供一個完整的 SWT 圖片檢視器範例,涵蓋 Image 載入、Canvas 繪圖、滑鼠滾輪縮放、拖曳平移、雙緩衝防閃爍、資源釋放等實務技巧。

Maven 依賴 (Windows x86_64 範例)

<dependency>
    <groupId>org.eclipse.platform</groupId>
    <artifactId>org.eclipse.swt.win32.win32.x86_64</artifactId>
    <version>4.28</version>
</dependency>

其他平台請換對應 artifactId:linux.gtk.x86_64、macosx.cocoa.x86_64 等。

完整圖片檢視器程式碼

package com.example.swt;

import org.eclipse.swt.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

public class SwtImageViewer {

    private Shell shell;
    private Canvas canvas;
    private Image originalImage;
    private Image displayImage; // 縮放後用於繪圖
    private float scale = 1.0f;
    private int offsetX = 0, offsetY = 0;
    private int dragStartX, dragStartY;
    private boolean dragging = false;

    public static void main(String[] args) {
        new SwtImageViewer().run();
    }

    public void run() {
        Display display = new Display();
        shell = new Shell(display);
        shell.setText("SWT 圖片檢視器");
        shell.setLayout(new FillLayout());
        shell.setSize(1024, 768);

        // 選擇檔案對話框
        FileDialog fd = new FileDialog(shell, SWT.OPEN);
        fd.setFilterNames(new String[] {"圖片檔案", "所有檔案"});
        fd.setFilterExtensions(new String[] {"*.jpg;*.jpeg;*.png;*.bmp;*.gif", "*.*"});
        String path = fd.open();
        if (path == null) {
            display.dispose();
            return;
        }

        // 載入原始圖片
        originalImage = new Image(display, path);
        if (originalImage.getBounds().isEmpty()) {
            System.err.println("無法載入圖片: " + path);
            display.dispose();
            return;
        }

        canvas = new Canvas(shell, SWT.DOUBLE_BUFFERED | SWT.H_SCROLL | SWT.V_SCROLL);
        canvas.addPaintListener(this::onPaint);
        canvas.addMouseWheelListener(this::onMouseWheel);
        canvas.addMouseListener(new MouseAdapter() {
            @Override public void mouseDown(MouseEvent e) {
                if (e.button == 1) { // 左鍵拖曳
                    dragging = true;
                    dragStartX = e.x - offsetX;
                    dragStartY = e.y - offsetY;
                }
            }
            @Override public void mouseUp(MouseEvent e) {
                dragging = false;
            }
        });
        canvas.addMouseMoveListener(e -> {
            if (dragging) {
                offsetX = e.x - dragStartX;
                offsetY = e.y - dragStartY;
                canvas.redraw();
            }
        });
        // 雙擊重置
        canvas.addMouseListener(new MouseAdapter() {
            @Override public void mouseDoubleClick(MouseEvent e) {
                resetView();
            }
        });

        // 選單列:檔案/檢視
        createMenuBar();

        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) display.sleep();
        }
        // 資源釋放
        if (originalImage != null) originalImage.dispose();
        if (displayImage != null) displayImage.dispose();
        display.dispose();
    }

    private void createMenuBar() {
        Menu bar = new Menu(shell, SWT.BAR);
        shell.setMenuBar(bar);

        MenuItem fileItem = new MenuItem(bar, SWT.CASCADE);
        fileItem.setText("檔案(&F)");
        Menu fileMenu = new Menu(shell, SWT.DROP_DOWN);
        fileItem.setMenu(fileMenu);
        MenuItem openItem = new MenuItem(fileMenu, SWT.PUSH);
        openItem.setText("開啟(&O)\tCtrl+O");
        openItem.addListener(SWT.Selection, e -> openNewImage());
        MenuItem exitItem = new MenuItem(fileMenu, SWT.PUSH);
        exitItem.setText("結束(&X)");
        exitItem.addListener(SWT.Selection, e -> shell.close());

        MenuItem viewItem = new MenuItem(bar, SWT.CASCADE);
        viewItem.setText("檢視(&V)");
        Menu viewMenu = new Menu(shell, SWT.DROP_DOWN);
        viewItem.setMenu(viewMenu);
        MenuItem zoomIn = new MenuItem(viewMenu, SWT.PUSH);
        zoomIn.setText("放大\tCtrl++");
        zoomIn.addListener(SWT.Selection, e -> zoom(1.2f));
        MenuItem zoomOut = new MenuItem(viewMenu, SWT.PUSH);
        zoomOut.setText("縮小\tCtrl+-");
        zoomOut.addListener(SWT.Selection, e -> zoom(0.8f));
        MenuItem resetItem = new MenuItem(viewMenu, SWT.PUSH);
        resetItem.setText("實際大小\tCtrl+0");
        resetItem.addListener(SWT.Selection, e -> resetView());
    }

    private void openNewImage() {
        FileDialog fd = new FileDialog(shell, SWT.OPEN);
        fd.setFilterExtensions(new String[] {"*.jpg;*.jpeg;*.png;*.bmp;*.gif"});
        String path = fd.open();
        if (path != null) {
            if (originalImage != null) originalImage.dispose();
            if (displayImage != null) displayImage.dispose();
            originalImage = new Image(shell.getDisplay(), path);
            resetView();
        }
    }

    private void onPaint(PaintEvent e) {
        GC gc = e.gc;
        gc.setAntialias(SWT.ON);
        gc.setInterpolation(SWT.HIGH);

        if (displayImage == null || displayImage.isDisposed()) {
            // 首次或縮放後重建
            Rectangle src = originalImage.getBounds();
            int w = (int) (src.width * scale);
            int h = (int) (src.height * scale);
            displayImage = new Image(shell.getDisplay(), w, h);
            GC imgGc = new GC(displayImage);
            imgGc.setAntialias(SWT.ON);
            imgGc.setInterpolation(SWT.HIGH);
            imgGc.drawImage(originalImage, 0, 0, src.width, src.height, 0, 0, w, h);
            imgGc.dispose();
        }

        // 計算置中或偏移位置
        Rectangle client = canvas.getClientArea();
        int x = (client.width - displayImage.getBounds().width) / 2 + offsetX;
        int y = (client.height - displayImage.getBounds().height) / 2 + offsetY;
        gc.drawImage(displayImage, x, y);
    }

    private void onMouseWheel(MouseEvent e) {
        if ((e.stateMask & SWT.CTRL) != 0) {
            zoom(e.count > 0 ? 1.1f : 0.9f); // 向上放大
        } else {
            // 垂直捲動由 SWT.H_SCROLL/V_SCROLL 自動處理
        }
    }

    private void zoom(float factor) {
        scale *= factor;
        scale = Math.max(0.1f, Math.min(10f, scale)); // 限制 10%-1000%
        if (displayImage != null) {
            displayImage.dispose();
            displayImage = null;
        }
        canvas.redraw();
    }

    private void resetView() {
        scale = 1.0f;
        offsetX = offsetY = 0;
        if (displayImage != null) {
            displayImage.dispose();
            displayImage = null;
        }
        canvas.redraw();
    }
}

關鍵技術點解析

技術說明
SWT.DOUBLE_BUFFEREDCanvas 內建雙緩衝,消除重繪閃爍,無需手動實作。
Image.dispose()SWT 圖片資源由作業系統管理,必須手動釋放,否則會洩漏 GDI/GTK 控制代碼。
GC.setInterpolation(SWT.HIGH)啟用高品質雙三次/蘭喬斯插值,縮放畫質優於預設最近鄰。
drawImage(src, sx, sy, sw, sh, dx, dy, dw, dh)單次呼叫完成裁剪+縮放+繪製,效能最佳。
滑鼠滾輪 e.count正值=向上/向前,負值=向下/向後;配合 CTRL 鍵區分縮放與捲動。

常見問題與解法

  • UnsatisfiedLinkError:未引入正確平台的 SWT JAR,或 32/64-bit 不匹配。請依 JVM 位元數選擇 artifact。
  • 圖片載入失敗:SWT 僅支援平台原生格式 (Windows: BMP/JPG/PNG/GIF/ICO;Linux 需安裝 libpng/libjpeg)。WebP、HEIC 需自行解碼轉 Bitmap。
  • 大圖 OOM:new Image(display, path) 會完整解碼到記憶體。超大圖 (如 100MP+) 建議先用 ImageIO 或 TwelveMonkeys 讀取縮略圖,或分塊載入。
  • 高 DPI 模糊:Windows 10+ 需在啟動參數加 -Dsun.java2d.uiScale.enabled=true 或 SWT 4.24+ Display.setAutoScaling(true)。

擴充建議

  1. 加入直方圖/EXIF 資訊面板。
  2. 支援多頁 TIFF / 動畫 GIF 播放。
  3. 整合 OpenCV / ImageJ 進行影像處理 (濾鏡、邊緣偵測)。
  4. 打包為可執行 JAR (使用 maven-shade-plugin + SWT 原生庫解壓縮)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved