使用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
}
}
配置参数说明
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| text | String | - | 水印文字内容(文字水印必需) |
| fontName | String | SimSun | 字体名称 |
| fontSize | int | 24 | 字体大小(像素) |
| color | Color | 灰色半透明 | 字体颜色 |
| alpha | float | 0.3 | 透明度(0.0-1.0) |
| rotateAngle | int | 0 | 旋转角度(度数) |
| positionType | PositionType | DIAGONAL | 水印位置类型 |
| x | int | 0 | 自定义X坐标(CUSTOM模式) |
| y | int | 0 | 自定义Y坐标(CUSTOM模式) |
| horizontalSpacing | int | 200 | 平铺水平间距 |
| verticalSpacing | int | 150 | 平铺垂直间距 |
| watermarkImagePath | String | - | 水印图片路径(图片水印必需) |
二、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实现旋转,核心逻辑:
- 保存原始变换状态
- 平移到旋转中心
- 执行旋转
- 平移回原位置
- 绘制内容
- 恢复原始变换状态
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图片水印工具,具有以下特点:
- 功能全面:支持文字和图片水印,多种位置布局
- 灵活配置:通过配置类轻松定制水印效果
- 代码清晰:结构清晰,注释完整,易于理解和扩展
- 即插即用:无需复杂配置,直接调用即可使用
以上就是使用Java实现一个功能完整的图片水印工具的详细内容,更多关于Java图片水印工具的资料请关注脚本之家其它相关文章!
相关文章
redis之基于SpringBoot实现Redis stream实时流事件处理方式
这篇文章主要介绍了redis之基于SpringBoot实现Redis stream实时流事件处理方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2023-06-06
reactor-logback的AsyncAppender执行流程源码解读
这篇文章主要为大家介绍了reactor-logback的AsyncAppender执行流程源码解读,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2023-12-12
Java SpringMVC框架开发之数据导出Excel文件格式实例详解
这篇文章主要介绍了Java基础开发之数据导出Excel文件格式实例详解,需要的朋友可以参考下2020-02-02


最新评论