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

Java 圖片處理工具類:水印、縮放、補白、格式轉換一站式解決方案

     編輯:Java 相關知識

Java 圖片處理核心技術全解析:文字水印、圖片水印、智能縮放、畫布補白

基於 java.awt 原生 API 實現的企業級圖片處理工具類,無第三方依賴,支援 JPEG/PNG/GIF/BMP 等主流格式,適用於內容審核、版權保護、縮圖生成、報表導出等場景。

核心功能矩陣

功能方法簽名關鍵參數應用場景
文字水印pressText(target, text, font, style, size, color, x, y, alpha)字體、顏色、透明度、位置版權聲明、機密標識、用戶水印
圖片水印pressImage(target, waterImg, x, y, alpha)水印圖、偏移量、透明度Logo 品牌、QR Code、簽名圖
智能縮放resize(file, height, width, needPadding)目標尺寸、是否補白縮圖生成、統一規格、防變形
格式轉換內建輸出 JPEG質量參數可擴展統一存儲格式、減小體積

完整工具類代碼

package com.example.image;

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

public final class ImageUtils {
    private static final String DEFAULT_FORMAT = "jpg";
    private ImageUtils() {}

    /**
     * 添加圖片水印
     * @param targetImg 目標圖片路徑
     * @param waterImg 水印圖片路徑 (支援透明通道 PNG)
     * @param x 水平偏移,負值=居中,超出寬度=靠右
     * @param y 垂直偏移,負值=居中,超出高度=靠下
     * @param alpha 透明度 0.0f(全透明) ~ 1.0f(不透明)
     */
    public static void pressImage(String targetImg, String waterImg, int x, int y, float alpha) {
        try {
            File file = new File(targetImg);
            BufferedImage src = ImageIO.read(file);
            int w = src.getWidth(), h = src.getHeight();
            
            BufferedImage canvas = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = canvas.createGraphics();
            g.drawImage(src, 0, 0, w, h, null);
            
            BufferedImage water = ImageIO.read(new File(waterImg));
            int ww = water.getWidth(), wh = water.getHeight();
            
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
            
            // 位置計算:負值居中,超出邊界緊貼邊緣
            int dx = (x < 0) ? (w - ww) / 2 : Math.min(x, w - ww);
            int dy = (y < 0) ? (h - wh) / 2 : Math.min(y, h - wh);
            
            g.drawImage(water, dx, dy, ww, wh, null);
            g.dispose();
            
            ImageIO.write(canvas, DEFAULT_FORMAT, file);
        } catch (IOException e) {
            throw new RuntimeException("圖片水印失敗: " + targetImg, e);
        }
    }

    /**
     * 添加文字水印
     * @param fontName 字體名稱 (如 "微軟雅黑", "宋體")
     * @param fontStyle Font.PLAIN/BOLD/ITALIC/BOLD|ITALIC
     * @param fontSize 字體大小 (像素)
     * @param color 字體顏色
     * @param x/y/alpha 同圖片水印
     */
    public static void pressText(String targetImg, String text, String fontName, 
                                 int fontStyle, int fontSize, Color color, 
                                 int x, int y, float alpha) {
        try {
            File file = new File(targetImg);
            BufferedImage src = ImageIO.read(file);
            int w = src.getWidth(), h = src.getHeight();
            
            BufferedImage canvas = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = canvas.createGraphics();
            g.drawImage(src, 0, 0, w, h, null);
            
            g.setFont(new Font(fontName, fontStyle, fontSize));
            g.setColor(color);
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
            
            // 估算文字寬度:中文按 fontSize,英文按 fontSize/2
            int textWidth = estimateTextWidth(text, fontSize);
            int textHeight = fontSize;
            
            int dx = (x < 0) ? (w - textWidth) / 2 : Math.min(x, w - textWidth);
            int dy = (y < 0) ? (h - textHeight) / 2 : Math.min(y, h - textHeight);
            
            // 繪製文字 (y 基線需加上字體高度)
            g.drawString(text, dx, dy + textHeight);
            g.dispose();
            
            ImageIO.write(canvas, DEFAULT_FORMAT, file);
        } catch (Exception e) {
            throw new RuntimeException("文字水印失敗: " + targetImg, e);
        }
    }

    /** 估算文字像素寬度:中文字符計 1,英文計 0.5 */
    private static int estimateTextWidth(String text, int fontSize) {
        int len = 0;
        for (char c : text.toCharArray()) {
            len += (String.valueOf(c).getBytes().length > 1) ? 1 : 0.5;
        }
        return (int) Math.ceil(len * fontSize);
    }

