水印工具类

This commit is contained in:
cwchen 2025-04-02 14:13:44 +08:00
parent 3ab1312f8e
commit e5c7830410
1 changed files with 323 additions and 0 deletions

View File

@ -0,0 +1,323 @@
package com.bonus.imgTool.utils;
import javax.imageio.*;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.FileImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @className:HighQualityWatermark
* @author:cwchen
* @date:2025-04-02-14:04
* @version:1.0
* @description:
*/
public class HighQualityWatermark {
private static final String DEFAULT_FONT_NAME = "Microsoft YaHei";
private static final int DEFAULT_FONT_STYLE = Font.BOLD;
private static final Color DEFAULT_COLOR = new Color(195, 32, 32, 255);
private static final int MIN_FONT_SIZE = 10;
private static final int MAX_FONT_SIZE = 72;
private static final float MIN_OPACITY = 0.3f;
private static final float MAX_OPACITY = 0.9f;
private static final float DEFAULT_QUALITY = 1.0f; // 最高质量(无损)
/**
* 添加高质量多行水印
* @param sourceImagePath 源图片路径
* @param targetImagePath 目标图片路径
* @param textLines 多行水印文本
* @param position 水印位置
* @param opacity 透明度
* @param fontName 字体名称
*/
public static void addHighQualityWatermark(String sourceImagePath, String targetImagePath,
List<String> textLines, String position,
float opacity, String fontName) throws Exception {
// 参数校验
if (opacity < MIN_OPACITY || opacity > MAX_OPACITY) {
opacity = 0.7f;
}
// 读取源图片
BufferedImage sourceImage = ImageIO.read(new File(sourceImagePath));
int imageWidth = sourceImage.getWidth();
int imageHeight = sourceImage.getHeight();
// 创建Graphics2D对象
Graphics2D g2d = sourceImage.createGraphics();
// 设置最高质量的渲染参数
setHighestQuality(g2d);
// 计算最大允许宽度图片宽度的四分之三
// int maxAllowedWidth = imageWidth / 2;
int maxAllowedWidth = (int) (imageWidth * 3.0 / 4);
// 确定最佳字体大小
Font optimalFont = findOptimalFontSize(g2d, textLines, maxAllowedWidth, imageWidth, imageHeight, fontName);
g2d.setFont(optimalFont);
FontMetrics fm = g2d.getFontMetrics();
// 处理每行文本的换行
List<List<String>> wrappedLines = wrapAllLines(g2d, textLines, maxAllowedWidth);
// 计算水印总高度和最大行宽度
int lineHeight = fm.getHeight();
int totalHeight = calculateTotalHeight(wrappedLines, lineHeight);
int maxLineWidth = findMaxLineWidth(g2d, wrappedLines);
// 设置水印颜色和透明度
Color watermarkColor = new Color(DEFAULT_COLOR.getRed(), DEFAULT_COLOR.getGreen(),
DEFAULT_COLOR.getBlue(), (int)(opacity * 255));
g2d.setColor(watermarkColor);
// 计算水印起始位置
Point startPoint = calculateWatermarkPosition(position, imageWidth, imageHeight,
maxLineWidth, totalHeight, fm);
// 绘制水印
drawWatermarkLines(g2d, wrappedLines, startPoint, lineHeight, position, imageWidth);
// 释放资源
g2d.dispose();
// 高质量保存图片
saveWithHighQuality(sourceImage, targetImagePath);
}
/**
* 设置最高质量的渲染参数
*/
private static void setHighestQuality(Graphics2D g2d) {
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);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
}
/**
* 寻找最佳字体大小
*/
private static Font findOptimalFontSize(Graphics2D g2d, List<String> textLines,
int maxAllowedWidth, int imgWidth,
int imgHeight, String fontName) {
// 基于图片对角线长度计算初始字体大小
double diagonal = Math.sqrt(imgWidth * imgWidth + imgHeight * imgHeight);
int initialSize = (int)(diagonal * 0.03);
initialSize = Math.max(MIN_FONT_SIZE, Math.min(initialSize, MAX_FONT_SIZE));
Font testFont = new Font(fontName, DEFAULT_FONT_STYLE, initialSize);
g2d.setFont(testFont);
/*// 检查所有行是否都适合
boolean allFit = checkAllLinesFit(g2d, textLines, maxAllowedWidth);
// 如果不适合逐步减小字体大小
int currentSize = initialSize;
while (!allFit && currentSize > MIN_FONT_SIZE) {
currentSize--;
testFont = new Font(fontName, DEFAULT_FONT_STYLE, currentSize);
g2d.setFont(testFont);
allFit = checkAllLinesFit(g2d, textLines, maxAllowedWidth);
}*/
return testFont;
}
/**
* 检查所有行是否适合最大宽度
*/
private static boolean checkAllLinesFit(Graphics2D g2d, List<String> textLines, int maxWidth) {
FontMetrics fm = g2d.getFontMetrics();
return textLines.stream().allMatch(line -> fm.stringWidth(line) <= maxWidth);
}
/**
* 处理所有行的换行
*/
private static List<List<String>> wrapAllLines(Graphics2D g2d, List<String> textLines, int maxWidth) {
List<List<String>> wrappedLines = new ArrayList<>();
for (String line : textLines) {
wrappedLines.add(wrapSingleLine(g2d, line, maxWidth));
}
return wrappedLines;
}
/**
* 单行文本换行处理
*/
private static List<String> wrapSingleLine(Graphics2D g2d, String line, int maxWidth) {
List<String> wrapped = new ArrayList<>();
FontMetrics fm = g2d.getFontMetrics();
// 如果整行宽度小于最大宽度直接返回
if (fm.stringWidth(line) <= maxWidth) {
wrapped.add(line);
return wrapped;
}
// 按空格分割单词
String[] words = line.split("@@");
StringBuilder currentLine = new StringBuilder(words[0]);
for (int i = 1; i < words.length; i++) {
String testLine = currentLine + " " + words[i];
if (fm.stringWidth(testLine) <= maxWidth) {
currentLine.append(" ").append(words[i]);
} else {
wrapped.add(currentLine.toString());
currentLine = new StringBuilder(words[i]);
}
}
wrapped.add(currentLine.toString());
return wrapped;
}
/**
* 计算水印总高度
*/
private static int calculateTotalHeight(List<List<String>> allWrappedLines, int lineHeight) {
int totalLines = allWrappedLines.stream().mapToInt(List::size).sum();
// 总行数 * 行高 + (段落数-1) * 行高/2 (段落间距)
return totalLines * lineHeight + (allWrappedLines.size() - 1) * (lineHeight / 2);
}
/**
* 查找最大行宽度
*/
private static int findMaxLineWidth(Graphics2D g2d, List<List<String>> allWrappedLines) {
int maxWidth = 0;
FontMetrics fm = g2d.getFontMetrics();
for (List<String> lines : allWrappedLines) {
for (String line : lines) {
int width = fm.stringWidth(line);
if (width > maxWidth) {
maxWidth = width;
}
}
}
return maxWidth;
}
/**
* 计算水印位置
*/
private static Point calculateWatermarkPosition(String position, int imgWidth, int imgHeight,
int maxLineWidth, int totalHeight,
FontMetrics fm) {
int margin = (int)(Math.min(imgWidth, imgHeight) * 0.05); // 5%边距
int x = 0;
int y = 0;
switch (position.toLowerCase()) {
case "center":
x = (imgWidth - maxLineWidth) / 2;
y = (imgHeight - totalHeight) / 2 + fm.getAscent();
break;
case "top-left":
x = margin;
y = margin + fm.getAscent();
break;
case "top-right":
x = imgWidth - maxLineWidth - margin;
y = margin + fm.getAscent();
break;
case "bottom-left":
x = margin;
y = imgHeight - totalHeight - margin + fm.getAscent();
break;
case "bottom-right":
x = imgWidth - maxLineWidth - margin;
y = imgHeight - totalHeight - margin + fm.getAscent();
break;
default: // 默认居中
x = (imgWidth - maxLineWidth) / 2;
y = (imgHeight - totalHeight) / 2 + fm.getAscent();
}
return new Point(x, y);
}
/**
* 绘制水印文本
*/
private static void drawWatermarkLines(Graphics2D g2d, List<List<String>> allWrappedLines,
Point startPoint, int lineHeight,
String position, int imgWidth) {
int currentY = startPoint.y;
FontMetrics fm = g2d.getFontMetrics();
for (List<String> lines : allWrappedLines) {
for (String line : lines) {
int lineWidth = fm.stringWidth(line);
int x = startPoint.x;
// 如果是居中位置每行单独计算x坐标
if ("center".equalsIgnoreCase(position)) {
x = (imgWidth - lineWidth) / 2;
}
g2d.drawString(line, x, currentY);
currentY += lineHeight;
}
// 段落间距多行组之间的额外间距
currentY += lineHeight / 8;
}
}
/**
* 高质量保存JPG图片无损或接近无损
*/
private static void saveWithHighQuality(BufferedImage image, String filePath) throws Exception {
String formatName = filePath.substring(filePath.lastIndexOf(".") + 1).toLowerCase();
if ("jpg".equals(formatName) || "jpeg".equals(formatName)) {
// JPG特殊处理以保证高质量
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
ImageWriter writer = writers.next();
// 配置JPEG编码参数
JPEGImageWriteParam jpegParams = (JPEGImageWriteParam) writer.getDefaultWriteParam();
jpegParams.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT);
jpegParams.setCompressionQuality(DEFAULT_QUALITY); // 1.0 = 最高质量
// 写入文件
try (FileImageOutputStream output = new FileImageOutputStream(new File(filePath))) {
writer.setOutput(output);
writer.write(null, new IIOImage(image, null, null), jpegParams);
}
writer.dispose();
} else {
// 其他格式直接保存
ImageIO.write(image, formatName, new File(filePath));
}
}
public static void main(String[] args) {
try {
// 准备多行水印文本
List<String> watermarkLines = new ArrayList<>();
watermarkLines.add("2024-05-12");
String proName = "广东电网直流背靠背东莞工程大湾区南粤直流背靠背工程南粤±300kV背靠背换流站工程/新增/广东电网直流背靠背东莞工程大湾区南通道直流背靠背工程110kV环保城输变电工程电缆部分";
watermarkLines.add(proName.replaceAll("(.{18})", "$1@@"));
watermarkLines.add("安全违章");
watermarkLines.add("违章照片");
String localPath = "L:\\其他\\测试图片\\5.jpg";
String outPath = "L:\\新建文件夹\\output.jpg";
// 添加高质量水印
addHighQualityWatermark(localPath, outPath,
watermarkLines, "bottom-left",
0.7f, "Microsoft YaHei");
System.out.println("高质量水印添加完成,图片已无损输出");
} catch (Exception e) {
e.printStackTrace();
}
}
}