本文實現基於 Java 2D API 的圖片水印功能,支援文字水印與圖片水印疊加。核心使用 Graphics2D 繪圖上下文與 AlphaComposite 控制透明度混合,僅支援 JPEG 格式輸出(受限於舊版 com.sun.image.codec.jpeg)。
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 class WatermarkUtil {
/**
* 添加文字水印
* @param srcImagePath 源圖片路徑
* @param text 水印文字
* @param font 字體
* @param color 字體顏色
* @param fontSize 字體大小
* @param alpha 透明度 0.0f-1.0f
* @param x X 座標
* @param y Y 座標
* @param outputPath 輸出路徑
* @param formatName 輸出格式 (jpg, png, etc.)
*/
public static void addTextWatermark(String srcImagePath, String text,
Font font, Color color, int fontSize, float alpha,
int x, int y, String outputPath, String formatName) throws IOException {
File srcFile = new File(srcImagePath);
BufferedImage srcImage = ImageIO.read(srcFile);
// 創建 Graphics2D
Graphics2D g2d = srcImage.createGraphics();
// 設置抗鋸齒
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// 設置字體
Font finalFont = font.deriveFont((float) fontSize);
g2d.setFont(finalFont);
// 設置透明度混合模式
AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
g2d.setComposite(composite);
// 設置顏色並繪製文字
g2d.setColor(color);
g2d.drawString(text, x, y);
// 釋放資源
g2d.dispose();
// 寫入輸出
ImageIO.write(srcImage, formatName, new File(outputPath));
}
/**
* 添加圖片水印
* @param srcImagePath 源圖片路徑
* @param watermarkPath 水印圖片路徑
* @param alpha 透明度 0.0f-1.0f
* @param x X 座標
* @param y Y 座標
* @param width 水印寬度 (-1 保持原寬)
* @param height 水印高度 (-1 保持原高)
* @param outputPath 輸出路徑
* @param formatName 輸出格式
*/
public static void addImageWatermark(String srcImagePath, String watermarkPath,
float alpha, int x, int y, int width, int height,
String outputPath, String formatName) throws IOException {
BufferedImage srcImage = ImageIO.read(new File(srcImagePath));
BufferedImage watermark = ImageIO.read(new File(watermarkPath));
// 縮放水印圖片(如指定尺寸)
if (width > 0 && height > 0 &&
(watermark.getWidth() != width || watermark.getHeight() != height)) {
watermark = resizeImage(watermark, width, height);
}
Graphics2D g2d = srcImage.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
g2d.setComposite(composite);
// 繪製水印圖片
g2d.drawImage(watermark, x, y, null);
g2d.dispose();
ImageIO.write(srcImage, formatName, new File(outputPath));
}
/**
* 圖片縮放(雙線性插值)
*/
private static BufferedImage resizeImage(BufferedImage src, int width, int height) {
BufferedImage dest = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
AffineTransform transform = AffineTransform.getScaleInstance(
(double) width / src.getWidth(),
(double) height / src.getHeight()
);
AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
return op.filter(src, dest);
}
/**
* 圖片縮略圖生成(保持比例)
*/
public static void createThumbnail(String srcPath, int maxWidth, int maxHeight, String outputPath) throws IOException {
BufferedImage src = ImageIO.read(new File(srcPath));
double ratio = Math.min(
(double) maxWidth / src.getWidth(),
(double) maxHeight / src.getHeight()
);
if (ratio >= 1.0) {
// 無需縮小,直接複製或轉格式
ImageIO.write(src, getFormat(outputPath), new File(outputPath));
return;
}
int newWidth = (int) (src.getWidth() * ratio);
int newHeight = (int) (src.getHeight() * ratio);
BufferedImage thumb = resizeImage(src, newWidth, newHeight);
ImageIO.write(thumb, getFormat(outputPath), new File(outputPath));
}
private static String getFormat(String path) {
int dot = path.lastIndexOf('.');
return (dot > 0) ? path.substring(dot + 1) : "jpg";
}
// 測試主方法
public static void main(String[] args) {
try {
String src = "F:/27.jpg";
String logo = "F:/logo.jpg";
String outText = "F:/watermark_text.jpg";
String outImage = "F:/watermark_image.jpg";
// 文字水印:紅色、微軟正黑體、77pt、40% 透明度、右上角
addTextWatermark(src, "圖片水印測試",
new Font("微軟正黑體", Font.PLAIN, 77),
Color.RED, 77, 0.4f, 1700, 77, outText, "jpg");
// 圖片水印:右下角、300x200、40% 透明度
addImageWatermark(src, logo, 0.4f, 1700, 77, 300, 200, outImage, "jpg");
System.out.println("水印添加完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
| 參數 | 範圍/值 | 說明 |
|---|---|---|
| alpha | 0.0f - 1.0f | 0 完全透明,1 完全不透明 |
| AlphaComposite.SRC_OVER | 12 種規則之一 | 源覆蓋目標,最常用的疊加模式 |
| 座標 (x, y) | 像素 | 文字基線左下角 / 圖片左上角 |
| 字體 | 系統已安裝字體 | 跨平台建議用邏輯字體:Serif, SansSerif, Monospaced, Dialog, DialogInput |
原因:JVM 找不到指定字體文件
解決:
new Font("SansSerif", Font.PLAIN, 24)GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(Font.createFont(Font.TRUETYPE_FONT, new File("msjh.ttc")))apt-get install -y fontconfig fonts-noto-cjk原因:JPEG 不支援透明通道
解決:輸出格式用 png,且源圖類型用 BufferedImage.TYPE_INT_ARGB
public enum WatermarkPosition {
TOP_LEFT, TOP_CENTER, TOP_RIGHT,
CENTER_LEFT, CENTER, CENTER_RIGHT,
BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT;
public int[] calcPosition(int imgW, int imgH, int wmW, int wmH, int margin) {
int x = 0, y = 0;
switch (this) {
case TOP_LEFT: x = margin; y = margin + wmH; break; // 文字基線
case TOP_CENTER: x = (imgW - wmW) / 2; y = margin + wmH; break;
case TOP_RIGHT: x = imgW - wmW - margin; y = margin + wmH; break;
case CENTER_LEFT: x = margin; y = (imgH + wmH) / 2; break;
case CENTER: x = (imgW - wmW) / 2; y = (imgH + wmH) / 2; break;
case CENTER_RIGHT: x = imgW - wmW - margin; y = (imgH + wmH) / 2; break;
case BOTTOM_LEFT: x = margin; y = imgH - margin; break;
case BOTTOM_CENTER: x = (imgW - wmW) / 2; y = imgH - margin; break;
case BOTTOM_RIGHT: x = imgW - wmW - margin; y = imgH - margin; break;
}
return new int[]{x, y};
}
}
Java 2D API 提供強大的圖片處理能力,配合 AlphaComposite 可輕鬆實現各類水印效果。現代代碼應棄用 com.sun.image.codec.jpeg 等內部 API,全面遷移至標準 ImageIO。生產環境需注意字體部署、透明通道處理、大圖記憶體管理等工程化問題。