使用Java实现一个功能完整的图片水印工具

 更新时间:2026年02月04日 09:01:39   作者:J_liaty  
在日常开发中,我们经常需要给图片添加水印,比如版权保护、品牌展示等场景,所以本文将介绍一个功能完整的Java图片水印工具,支持文字水印和图片水印,需要的朋友可以参考下

前言

在日常开发中,我们经常需要给图片添加水印,比如版权保护、品牌展示等场景。本文将介绍一个功能完整的Java图片水印工具,支持文字水印图片水印,并提供多种位置布局、旋转角度、透明度调节等功能。

功能特性

  • ✅ 支持文字水印和图片水印两种模式
  • ✅ 五种水印位置:居中、对角线平铺、右下角、左上角、自定义坐标
  • ✅ 支持水印旋转(任意角度)
  • ✅ 支持透明度调节
  • ✅ 支持字体、颜色、大小自定义
  • ✅ 图片水印自动缩放(不超过原图1/5)
  • ✅ 抗锯齿处理,保证图片质量

项目结构

├── WatermarkConfig.java      # 水印配置类
└── ImageWatermarkUtil.java   # 水印工具类

一、WatermarkConfig 配置类

这个类用于封装水印的所有参数,通过链式调用可以方便地配置水印效果。

源代码

import lombok.Data;

import java.awt.*;

/**
 * 图片水印参数配置类
 */
@Data
public class WatermarkConfig {
    /** 水印文字内容 */
    private String text;

    /** 字体名称(默认宋体) */
    private String fontName = "SimSun";

    /** 字体大小(默认24px) */
    private int fontSize = 24;

    /** 字体颜色(默认灰色半透明) */
    private Color color = new Color(128, 128, 128, 100);

    /** 水印透明度(0.0-1.0,默认0.3) */
    private float alpha = 0.3f;

    /** 旋转角度(度数,默认-30度) */
    private int rotateAngle = 0;

    /** 水印位置类型 */
    private PositionType positionType = PositionType.DIAGONAL;

    /** X坐标偏移(当positionType为CUSTOM时使用) */
    private int x = 0;

    /** Y坐标偏移(当positionType为CUSTOM时使用) */
    private int y = 0;

    /** 平铺水印时的水平间距(默认200px) */
    private int horizontalSpacing = 200;

    /** 平铺水印时的垂直间距(默认150px) */
    private int verticalSpacing = 150;

    /** 水印图片路径(图片水印时使用) */
    private String watermarkImagePath;

    public enum PositionType {
        /** 居中 */
        CENTER,
        /** 对角线平铺 */
        DIAGONAL,
        /** 右下角 */
        BOTTOM_RIGHT,
        /** 左上角 */
        TOP_LEFT,
        /** 自定义坐标 */
        CUSTOM
    }
}

配置参数说明

参数类型默认值说明
textString-水印文字内容(文字水印必需)
fontNameStringSimSun字体名称
fontSizeint24字体大小(像素)
colorColor灰色半透明字体颜色
alphafloat0.3透明度(0.0-1.0)
rotateAngleint0旋转角度(度数)
positionTypePositionTypeDIAGONAL水印位置类型
xint0自定义X坐标(CUSTOM模式)
yint0自定义Y坐标(CUSTOM模式)
horizontalSpacingint200平铺水平间距
verticalSpacingint150平铺垂直间距
watermarkImagePathString-水印图片路径(图片水印必需)

二、ImageWatermarkUtil 工具类

这是核心工具类,提供了添加文字水印和图片水印的方法。

源代码

import com.sygt.access.config.WatermarkConfig;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

/**
 * 图片水印工具类
 * 支持文字水印和图片水印
 */
