水印工具类
This commit is contained in:
parent
3ab1312f8e
commit
e5c7830410
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue