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

Java 數字水印技術實現 (基於 AlphaComposite 透明度混合)

     編輯:Java 相關知識

本文詳細講解使用 Java 2D API 實現可見數字水印,核心利用 AlphaComposite 類實現圖層疊加透明度控制。支援文字水印與圖片水印,提供完整可運行代碼與參數詳細說明。

核心類:AlphaComposite

AlphaComposite 實現 Porter-Duff 混合規則,定義源圖與目標圖像素如何混合。

關鍵方法


AlphaComposite.getInstance(int rule, float alpha)
  • rule:混合規則,常用 AlphaComposite.SRC_OVER (源覆蓋目標)
  • alpha:0.0f~1.0f,源圖透明度乘數

SRC_OVER 混合公式


Ar = As + Ad * (1 - As)
Cr = Cs + Cd * (1 - As)
// Ar: 結果 Alpha, As: 源 Alpha, Ad: 目標 Alpha
// Cr: 結果顏色, Cs: 源顏色, Cd: 目標顏色

完整水印工具類


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

public class WatermarkUtil {

    /**
     * 添加文字水印
     * @param srcPath       源圖片路徑
     * @param text          水印文字
     * @param fontName      字體名稱 (如 "微軟雅黑", "SansSerif")
     * @param fontStyle     字體樣式 (Font.PLAIN/BOLD/ITALIC)
     * @param fontSize      字體大小
     * @param color         字體顏色
     * @param alpha         透明度 0.0f-1.0f
     * @param x             X 座標 (文字基線左邊)
     * @param y             Y 座標 (文字基線底部)
     * @param outputPath    輸出路徑
     * @param formatName    輸出格式 (jpg, png)
     */
    public static void addTextWatermark(String srcPath, String text,
            String fontName, int fontStyle, int fontSize,
            Color color, float alpha, int x, int y,
            String outputPath, String formatName) throws IOException {
        
        BufferedImage src = ImageIO.read(new File(srcPath));
        Graphics2D g2d = src.createGraphics();
        
        // 抗鋸齒優化
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        
        // 字體設置
        Font font = new Font(fontName, fontStyle, fontSize);
        g2d.setFont(font);
        
        // 透明度混合模式
        AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
        g2d.setComposite(composite);
        
        // 繪製文字
        g2d.setColor(color);
        g2d.drawString(text, x, y);
        
        g2d.dispose();
        ImageIO.write(src, formatName, new File(outputPath));
    }

    /**
     * 添加圖片水印
     * @param srcPath       源圖片路徑
     * @param watermarkPath 水印圖片路徑 (支援透明 PNG)
     * @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 srcPath, String watermarkPath,
            float alpha, int x, int y, int width, int height,
            String outputPath, String formatName) throws IOException {
        
        BufferedImage src = ImageIO.read(new File(srcPath));
        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 = src.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(src, formatName, new File(outputPath));
    }

    /**
     * 圖片縮放 (雙線性插值)
     */
    private static BufferedImage resizeImage(BufferedImage src, int width, int height) {
        BufferedImage dest = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = dest.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.drawImage(src, 0, 0, width, height, null);
        g2d.dispose();
        return dest;
    }

    /**
     * 九大預設位置計算
     */
    public static int[] calcPosition(Position pos, int imgW, int imgH, int wmW, int wmH, int margin) {
        return switch (pos) {
            case TOP_LEFT -> new int[]{margin, margin + wmH};  // 文字基線
            case TOP_CENTER -> new int[]{(imgW - wmW) / 2, margin + wmH};
            case TOP_RIGHT -> new int[]{imgW - wmW - margin, margin + wmH};
            case MIDDLE_LEFT -> new int[]{margin, (imgH + wmH) / 2};
            case CENTER -> new int[]{(imgW - wmW) / 2, (imgH + wmH) / 2};
            case MIDDLE_RIGHT -> new int[]{imgW - wmW - margin, (imgH + wmH) / 2};
            case BOTTOM_LEFT -> new int[]{margin, imgH - margin};
            case BOTTOM_CENTER -> new int[]{(imgW - wmW) / 2, imgH - margin};
            case BOTTOM_RIGHT -> new int[]{imgW - wmW - margin, imgH - margin};
        };
    }

    public enum Position {
        TOP_LEFT, TOP_CENTER, TOP_RIGHT,
        MIDDLE_LEFT, CENTER, MIDDLE_RIGHT,
        BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT
    }

    // 測試主方法
    public static void main(String[] args) {
        try {
            String src = "F:/photo.jpg";
            String logo = "F:/logo.png";  // 建議透明 PNG
            String out1 = "F:/watermark_text.jpg";
            String out2 = "F:/watermark_image.jpg";
            
            // 文字水印:右下角,紅色,40% 透明
            addTextWatermark(src, "版權所有 @MyCompany", 
                "微軟雅黑", Font.BOLD, 36,
                Color.RED, 0.4f, 50, 50, out1, "jpg");
            
            // 圖片水印:右下角,100x100,50% 透明
            addImageWatermark(src, logo, 0.5f, 
                800, 600, 100, 100, out2, "jpg");
            
            System.out.println("水印生成完成!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

參數詳細說明

參數類型範圍/建議值說明
alphafloat0.1f~0.5f0.3-0.4 較適中,過高遮擋內容,過低難識別
fontNameString系統字體跨平台建議 "SansSerif", "Serif", "Monospaced" 或註冊 TTF
colorColorColor.WHITE/BLACK/RED 等根據背景選擇對比色
x, yint像素座標文字基線 / 圖片左上角
formatNameString"jpg", "png"需透明背景用 png

常見問題與解決

1. 中文顯示為方塊/亂碼

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

解決:

  • 使用邏輯字體:new Font("SansSerif", Font.PLAIN, 24)
  • 動態註冊字體:
    
    Font font = Font.createFont(Font.TRUETYPE_FONT, new File("msyh.ttc"));
    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    ge.registerFont(font);
    // 之後可用 "Microsoft YaHei"
    
  • Docker/Linux 環境安裝中文字體:
    
    # Debian/Ubuntu
    apt-get update && apt-get install -y fontconfig fonts-noto-cjk
    # CentOS
    yum install -y fontconfig google-noto-cjk-fonts
    fc-cache -fv
    

2. PNG 透明背景變黑

原因:JPEG 不支援 Alpha 通道

解決:輸出格式用 png,讀取時確保 BufferedImage.TYPE_INT_ARGB

3. 大圖記憶體溢出 (OOM)

原因:ImageIO.read 將整圖載入堆內存

解決:

  • 增加堆內存:-Xmx2g
  • 分塊處理 (ImageReader + ImageReadParam)
  • 使用 TwelveMonkeys ImageIO 插件優化

進階:隱形水印 (不可見)

本文實現的是可見水印。不可見水印需頻域嵌入:

  • DCT (離散餘弦變換) 域嵌入:JPEG 壓縮殘留係數中隱藏信息
  • DWT (小波變換) 域嵌入:更魯棒
  • 空域 LSB (最低有效位) 嵌入:簡單但易被壓縮破壞

推薦開源庫:Java-Watermark、Steganography

總結

Java 2D API 配合 AlphaComposite.SRC_OVER 可輕鬆實現靈活的可見水印。關鍵點:正確設置字體、透明度、座標、輸出格式。生產環境需解決字體部署、大圖記憶體、高併發處理等工程化問題。不可見水印屬於資訊隱藏範疇,算法複雜度較高,建議使用成熟庫。

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