    /**
     * 智能縮放圖片
     * @param filePath 圖片路徑 (覆蓋原文件)
     * @param targetHeight 目標高度
     * @param targetWidth 目標寬度
     * @param needPadding true=等比縮放後不足部分補白,false=強制拉伸
     */
    public static void resize(String filePath, int targetHeight, int targetWidth, boolean needPadding) {
        try {
            File file = new File(filePath);
            BufferedImage src = ImageIO.read(file);
            int srcW = src.getWidth(), srcH = src.getHeight();
            
            // 計算縮放比例 (取較小者保證完整顯示)
            double ratio = Math.min((double) targetWidth / srcW, (double) targetHeight / srcH);
            
            AffineTransformOp op = new AffineTransformOp(
                AffineTransform.getScaleInstance(ratio, ratio), 
                AffineTransformOp.TYPE_BILINEAR); // 雙線性插值,質量較好
            BufferedImage scaled = op.filter(src, null);
            
            BufferedImage result;
            if (needPadding) {
                // 建立目標畫布,居中貼圖,背景白色
                result = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
                Graphics2D g = result.createGraphics();
                g.setColor(Color.WHITE);
                g.fillRect(0, 0, targetWidth, targetHeight);
                int dx = (targetWidth - scaled.getWidth()) / 2;
                int dy = (targetHeight - scaled.getHeight()) / 2;
                g.drawImage(scaled, dx, dy, null);
                g.dispose();
            } else {
                result = scaled;
            }
            
            ImageIO.write(result, DEFAULT_FORMAT, file);
        } catch (IOException e) {
            throw new RuntimeException("圖片縮放失敗: " + filePath, e);
        }
    }
    
    // 測試主方法
    public static void main(String[] args) {
        pressImage("photo.jpg", "logo.png", -1, -1, 0.5f); // 居中半透明水印
        pressText("photo.jpg", "© 2024 MyCompany", "微軟雅黑", Font.BOLD, 24, Color.WHITE, -1, -1, 0.8f);
        resize("photo.jpg", 800, 600, true); // 等比縮放至 800x600 內,補白
    }
}

關鍵技術點深度解析

1. AlphaComposite 實現透明度混合

AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)
// SRC_ATOP: 只在目標不透明區域顯示源圖像,配合 alpha 實現半透明疊加

2. AffineTransformOp 高質量縮放

  • TYPE_NEAREST_NEIGHBOR:最鄰近插值,速度最快,質量最差
  • TYPE_BILINEAR:雙線性插值,速度質量平衡 (推薦)
  • TYPE_BICUBIC:雙三次插值,質量最好,速度最慢

3. 文字寬度估算算法

Java 原生 FontMetrics 需要 Graphics 上下文,工具類採用啟發式估算:中文字符按字體大小計算,英文按一半,足夠滿足水印定位需求,避免創建臨時 Graphics 開銷。

4. 顏色模型選擇

  • TYPE_INT_RGB:無透明通道,輸出 JPEG 適合,記憶體佔用較小
  • TYPE_INT_ARGB:含透明通道,處理 PNG 水印時需使用,否則透明區變黑

生產環境擴展建議

需求解決方案關鍵技術
輸出質量控制JPEG 壓縮質量參數ImageWriteParam + JPEGImageWriteParam
大圖處理不 OOM分塊處理/流式寫入ImageReader.readAsRenderedImage + ImageWriter.writeInsert
圖片旋轉/翻轉AffineTransform 旋轉實例AffineTransform.getRotateInstance(Math.PI/2)
EXIF 方向修正讀取 Orientation 標籤自動旋轉Metadata-Extractor 庫或 Sanselan
高併發處理線程池 + 異步任務CompletableFuture + 自定義執行器

替代方案對比

  • Thumbnailator:API 極簡 (Thumbnails.of(...).size(...).toFile(...)),內部封裝 AWT,適合簡單縮放水印
  • TwelveMonkeys ImageIO:擴展格式支援 (TIFF, JPEG 2000, PSD, ICO...)
  • ImageMagick (JMagick/IM4Java):功能最強,需安裝原生庫,適合複雜圖像處理管道
  • 原生 AWT (本文方案):零依賴、JDK 內建、部署最簡、效能基準、完全可控
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved