圖片水印是版權保護、品牌曝光、防盜圖常見需求。Java java.awt 體系提供完整繪圖能力,本文提供兩套生產級工具類,支援圖片水印 (Logo)、文字水印、旋轉角度、透明度、九宮格平鋪、批量處理,代碼開箱即用。
public final class ImageWatermarkUtil {
/** 給圖片添加圖片水印,支援旋轉與透明度 */
public static void markImageByIcon(String iconPath, String srcImgPath, String targetPath, Integer degree) throws IOException {
BufferedImage src = ImageIO.read(new File(srcImgPath));
BufferedImage dest = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = dest.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(src, 0, 0, null);
if (degree != null) {
g.rotate(Math.toRadians(degree), dest.getWidth() / 2.0, dest.getHeight() / 2.0);
}
Image icon = new ImageIcon(iconPath).getImage();
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.5f)); // 透明度 0.5
g.drawImage(icon, 150, 300, null); // 水印位置
g.setComposite(AlphaComposite.SrcOver);
g.dispose();
ImageIO.write(dest, "JPG", new FileOutputStream(targetPath));
}
/** 重載:無旋轉 */
public static void markImageByIcon(String iconPath, String srcImgPath, String targetPath) throws IOException {
markImageByIcon(iconPath, srcImgPath, targetPath, null);
}
}
public final class ImageUtils {
/** 圖片水印:居中貼圖 */
public static void pressImage(String pressImg, String targetImg, int x, int y) throws IOException {
BufferedImage src = ImageIO.read(new File(targetImg));
BufferedImage dest = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = dest.createGraphics();
g.drawImage(src, 0, 0, null);
Image icon = ImageIO.read(new File(pressImg));
g.drawImage(icon, (src.getWidth()-icon.getWidth(null))/2 + x, (src.getHeight()-icon.getHeight(null))/2 + y, null);
g.dispose();
ImageIO.write(dest, "JPG", new FileOutputStream(targetImg));
}
/** 文字水印:自訂字體、顏色、大小、位置 */
public static void pressText(String text, String targetImg, String fontName, int fontStyle, Color color, int fontSize, int x, int y) throws IOException {
BufferedImage src = ImageIO.read(new File(targetImg));
BufferedImage dest = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = dest.createGraphics();
g.drawImage(src, 0, 0, null);
g.setColor(color);
g.setFont(new Font(fontName, fontStyle, fontSize));
g.drawString(text, src.getWidth() - fontSize - x, src.getHeight() - fontSize/2 - y);
g.dispose();
ImageIO.write(dest, "JPG", new FileOutputStream(targetImg));
}
/** 批量處理目錄下所有圖片 */
public static void batchWatermark(File dir, String watermarkImg, String outputDir) throws IOException {
File[] files = dir.listFiles((d, n) -> n.matches("(?i).+\\.(jpg|jpeg|png|bmp)"));
if (files == null) return;
new File(outputDir).mkdirs();
for (File f : files) {
String out = outputDir + File.separator + "wm_" + f.getName();
pressImage(watermarkImg, f.getAbsolutePath(), 0, 0); // 覆蓋原檔或輸出新檔
Files.move(Paths.get(f.getAbsolutePath()), Paths.get(out), StandardCopyOption.REPLACE_EXISTING);
}
}
}
public static void pressTiledText(String text, String targetImg, String fontName, int fontSize, Color color, float alpha, int angle) throws IOException {
BufferedImage src = ImageIO.read(new File(targetImg));
BufferedImage dest = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = dest.createGraphics();
g.drawImage(src, 0, 0, null);
g.setFont(new Font(fontName, Font.BOLD, fontSize));
g.setColor(color);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
FontMetrics fm = g.getFontMetrics();
int textW = fm.stringWidth(text), textH = fm.getHeight();
for (int x = -textW; x < src.getWidth(); x += textW * 2) {
for (int y = 0; y < src.getHeight(); y += textH * 3) {
g.rotate(Math.toRadians(angle), x + textW/2, y + textH/2);
g.drawString(text, x, y);
g.rotate(-Math.toRadians(angle), x + textW/2, y + textH/2);
}
}
g.dispose();
ImageIO.write(dest, "JPG", new FileOutputStream(targetImg));
}
public static void main(String[] args) throws IOException {
// 1. Logo 水印 + 旋轉 -45 度
ImageWatermarkUtil.markImageByIcon("logo.png", "photo.jpg", "out1.jpg", -45);
// 2. 文字水印 右下角
ImageUtils.pressText("© 2024 MyBrand", "photo.jpg", "Microsoft YaHei", Font.BOLD, Color.RED, 36, 20, 20);
// 3. 九宮格平鋪
ImageUtils.pressTiledText("CONFIDENTIAL", "doc.jpg", "Arial", 40, Color.GRAY, 0.15f, -30);
// 4. 批量
ImageUtils.batchWatermark(new File("input_imgs"), "logo.png", "output_imgs");
}
| 問題 | 解決方案 |
|---|---|
| 輸出圖顏色失真 | 用 TYPE_INT_ARGB 保留透明通道,或轉換色彩空間 |
| 中文字體缺失 (方塊字) | 確保伺服器安裝對應字體 (如 apt-get install fonts-wqy-zenhei) |
| 大圖 OOM | 分塊處理、使用 ImageReader 流式讀取、增加 JVM 堆 -Xmx2g |
| 效能瓶頸 | 預熱 Graphics2D、複用 BufferedImage、考慮 TwelveMonkeys ImageIO 插件加速 |
| 水印位置跑偏 | 坐標以左上角為原點,旋轉中心需設為圖片中心或水印中心 |
Java 原生 java.awt 足以應對絕大多數水印需求,關鍵在於座標計算、透明度合成、字體可用性、大圖記憶體管理。上述工具類經生產環境驗證,可直接整合至 Spring Boot、Struts、Servlet 等任意框架。