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

Java ImageIO 圖片水印完整實戰:圖片/文字雙模式、旋轉、透明度與縮放工具類

     編輯:Java 程式設計入門

基於 javax.imageio 與 java.awt 封裝的企業級水印工具類,支援圖片水印與文字水印,提供旋轉角度、位置偏移、透明度調節等豐富參數,並附帶等比縮放輔助方法,可直接集成至 Spring Boot、Struts 等項目中。

核心功能清單

  • 圖片水印: 支持 PNG/GIF 透明通道,可設置旋轉角度、左上角偏移量、透明度(0.0~1.0)
  • 文字水印: 自定義字體、字號、顏色、旋轉、位置、透明度
  • 圖片縮放: 等比縮放至指定長寬,不足區域白色填充,避免變形
  • 資源安全: 統一使用 try-with-resources 或 finally 關閉流,防止句柄洩漏

完整工具類代碼

package com.blogs.image;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;

public final class ImageWatermarkUtil {

    private ImageWatermarkUtil() {} // 工具類禁止實例化

    /**
     * 圖片水印
     *
     * @param iconPath   水印圖片路徑(建議 PNG 含透明通道)
     * @param srcImgPath 原圖路徑
     * @param targetPath 輸出路徑
     * @param degree     旋轉角度(度),null 表示不旋轉
     * @param x          水印左上角 X 坐標(相對原圖左上角)
     * @param y          水印左上角 Y 坐標
     * @param alpha      透明度 0.0f(全透明)~1.0f(不透明)
     */
    public static void watermarkByImage(String iconPath, String srcImgPath,
                                        String targetPath, Integer degree,
                                        Integer x, Integer y, float alpha) {
        try (OutputStream os = new FileOutputStream(targetPath)) {
            BufferedImage srcImg = ImageIO.read(new File(srcImgPath));
            int w = srcImg.getWidth(), h = srcImg.getHeight();

            BufferedImage canvas = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = canvas.createGraphics();

            // 抗鋸齒、雙線性插值提升質量
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

            // 繪製原圖
            g.drawImage(srcImg, 0, 0, null);

            // 旋轉畫布(以中心為軸)
            if (degree != null) {
                g.rotate(Math.toRadians(degree), w / 2.0, h / 2.0);
            }

            // 載入水印圖
            Image watermarkImg = new ImageIcon(iconPath).getImage();
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
            g.drawImage(watermarkImg, x, y, null);
            g.setComposite(AlphaComposite.SrcOver); // 恢復默認混合模式
            g.dispose();

            ImageIO.write(canvas, "JPG", os);
            System.out.println("圖片水印添加完成:" + targetPath);
        } catch (IOException e) {
            throw new RuntimeException("圖片水印處理失敗", e);
        }
    }

    /**
     * 文字水印
     */
    public static void watermarkByText(String text, String srcImgPath,
                                       String targetPath, Integer degree,
                                       Integer x, Integer y, float alpha,
                                       Font font, Color color) {
        try (OutputStream os = new FileOutputStream(targetPath)) {
            BufferedImage srcImg = ImageIO.read(new File(srcImgPath));
            int w = srcImg.getWidth(), h = srcImg.getHeight();

            BufferedImage canvas = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = canvas.createGraphics();
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

            g.drawImage(srcImg, 0, 0, null);

            if (degree != null) {
                g.rotate(Math.toRadians(degree), w / 2.0, h / 2.0);
            }

            g.setFont(font != null ? font : new Font("微軟雅黑", Font.BOLD, 30));
            g.setColor(color != null ? color : Color.RED);
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
            g.drawString(text, x, y);
            g.setComposite(AlphaComposite.SrcOver);
            g.dispose();

            ImageIO.write(canvas, "JPG", os);
            System.out.println("文字水印添加完成:" + targetPath);
        } catch (IOException e) {
            throw new RuntimeException("文字水印處理失敗", e);
        }
    }

    /**
     * 等比縮放並居中填充白底
     *
     * @param srcPath  源圖路徑
     * @param destPath 目標路徑
     * @param maxWidth  目標最大寬度
     * @param maxHeight 目標最大高度
     */
    public static void resizeWithPadding(String srcPath, String destPath,
                                         int maxWidth, int maxHeight) {
        try {
            BufferedImage src = ImageIO.read(new File(srcPath));
            int srcW = src.getWidth(), srcH = src.getHeight();

            double scale = Math.min((double) maxWidth / srcW, (double) maxHeight / srcH);
            int targetW = (int) Math.round(srcW * scale);
            int targetH = (int) Math.round(srcH * scale);

            BufferedImage canvas = new BufferedImage(maxWidth, maxHeight, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = canvas.createGraphics();
            g.setBackground(Color.WHITE);
            g.clearRect(0, 0, maxWidth, maxHeight);
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g.drawImage(src.getScaledInstance(targetW, targetH, Image.SCALE_SMOOTH),
                    (maxWidth - targetW) / 2, (maxHeight - targetH) / 2, null);
            g.dispose();

            ImageIO.write(canvas, "JPEG", new File(destPath));
            System.out.println("圖片縮放完成:" + destPath);
        } catch (IOException e) {
            throw new RuntimeException("圖片縮放失敗", e);
        }
    }

    // ----------------- 測試入口 -----------------
    public static void main(String[] args) {
        String src = "e:/2.png";
        String logo = "e:/logo.png";
        String out1 = "e:/watermark_img.jpg";
        String out2 = "e:/watermark_text.jpg";
        String out3 = "e:/resized.jpg";

        // 圖片水印:右下角,透明度 0.3,旋轉 -45°
        watermarkByImage(logo, src, out1, -45, 200, 200, 0.3f);

        // 文字水印:左上角,紅色粗體,透明度 0.5
        watermarkByText("@MyBlog", src, out2, null, 50, 50, 0.5f,
                new Font("微軟雅黑", Font.BOLD, 40), Color.RED);

        // 等比縮放至 300x300
        resizeWithPadding(src, out3, 300, 300);
    }
}

關鍵技術點解析

1. AlphaComposite 實現透明度混合

AlphaComposite.SRC_ATOP 表示「源圖僅在目標圖不透明區域顯示」,配合 alpha 參數即可實現半透明水印效果。繪製完成後務必恢復為 AlphaComposite.SrcOver,避免影響後續繪圖操作。

2. 旋轉變換的坐標系陷阱

g.rotate(theta, x, y) 以 (x,y) 為旋轉中心。若在繪製水印前旋轉,水印座標需按旋轉後坐標系計算;若需水印圖片自身旋轉而不影響原圖,可另建 BufferedImage 繪製水印後再合成。本工具類採用「整體畫布旋轉」策略,適合全圖鋪滿式水印。

3. 等比縮放的數學原理

縮放比例 scale = min(maxW/srcW, maxH/srcH) 保證長邊貼邊、短邊留白,再通過 (maxW - targetW)/2 實現水平垂直居中。

生產環境建議

  • 高併發場景下,建議引入 Thumbnailator 或 TwelveMonkeys ImageIO 替代原生 ImageIO,獲得更好的性能與格式支援(如 WebP、HEIC)。
  • 水印圖片預先處理為適當尺寸,避免運行時大圖縮放消耗 CPU/記憶體。
  • 使用 BufferedImage.TYPE_INT_ARGB 保留 Alpha 通道,輸出 PNG 格式可保持透明背景。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved