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

Java 圖片水印添加實現(文字與圖片水印)

     編輯:Java 相關知識

本文實現基於 Java 2D API 的圖片水印功能,支援文字水印與圖片水印疊加。核心使用 Graphics2D 繪圖上下文與 AlphaComposite 控制透明度混合,僅支援 JPEG 格式輸出(受限於舊版 com.sun.image.codec.jpeg)。

核心技術點

  • Graphics2D:Java 2D 繪圖上下文,支援幾何圖形、文字、圖片繪製
  • AlphaComposite.SRC_OVER:Porter-Duff 疊加規則,源圖覆蓋目標圖,配合 alpha 實現透明度
  • AffineTransformOp:圖片縮放變換,支援雙線性插值
  • ImageIO:標準圖片讀寫 API(替代舊版 JPEGCodec)

完整代碼實現(現代化重構版)


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();
        }
    }
}

關鍵參數說明

參數範圍/值說明
alpha0.0f - 1.0f0 完全透明,1 完全不透明
AlphaComposite.SRC_OVER12 種規則之一源覆蓋目標,最常用的疊加模式
座標 (x, y)像素文字基線左下角 / 圖片左上角
字體系統已安裝字體跨平台建議用邏輯字體:Serif, SansSerif, Monospaced, Dialog, DialogInput

常見問題與解決

1. 中文顯示亂碼/方塊

原因:JVM 找不到指定字體文件

解決:

  • 使用邏輯字體:new Font("SansSerif", Font.PLAIN, 24)
  • 或註冊物理字體:GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(Font.createFont(Font.TRUETYPE_FONT, new File("msjh.ttc")))
  • Docker 環境安裝字體包:apt-get install -y fontconfig fonts-noto-cjk

2. 透明背景 PNG 輸出變黑

原因:JPEG 不支援透明通道

解決:輸出格式用 png,且源圖類型用 BufferedImage.TYPE_INT_ARGB

3. 圖片讀取報錯:IIOException

  • 路徑錯誤或檔案不存在
  • 格式不支援(如 WebP 需額外插件)
  • 檔案損壞或非圖片檔

擴展:九大水印位置預設


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。生產環境需注意字體部署、透明通道處理、大圖記憶體管理等工程化問題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved