基於 javax.imageio 與 java.awt 封裝的企業級水印工具類,支援圖片水印與文字水印,提供旋轉角度、位置偏移、透明度調節等豐富參數,並附帶等比縮放輔助方法,可直接集成至 Spring Boot、Struts 等項目中。
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);
}
}
AlphaComposite.SRC_ATOP 表示「源圖僅在目標圖不透明區域顯示」,配合 alpha 參數即可實現半透明水印效果。繪製完成後務必恢復為 AlphaComposite.SrcOver,避免影響後續繪圖操作。
g.rotate(theta, x, y) 以 (x,y) 為旋轉中心。若在繪製水印前旋轉,水印座標需按旋轉後坐標系計算;若需水印圖片自身旋轉而不影響原圖,可另建 BufferedImage 繪製水印後再合成。本工具類採用「整體畫布旋轉」策略,適合全圖鋪滿式水印。
縮放比例 scale = min(maxW/srcW, maxH/srcH) 保證長邊貼邊、短邊留白,再通過 (maxW - targetW)/2 實現水平垂直居中。
BufferedImage.TYPE_INT_ARGB 保留 Alpha 通道,輸出 PNG 格式可保持透明背景。