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

Java 圖片處理工具類:縮放、裁切、格式轉換、灰階、文字/圖片水印一站式實作

     編輯:關於JSP

Java java.awt.image 與 javax.imageio 提供完整的影像處理能力,無需外部依賴即可實現縮放、裁切、轉檔、水印等常見需求。本文整理一個生產級 ImageUtils 工具類,含完整參數說明、單元測試入口與最佳實務建議。

核心功能清單

  • 縮放:按比例、指定寬高(可選補白保持比例)
  • 裁切:座標範圍、等分行列、固定塊大小
  • 格式轉換:JPG↔PNG↔GIF↔BMP 互轉
  • 灰階:ColorConvertOp 一鍵轉換
  • 文字水印:字型、顏色、大小、透明度、位置微調
  • 圖片水印:疊加 Logo、透明度控制、預設置中

完整工具類代碼

package com.example.util;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.*;
import java.io.File;
import java.io.IOException;

public final class ImageUtils {
    private ImageUtils() {} // 工具類禁止實例化

    // ===== 常見格式常數 =====
    public static final String FMT_JPG  = "jpg";
    public static final String FMT_JPEG = "jpeg";
    public static final String FMT_PNG  = "png";
    public static final String FMT_GIF  = "gif";
    public static final String FMT_BMP  = "bmp";

    // ===== 1. 縮放 =====
    /** 按比例縮放 */
    public static void scale(String src, String dest, double ratio, boolean enlarge) throws IOException {
        BufferedImage srcImg = ImageIO.read(new File(src));
        int w = (int) (srcImg.getWidth() * (enlarge ? ratio : 1/ratio));
        int h = (int) (srcImg.getHeight() * (enlarge ? ratio : 1/ratio));
        resizeAndWrite(srcImg, dest, w, h, false);
    }

    /** 指定寬高縮放(可選補白) */
    public static void scaleFixed(String src, String dest, int targetW, int targetH, boolean padWhite) throws IOException {
        BufferedImage srcImg = ImageIO.read(new File(src));
        resizeAndWrite(srcImg, dest, targetW, targetH, padWhite);
    }

    // ===== 2. 裁切 =====
    /** 指定座標裁切 */
    public static void crop(String src, String dest, int x, int y, int w, int h) throws IOException {
        BufferedImage srcImg = ImageIO.read(new File(src));
        BufferedImage cropped = srcImg.getSubimage(x, y, w, h);
        ImageIO.write(cropped, getFormat(dest), new File(dest));
    }

    /** 等分行列裁切 */
    public static void cropGrid(String src, String destDir, int rows, int cols) throws IOException {
        BufferedImage srcImg = ImageIO.read(new File(src));
        int sw = srcImg.getWidth(), sh = srcImg.getHeight();
        int tileW = (int) Math.ceil((double) sw / cols);
        int tileH = (int) Math.ceil((double) sh / rows);
        File dir = new File(destDir);
        dir.mkdirs();
        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                int x = c * tileW;
                int y = r * tileH;
                int w = Math.min(tileW, sw - x);
                int h = Math.min(tileH, sh - y);
                BufferedImage tile = srcImg.getSubimage(x, y, w, h);
                File out = new File(dir, String.format("r%d_c%d.%s", r, c, getFormat(src)));
                ImageIO.write(tile, getFormat(src), out);
            }
        }
    }

    // ===== 3. 格式轉換 =====
    public static void convert(String src, String dest, String formatName) throws IOException {
        BufferedImage img = ImageIO.read(new File(src));
        ImageIO.write(img, formatName, new File(dest));
    }

    // ===== 4. 灰階 =====
    public static void toGray(String src, String dest) throws IOException {
        BufferedImage srcImg = ImageIO.read(new File(src));
        ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
        ColorConvertOp op = new ColorConvertOp(cs, null);
        BufferedImage gray = op.filter(srcImg, null);
        ImageIO.write(gray, getFormat(dest), new File(dest));
    }

    // ===== 5. 文字水印 =====
    public static void watermarkText(String src, String dest, String text,
                                     String fontName, int fontStyle, Color color,
                                     int fontSize, int offsetX, int offsetY, float alpha) throws IOException {
        BufferedImage srcImg = ImageIO.read(new File(src));
        int w = srcImg.getWidth(), h = srcImg.getHeight();
        BufferedImage canvas = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = canvas.createGraphics();
        g2.drawImage(srcImg, 0, 0, null);
        g2.setFont(new Font(fontName, fontStyle, fontSize));
        g2.setColor(color);
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
        // 文字置中 + 微調
        FontMetrics fm = g2.getFontMetrics();
        int textW = fm.stringWidth(text);
        int textH = fm.getHeight();
        int x = (w - textW) / 2 + offsetX;
        int y = (h + textH) / 2 + offsetY;
        g2.drawString(text, x, y);
        g2.dispose();
        ImageIO.write(canvas, getFormat(dest), new File(dest));
    }

    // ===== 6. 圖片水印 =====
    public static void watermarkImage(String src, String dest, String watermarkFile,
                                      int offsetX, int offsetY, float alpha) throws IOException {
        BufferedImage srcImg = ImageIO.read(new File(src));
        BufferedImage markImg = ImageIO.read(new File(watermarkFile));
        int w = srcImg.getWidth(), h = srcImg.getHeight();
        BufferedImage canvas = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = canvas.createGraphics();
        g2.drawImage(srcImg, 0, 0, null);
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
        int mw = markImg.getWidth(), mh = markImg.getHeight();
        int x = (w - mw) / 2 + offsetX;
        int y = (h - mh) / 2 + offsetY;
        g2.drawImage(markImg, x, y, null);
        g2.dispose();
        ImageIO.write(canvas, getFormat(dest), new File(dest));
    }

    // ===== 內部輔助 =====
    private static void resizeAndWrite(BufferedImage src, String dest, int w, int h, boolean padWhite) throws IOException {
        BufferedImage result;
        if (padWhite) {
            // 保持比例縮放至目標框內,不足處補白
            double ratio = Math.min((double) w / src.getWidth(), (double) h / src.getHeight());
            int nw = (int) (src.getWidth() * ratio);
            int nh = (int) (src.getHeight() * ratio);
            BufferedImage scaled = new BufferedImage(nw, nh, BufferedImage.TYPE_INT_RGB);
            Graphics2D g2 = scaled.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2.drawImage(src, 0, 0, nw, nh, null);
            g2.dispose();

            result = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
            g2 = result.createGraphics();
            g2.setColor(Color.WHITE);
            g2.fillRect(0, 0, w, h);
            g2.drawImage(scaled, (w - nw) / 2, (h - nh) / 2, null);
            g2.dispose();
        } else {
            // 直接拉伸填滿
            result = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
            Graphics2D g2 = result.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2.drawImage(src, 0, 0, w, h, null);
            g2.dispose();
        }
        ImageIO.write(result, getFormat(dest), new File(dest));
    }

    private static String getFormat(String fileName) {
        int dot = fileName.lastIndexOf('.');
        return (dot > 0) ? fileName.substring(dot + 1).toLowerCase() : FMT_JPG;
    }

    // ===== 測試入口 =====
    public static void main(String[] args) throws IOException {
        String src = "e:/test.jpg";
        scale(src, "e:/test_scale.jpg", 0.5, false);
        scaleFixed(src, "e:/test_fixed.jpg", 400, 300, true);
        crop(src, "e:/test_crop.jpg", 100, 100, 200, 200);
        cropGrid(src, "e:/tiles", 2, 3);
        convert(src, "e:/test.png", "png");
        toGray(src, "e:/test_gray.jpg");
        watermarkText(src, "e:/test_textwm.jpg", "版權所有", "微軟正黑體", Font.BOLD, Color.WHITE, 48, 0, 0, 0.5f);
        watermarkImage(src, "e:/test_imgwm.jpg", "e:/logo.png", 10, 10, 0.7f);
    }
}

關鍵技術點解析

功能核心 API注意事項
縮放AffineTransformOp、getScaledInstance雙線性插值 RenderingHints.VALUE_INTERPOLATION_BILINEAR 品質較好
裁切BufferedImage.getSubimage()回傳視圖共用資料,寫入前建議 new BufferedImage(...) 複製
水印透明度AlphaComposite.SRC_ATOPalpha 0.0~1.0,疊加模式保留原圖非透明區
文字置中FontMetrics.stringWidth()基線座標 y 需加 getAscent() 或用 getHeight() 近似
PNG 透明背景BufferedImage.TYPE_INT_ARGB輸出 PNG 保留透明通道,JPG 不支援透明

生產環境最佳實務

  1. 串流處理大圖:超過 4K 解析度建議用 ImageReader.setInput 分塊讀取,避免 OOM。
  2. 線程安全:Graphics2D 非線程安全,請勿共用實例;工具類方法內部建立即用即銷。
  3. 資源關閉:g2.dispose() 必須呼叫,釋放原生資源。
  4. 字型可用性:Linux 伺服器常缺中文字體,需安裝 fontconfig + 字體檔,或打包 .ttf 用 Font.createFont 動態載入。
  5. 並行處理:批量水印可用 CompletableFuture 或線程池並行,CPU 密集型任務核心數配置。

擴充方向

  • 引入 Thumbnailator(Thumbnails.of(src).size(200,200).toFile(dest))簡化縮放代碼。
  • 引入 TwelveMonkeys ImageIO 擴充支援 TIFF、ICO、WEBP、JPEG 2000 等格式。
  • 結合 OpenCV (JavaCV) 實現進階濾鏡、人臉偵測水印避開、超解析度放大。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved