From e5c7830410af42286468767984ccd7c698ee14a1 Mon Sep 17 00:00:00 2001 From: cwchen <1048842385@qq.com> Date: Wed, 2 Apr 2025 14:13:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B0=B4=E5=8D=B0=E5=B7=A5=E5=85=B7=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../imgTool/utils/HighQualityWatermark.java | 323 ++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 src/main/java/com/bonus/imgTool/utils/HighQualityWatermark.java diff --git a/src/main/java/com/bonus/imgTool/utils/HighQualityWatermark.java b/src/main/java/com/bonus/imgTool/utils/HighQualityWatermark.java new file mode 100644 index 0000000..da52cbb --- /dev/null +++ b/src/main/java/com/bonus/imgTool/utils/HighQualityWatermark.java @@ -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 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> 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 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 textLines, int maxWidth) { + FontMetrics fm = g2d.getFontMetrics(); + return textLines.stream().allMatch(line -> fm.stringWidth(line) <= maxWidth); + } + + /** + * 处理所有行的换行 + */ + private static List> wrapAllLines(Graphics2D g2d, List textLines, int maxWidth) { + List> wrappedLines = new ArrayList<>(); + for (String line : textLines) { + wrappedLines.add(wrapSingleLine(g2d, line, maxWidth)); + } + return wrappedLines; + } + + /** + * 单行文本换行处理 + */ + private static List wrapSingleLine(Graphics2D g2d, String line, int maxWidth) { + List 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> 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> allWrappedLines) { + int maxWidth = 0; + FontMetrics fm = g2d.getFontMetrics(); + + for (List 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> allWrappedLines, + Point startPoint, int lineHeight, + String position, int imgWidth) { + int currentY = startPoint.y; + FontMetrics fm = g2d.getFontMetrics(); + + for (List 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 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 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(); + } + } +}