public class ImageWatermarkUtil {
    /**
     * 添加文字水印
     *
     * @param srcImagePath 源图片路径
     * @param destImagePath 目标图片路径
     * @param config 水印配置
     * @throws IOException IO异常
     */
    public static void addTextWatermark(String srcImagePath, String destImagePath,
                                        WatermarkConfig config) throws IOException {
        // 读取原始图片
        BufferedImage srcImage = ImageIO.read(new File(srcImagePath));

        // 创建Graphics2D对象
        Graphics2D g2d = srcImage.createGraphics();

        try {
            // 设置抗锯齿
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);

            // 设置字体
            Font font = new Font(config.getFontName(), Font.PLAIN, config.getFontSize());
            g2d.setFont(font);

            // 设置颜色和透明度
            Color color = config.getColor();
            g2d.setColor(new Color(color.getRed(), color.getGreen(),
                    color.getBlue(), (int)(color.getAlpha() * config.getAlpha())));

            // 根据位置类型绘制水印(旋转逻辑移到drawTextWatermark内部处理)
            drawTextWatermark(g2d, config, srcImage);

        } finally {
            g2d.dispose();
        }

        // 保存图片
        String format = destImagePath.substring(destImagePath.lastIndexOf(".") + 1);
        ImageIO.write(srcImage, format, new File(destImagePath));
    }

    /**
     * 添加图片水印
     *
     * @param srcImagePath 源图片路径
     * @param destImagePath 目标图片路径
     * @param config 水印配置
     * @throws IOException IO异常
     */
    public static void addImageWatermark(String srcImagePath, String destImagePath,
                                         WatermarkConfig config) throws IOException {
        BufferedImage srcImage = ImageIO.read(new File(srcImagePath));
        BufferedImage watermarkImage = ImageIO.read(new File(config.getWatermarkImagePath()));

        Graphics2D g2d = srcImage.createGraphics();

        try {
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);

            // 设置图片水印透明度
            AlphaComposite alphaComposite = AlphaComposite.getInstance(
                    AlphaComposite.SRC_OVER, config.getAlpha());
            g2d.setComposite(alphaComposite);

            // 绘制图片水印
            drawImageWatermark(g2d, watermarkImage, config, srcImage);

        } finally {
            g2d.dispose();
        }

        String format = destImagePath.substring(destImagePath.lastIndexOf(".") + 1);
        ImageIO.write(srcImage, format, new File(destImagePath));
    }

    /**
     * 绘制文字水印
     */
    private static void drawTextWatermark(Graphics2D g2d, WatermarkConfig config,
                                          BufferedImage srcImage) {
        String text = config.getText();
        FontMetrics fontMetrics = g2d.getFontMetrics();
        int textWidth = fontMetrics.stringWidth(text);
        int textHeight = fontMetrics.getHeight();

        int imageWidth = srcImage.getWidth();
        int imageHeight = srcImage.getHeight();
        float rotateAngle = config.getRotateAngle();

        switch (config.getPositionType()) {
            case CENTER:
                int centerX = (imageWidth - textWidth) / 2;
                int centerY = (imageHeight + textHeight) / 2;
                if (rotateAngle != 0) {
                    drawRotatedText(g2d, text, centerX + textWidth/2, centerY - textHeight/2,
                            rotateAngle, centerX, centerY);
                } else {
                    g2d.drawString(text, centerX, centerY);
                }
                break;

            case DIAGONAL:
                // 对角线平铺水印 - 全局旋转整个画布
                AffineTransform originalTransform = g2d.getTransform();
                if (rotateAngle != 0) {
                    g2d.rotate(Math.toRadians(rotateAngle));
                }

                try {
                    for (int x = -imageWidth; x < imageWidth * 2; x += config.getHorizontalSpacing()) {
                        for (int y = -imageHeight; y < imageHeight * 2; y += config.getVerticalSpacing()) {
                            g2d.drawString(text, x, y);
                        }
                    }
                } finally {
                    // 恢复原始变换
                    g2d.setTransform(originalTransform);
                }
                break;

            case BOTTOM_RIGHT:
                int margin = 20;
                int bottomRightX = imageWidth - textWidth - margin;
                int descent = fontMetrics.getDescent();
                int bottomRightY = imageHeight - descent - margin;

                if (rotateAngle != 0) {
                    // 以水印中心为旋转点
                    int centerXRotate = bottomRightX + textWidth / 2;
                    int centerYRotate = bottomRightY - textHeight / 2;
                    drawRotatedText(g2d, text, centerXRotate, centerYRotate,
                            rotateAngle, bottomRightX, bottomRightY);
                } else {
                    g2d.drawString(text, bottomRightX, bottomRightY);
                }
                break;

            case TOP_LEFT:
                int topLeftX = 20;
                int topLeftY = textHeight + 20;

                if (rotateAngle != 0) {
                    int centerXRotate = topLeftX + textWidth / 2;
                    int centerYRotate = topLeftY - textHeight / 2;
                    drawRotatedText(g2d, text, centerXRotate, centerYRotate,
                            rotateAngle, topLeftX, topLeftY);
                } else {
                    g2d.drawString(text, topLeftX, topLeftY);
                }
                break;

            case CUSTOM:
                int customX = config.getX();
                int customY = config.getY();

                if (rotateAngle != 0) {
                    int centerXRotate = customX + textWidth / 2;
                    int centerYRotate = customY - textHeight / 2;
                    drawRotatedText(g2d, text, centerXRotate, centerYRotate,
                            rotateAngle, customX, customY);
                } else {
                    g2d.drawString(text, customX, customY);
                }
                break;
        }
    }

    /**
     * 绘制旋转的文字
     */
    private static void drawRotatedText(Graphics2D g2d, String text,
                                        int rotateCenterX, int rotateCenterY,
                                        float rotateAngle, int drawX, int drawY) {
        // 保存原始变换状态
        AffineTransform originalTransform = g2d.getTransform();

        try {
            // 平移到旋转中心,旋转,再平移回去
            g2d.translate(rotateCenterX, rotateCenterY);
            g2d.rotate(Math.toRadians(rotateAngle));
            g2d.translate(-rotateCenterX, -rotateCenterY);

            // 绘制文字
            g2d.drawString(text, drawX, drawY);
        } finally {
            // 恢复原始变换
            g2d.setTransform(originalTransform);
        }
    }

    /**
     * 绘制图片水印
     */
    private static void drawImageWatermark(Graphics2D g2d, BufferedImage watermarkImage,
                                           WatermarkConfig config, BufferedImage srcImage) {
        int watermarkWidth = watermarkImage.getWidth();
        int watermarkHeight = watermarkImage.getHeight();

        // 调整水印大小(不超过原图的1/5)
        int maxWidth = srcImage.getWidth() / 5;
        int maxHeight = srcImage.getHeight() / 5;

        if (watermarkWidth > maxWidth || watermarkHeight > maxHeight) {
            double scale = Math.min((double)maxWidth / watermarkWidth,
                    (double)maxHeight / watermarkHeight);
            watermarkWidth = (int)(watermarkWidth * scale);
            watermarkHeight = (int)(watermarkHeight * scale);
        }

        // 缩放水印图片
        BufferedImage scaledWatermark = new BufferedImage(watermarkWidth, watermarkHeight,
                BufferedImage.TYPE_INT_ARGB);
        Graphics2D watermarkG2d = scaledWatermark.createGraphics();
        try {
            watermarkG2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            watermarkG2d.drawImage(watermarkImage, 0, 0, watermarkWidth, watermarkHeight, null);
        } finally {
            watermarkG2d.dispose();
        }

        float rotateAngle = config.getRotateAngle();

        // 绘制水印
        switch (config.getPositionType()) {
            case DIAGONAL:
                // 平铺图片水印 - 全局旋转整个画布
                AffineTransform originalTransform = g2d.getTransform();
                if (rotateAngle != 0) {
                    g2d.rotate(Math.toRadians(rotateAngle));
                }

                try {
                    for (int i = 0; i < srcImage.getWidth(); i += watermarkWidth + 100) {
                        for (int j = 0; j < srcImage.getHeight(); j += watermarkHeight + 100) {
                            g2d.drawImage(scaledWatermark, i, j, watermarkWidth, watermarkHeight, null);
                        }
                    }
                } finally {
                    g2d.setTransform(originalTransform);
                }
                return;

            default:
                // 计算位置
                int x = 0, y = 0;
                switch (config.getPositionType()) {
                    case CENTER:
                        x = (srcImage.getWidth() - watermarkWidth) / 2;
                        y = (srcImage.getHeight() - watermarkHeight) / 2;
                        break;
                    case BOTTOM_RIGHT:
                        x = srcImage.getWidth() - watermarkWidth - 20;
                        y = srcImage.getHeight() - watermarkHeight - 20;
                        break;
                    case TOP_LEFT:
                        x = 20;
                        y = 20;
                        break;
                    case CUSTOM:
                        x = config.getX();
                        y = config.getY();
                        break;
                }

                if (rotateAngle != 0) {
                    // 绘制旋转的图片水印
                    drawRotatedImage(g2d, scaledWatermark, x + watermarkWidth/2,
                            y + watermarkHeight/2, rotateAngle, x, y,
                            watermarkWidth, watermarkHeight);
                } else {
                    g2d.drawImage(scaledWatermark, x, y, watermarkWidth, watermarkHeight, null);
                }
                break;
        }
    }

    /**
     * 绘制旋转的图片
     */
    private static void drawRotatedImage(Graphics2D g2d, BufferedImage image,
                                         int rotateCenterX, int rotateCenterY,
                                         float rotateAngle, int drawX, int drawY,
                                         int width, int height) {
        // 保存原始变换状态
        AffineTransform originalTransform = g2d.getTransform();

        try {
            // 平移到旋转中心,旋转,再平移回去
            g2d.translate(rotateCenterX, rotateCenterY);
            g2d.rotate(Math.toRadians(rotateAngle));
            g2d.translate(-rotateCenterX, -rotateCenterY);

            // 绘制图片
            g2d.drawImage(image, drawX, drawY, width, height, null);
        } finally {
            // 恢复原始变换
            g2d.setTransform(originalTransform);
        }
    }
}

