Java图片加水印 实战demo
/**
* 图片水印工具类
*/
@Slf4j
@Component
public class WatermarkUtils {
@Autowired
private IStWatermarkManageService stWatermarkManageService;
@Autowired
private RedisCache redisCache;
/**
* 给图片添加水印
*
* @param file 需要加水印的图片文件,支持 File、MultipartFile、InputStream 类型
* @param isCompress 图片压缩比例,true=大压缩 false=微压缩
* @return 返回图片的输入流
* @throws IOException
*/
public InputStream addWatermark(Object file, Boolean isCompress) throws Exception {
// 获取水印名称
String watermarkName = getWatermarkName();
// 字体
String fontName = "WenQuanYi Zen Hei Mono"; // 文泉驿等宽正黑
// 创建文件输入流
try (InputStream inputStream = getInputStream(file)) {
// 这里添加了对输入流的格式检查,确保是图片格式文件
if (inputStream == null) {
throw new IOException("无法读取输入流,文件可能为空或格式不正确");
}
// 读取图片使用ImageIO
BufferedImage image = ImageIO.read(inputStream);
if (image == null) {
throw new IOException("无法加载图片,图片格式可能不受支持");
}
// 获取图片宽度和高度
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
// 创建一个新的图片对象,带有背景(使用白色或中性色背景)
// 这里只对PNG图片处理时使用透明背景,其它格式不需要额外添加背景
BufferedImage backgroundImage = image;
// 判断图片格式是否是PNG,如果是PNG,才添加透明背景
String formatName = getFileExtension(file); // 获取文件扩展名(如 PNG、JPEG)
if ("png".equalsIgnoreCase(formatName)) {
backgroundImage = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB); // 使用 BufferedImage.TYPE_INT_ARGB 类型,支持透明度
Graphics2D graphics = backgroundImage.createGraphics();
// 设置交替背景颜色的网格
// 根据图片宽度动态计算 gridSize,保持合适的比例
int minGridSize = 30; // 最小网格大小
int maxGridSize = 100; // 最大网格大小
int gridSize = Math.max(minGridSize, Math.min(maxGridSize, imageWidth / 15));
// 计算每张图片的网格并填充
for (int y = 0; y < imageHeight; y += gridSize) {
for (int x = 0; x < imageWidth; x += gridSize) {
// 计算当前格子宽度和高度,防止最后一行或最后一列超出边界
int cellWidth = (x + gridSize > imageWidth) ? imageWidth - x : gridSize;
int cellHeight = (y + gridSize > imageHeight) ? imageHeight - y : gridSize;
// 交替填充浅灰色和浅黑色
Color fillColor = ((x / gridSize + y / gridSize) % 2 == 0) ? new Color(230, 230, 230) : new Color(190, 190, 190);
graphics.setColor(fillColor);
graphics.fillRect(x, y, cellWidth, cellHeight); // 使用固定的格子大小填充每个格子
}
}
// 绘制原始图片到背景上(如果原始图片有透明部分,可以保留)
graphics.drawImage(image, 0, 0, null);
graphics.dispose(); // 释放Graphics2D资源
}
// 设置水印的字体和样式
Font font = new Font(fontName, Font.PLAIN, 50); // 动态计算的字体大小
Graphics2D graphics = backgroundImage.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// 计算水印的中心位置
FontMetrics metrics = graphics.getFontMetrics(font);
int textWidth = metrics.stringWidth(watermarkName);
int textHeight = metrics.getHeight();
int x = (imageWidth - textWidth) / 2; // 水平居中
int y = (imageHeight - textHeight) / 2 + metrics.getAscent(); // 垂直居中,调整y值以确保文字垂直居中
// 水印透明度和旋转角度
float opacity = 0.2f; // 水印透明度
int shadowOffset = 5; // 阴影偏移
float rotationAngle = -25f; // 逆时针旋转角度
// 设置水印字体和样式
graphics.setFont(font);
// 逆时针旋转Graphics2D对象
graphics.rotate(rotationAngle, x, y);
// 先绘制阴影效果(稍微偏移和透明度较低的灰色)
graphics.setColor(new Color(0, 0, 0, 50)); // 阴影色,透明度为50
graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
graphics.drawString(watermarkName, x + shadowOffset, y + shadowOffset);
// 再绘制水印文字
graphics.setColor(Color.WHITE); // 水印颜色为白色
graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity));
graphics.drawString(watermarkName, x, y);
// 释放资源
graphics.dispose();
// TODO 访问该地址复制源码: https://stjl.xyz
// 返回图片的输入流
return new ByteArrayInputStream(baos.toByteArray());
} catch (IOException e) {
throw new IOException("处理文件时发生错误: " + e.getMessage(), e);
}
}
/**
* 获取图片格式(PNG/JPEG等)
*
* @param file 图片文件
* @return 图片格式
* @throws IOException
*/
private String getImageFormat(Object file) throws IOException {
try (InputStream inputStream = getInputStream(file)) {
BufferedImage image = ImageIO.read(inputStream);
String formatName = null;
if (image != null) {
formatName = "png"; // 默认PNG
Iterator<ImageReader> readers = ImageIO.getImageReaders(image);
if (readers.hasNext()) {
ImageReader reader = readers.next();
formatName = reader.getFormatName();
}
}
return formatName != null ? formatName.toLowerCase() : "png"; // 默认返回"png"
}
}
/**
* 获取水印名称
* 如果缓存中存在则直接返回,如果缓存中不存在则从数据库中查询并存入缓存。
*
* @return 水印名称
*/
public String getWatermarkName() {
// 尝试从缓存中获取水印名称
String watermarkName = redisCache.getCacheObject(ConstantUtils.WATERMARK_NAME_KEY);
if (watermarkName == null) {
// 缓存中不存在,从数据库中获取水印名称
watermarkName = stWatermarkManageService.getNameOne();
// 将水印名称存入缓存,设置为永久有效
redisCache.setCacheObject(ConstantUtils.WATERMARK_NAME_KEY, watermarkName);
}
return watermarkName;
}
/**
* 获取输入流的方法,支持不同类型的文件输入
*
* @param file 支持 File、MultipartFile、InputStream 类型
* @return 输入流
* @throws IOException 如果无法处理文件
*/
private static InputStream getInputStream(Object file) throws IOException {
if (file instanceof File) {
return new FileInputStream((File) file);
} else if (file instanceof MultipartFile) {
return ((MultipartFile) file).getInputStream();
} else if (file instanceof InputStream) {
return (InputStream) file;
} else {
throw new IllegalArgumentException("不支持的文件类型");
}
}
/**
* 获取文件扩展名
*
* @param file 文件对象
* @return 文件扩展名
*/
private String getFileExtension(Object file) {
String fileName = null;
if (file instanceof File) {
fileName = ((File) file).getName();
} else if (file instanceof MultipartFile) {
fileName = ((MultipartFile) file).getOriginalFilename();
} else if (file instanceof InputStream) {
// 如果是 InputStream,暂时无法直接获取文件扩展名,可以尝试通过其他方式传递扩展名
throw new IllegalArgumentException("无法通过 InputStream 获取文件扩展名");
}
// 从文件名中提取扩展名(例如 .png 或 .jpg)
if (fileName != null) {
int index = fileName.lastIndexOf('.');
if (index > 0) {
return fileName.substring(index + 1);
}
}
return "";
}
// 水印配置类
public static class Watermark {
private String text; // 水印文字
private float x; // 水印X位置
private float y; // 水印Y位置
private Color color; // 水印颜色
private float opacity; // 透明度
private String fontName; // 字体名称
private int fontSize; // 字体大小
private float angle; // 倾斜度(度数)
public Watermark(String text, float x, float y, Color color, float opacity, String fontName, int fontSize, float angle) {
this.text = text;
this.x = x;
this.y = y;
this.color = color;
this.opacity = opacity;
this.fontName = fontName;
this.fontSize = fontSize;
this.angle = angle;
}
public String getText() {
return text;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
public Color getColor() {
return color;
}
public float getOpacity() {
return opacity;
}
public String getFontName() {
return fontName;
}
public int getFontSize() {
return fontSize;
}
public float getAngle() {
return angle;
}
}
}
捡图