核心方法说明

1. addTextWatermark - 添加文字水印

public static void addTextWatermark(String srcImagePath, String destImagePath,
                                    WatermarkConfig config) throws IOException

参数说明:

  • srcImagePath: 原始图片路径
  • destImagePath: 输出图片路径
  • config: 水印配置对象

2. addImageWatermark - 添加图片水印

public static void addImageWatermark(String srcImagePath, String destImagePath,
                                     WatermarkConfig config) throws IOException

参数说明:

  • srcImagePath: 原始图片路径
  • destImagePath: 输出图片路径
  • config: 水印配置对象(需设置 watermarkImagePath)

三、使用示例

下面提供完整的使用示例,包含 main 方法测试。

package com.sygt.access.test;

import com.sygt.access.config.WatermarkConfig;
import com.sygt.access.utils.ImageWatermarkUtil;

import java.awt.Color;
import java.io.IOException;

/**
 * 水印工具测试类
 */
public class WatermarkTest {

    public static void main(String[] args) {
        // 原始图片路径
        String srcImagePath = "D:/images/source.jpg";
        
        // 输出图片路径
        String destImagePath = "D:/images/output.jpg";
        
        // 水印图片路径(图片水印使用)
        String watermarkImagePath = "D:/images/logo.png";

        try {
            // ========== 示例1:添加文字水印(对角线平铺) ==========
            System.out.println("示例1:添加文字水印(对角线平铺)");
            WatermarkConfig textConfig = new WatermarkConfig();
            textConfig.setText("版权所有 @2024");
            textConfig.setFontName("Microsoft YaHei");
            textConfig.setFontSize(30);
            textConfig.setColor(new Color(128, 128, 128, 150));
            textConfig.setAlpha(0.4f);
            textConfig.setRotateAngle(-30);
            textConfig.setPositionType(WatermarkConfig.PositionType.DIAGONAL);
            textConfig.setHorizontalSpacing(300);
            textConfig.setVerticalSpacing(200);
            
            ImageWatermarkUtil.addTextWatermark(srcImagePath, 
                "D:/images/output_text_diagonal.jpg", textConfig);
            System.out.println("文字水印添加完成!");

            // ========== 示例2:添加文字水印(右下角) ==========
            System.out.println("\n示例2:添加文字水印(右下角)");
            WatermarkConfig textConfig2 = new WatermarkConfig();
            textConfig2.setText("仅供内部使用");
            textConfig2.setFontName("SimHei");
            textConfig2.setFontSize(24);
            textConfig2.setColor(new Color(255, 0, 0, 200));
            textConfig2.setAlpha(0.8f);
            textConfig2.setRotateAngle(0);
            textConfig2.setPositionType(WatermarkConfig.PositionType.BOTTOM_RIGHT);
            
            ImageWatermarkUtil.addTextWatermark(srcImagePath, 
                "D:/images/output_text_bottomright.jpg", textConfig2);
            System.out.println("文字水印添加完成!");

            // ========== 示例3:添加文字水印(居中旋转) ==========
            System.out.println("\n示例3:添加文字水印(居中旋转)");
            WatermarkConfig textConfig3 = new WatermarkConfig();
            textConfig3.setText("机密文档");
            textConfig3.setFontName("Microsoft YaHei");
            textConfig3.setFontSize(60);
            textConfig3.setColor(new Color(255, 0, 0, 100));
            textConfig3.setAlpha(0.5f);
            textConfig3.setRotateAngle(45);
            textConfig3.setPositionType(WatermarkConfig.PositionType.CENTER);
            
            ImageWatermarkUtil.addTextWatermark(srcImagePath, 
                "D:/images/output_text_center.jpg", textConfig3);
            System.out.println("文字水印添加完成!");

            // ========== 示例4:添加图片水印(右下角) ==========
            System.out.println("\n示例4:添加图片水印(右下角)");
            WatermarkConfig imageConfig = new WatermarkConfig();
            imageConfig.setWatermarkImagePath(watermarkImagePath);
            imageConfig.setAlpha(0.7f);
            imageConfig.setRotateAngle(0);
            imageConfig.setPositionType(WatermarkConfig.PositionType.BOTTOM_RIGHT);
            
            ImageWatermarkUtil.addImageWatermark(srcImagePath, 
                "D:/images/output_image_bottomright.jpg", imageConfig);
            System.out.println("图片水印添加完成!");

            // ========== 示例5:添加图片水印(对角线平铺) ==========
            System.out.println("\n示例5:添加图片水印(对角线平铺)");
            WatermarkConfig imageConfig2 = new WatermarkConfig();
            imageConfig2.setWatermarkImagePath(watermarkImagePath);
            imageConfig2.setAlpha(0.2f);
            imageConfig2.setRotateAngle(-15);
            imageConfig2.setPositionType(WatermarkConfig.PositionType.DIAGONAL);
            
            ImageWatermarkUtil.addImageWatermark(srcImagePath, 
                "D:/images/output_image_diagonal.jpg", imageConfig2);
            System.out.println("图片水印添加完成!");

            // ========== 示例6:添加文字水印(自定义坐标) ==========
            System.out.println("\n示例6:添加文字水印(自定义坐标)");
            WatermarkConfig textConfig4 = new WatermarkConfig();
            textConfig4.setText("自定义位置水印");
            textConfig4.setFontName("Microsoft YaHei");
            textConfig4.setFontSize(20);
            textConfig4.setColor(new Color(0, 0, 255, 180));
            textConfig4.setAlpha(0.7f);
            textConfig4.setRotateAngle(0);
            textConfig4.setPositionType(WatermarkConfig.PositionType.CUSTOM);
            textConfig4.setX(100);
            textConfig4.setY(100);
            
            ImageWatermarkUtil.addTextWatermark(srcImagePath, 
                "D:/images/output_text_custom.jpg", textConfig4);
            System.out.println("文字水印添加完成!");

            System.out.println("\n所有水印添加任务完成!");

        } catch (IOException e) {
            System.err.println("水印添加失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
}

四、效果展示

1. 文字水印效果

位置类型效果说明
对角线平铺水印文字按指定角度和间距平铺整个图片
右下角单个水印位于图片右下角
左上角单个水印位于图片左上角
居中单个水印位于图片中央
自定义坐标水印位于指定的X、Y坐标位置

2. 图片水印效果

位置类型效果说明
对角线平铺水印图片自动缩放后平铺整个图片
右下角单个水印位于图片右下角
左上角单个水印位于图片左上角
居中单个水印位于图片中央
自定义坐标水印位于指定的X、Y坐标位置

五、技术要点解析

1. 抗锯齿处理

g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);

开启抗锯齿可以避免文字边缘出现锯齿,提高水印的清晰度。

2. 透明度控制

对于文字水印,通过Color的alpha值控制透明度:

g2d.setColor(new Color(color.getRed(), color.getGreen(),
        color.getBlue(), (int)(color.getAlpha() * config.getAlpha())));

对于图片水印,使用AlphaComposite控制透明度:

AlphaComposite alphaComposite = AlphaComposite.getInstance(
        AlphaComposite.SRC_OVER, config.getAlpha());
g2d.setComposite(alphaComposite);

3. 旋转实现

使用AffineTransform实现旋转,核心逻辑:

  1. 保存原始变换状态
  2. 平移到旋转中心
  3. 执行旋转
  4. 平移回原位置
  5. 绘制内容
  6. 恢复原始变换状态
AffineTransform originalTransform = g2d.getTransform();
try {
    g2d.translate(rotateCenterX, rotateCenterY);
    g2d.rotate(Math.toRadians(rotateAngle));
    g2d.translate(-rotateCenterX, -rotateCenterY);
    g2d.drawString(text, drawX, drawY);
} finally {
    g2d.setTransform(originalTransform);
}

4. 图片自动缩放

图片水印会自动缩放到不超过原图的1/5,避免水印过大影响原图:

int maxWidth = srcImage.getWidth() / 5;
int maxHeight = srcImage.getHeight() / 5;

if (watermarkWidth > maxWidth || watermarkHeight > maxHeight) {
    double scale = Math.min((double)maxWidth / watermarkWidth,
            (double)maxHeight / watermarkHeight);
    watermarkWidth = (int)(watermarkWidth * scale);
    watermarkHeight = (int)(watermarkHeight * scale);
}

六、常见问题

Q1:中文乱码怎么办?

确保字体名称正确,可以使用系统已安装的中文字体:

config.setFontName("Microsoft YaHei");  // 微软雅黑
config.setFontName("SimHei");           // 黑体
config.setFontName("SimSun");           // 宋体

Q2:如何调整水印密度?

对于平铺水印,调整水平和垂直间距:

config.setHorizontalSpacing(300);  // 水平间距
config.setVerticalSpacing(200);    // 垂直间距

Q3:支持的图片格式有哪些?

支持所有ImageIO支持的格式:JPG、PNG、BMP、GIF等。

Q4:如何批量处理图片?

遍历文件目录,对每张图片调用水印方法即可:

File folder = new File("D:/images");
for (File file : folder.listFiles()) {
    if (file.getName().endsWith(".jpg")) {
        ImageWatermarkUtil.addTextWatermark(
            file.getPath(), 
            "D:/output/" + file.getName(), 
            config
        );
    }
}

七、依赖说明

本工具仅使用JDK标准库,无需额外依赖。

如果使用Lombok简化代码,需要添加Lombok依赖:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
    <scope>provided</scope>
</dependency>

如果不使用Lombok,需要为WatermarkConfig类添加getter/setter方法。

八、总结

本文介绍了一个功能完整的Java图片水印工具,具有以下特点:

  1. 功能全面:支持文字和图片水印,多种位置布局
  2. 灵活配置:通过配置类轻松定制水印效果
  3. 代码清晰:结构清晰,注释完整,易于理解和扩展
  4. 即插即用:无需复杂配置,直接调用即可使用

以上就是使用Java实现一个功能完整的图片水印工具的详细内容,更多关于Java图片水印工具的资料请关注脚本之家其它相关文章!

相关文章

最新评论