|
...
|
...
|
@@ -48,109 +48,147 @@ public class MultiTextImageGenerator { |
|
|
|
* 背景图分为顶部 logo、中部纯色、底部 logo 三部分。
|
|
|
|
* 中间内容区根据数据动态拉伸,高度自动适配。
|
|
|
|
*/
|
|
|
|
public static String generateImage(List<AbstractMap.SimpleEntry<String, String>> dataList, StyleConfig style) throws IOException {
|
|
|
|
// 读取背景图
|
|
|
|
public static String generateImage(List<AbstractMap.SimpleEntry<String, String>> dataList,
|
|
|
|
StyleConfig style) throws IOException {
|
|
|
|
// 加载背景图及参数
|
|
|
|
BufferedImage baseImage = ImageIO.read(new File(style.backgroundPath));
|
|
|
|
int width = baseImage.getWidth();
|
|
|
|
int maxTextWidth = width - 2 * style.startX;
|
|
|
|
|
|
|
|
// 取背景图中部颜色作为中间背景区的纯色背景
|
|
|
|
// 获取中部纯色背景色
|
|
|
|
int centerY = baseImage.getHeight() / 2;
|
|
|
|
int centerColorRGB = baseImage.getRGB(width / 2, centerY);
|
|
|
|
Color pureBackgroundColor = new Color(centerColorRGB);
|
|
|
|
|
|
|
|
// 创建临时画布用于计算文字高度
|
|
|
|
BufferedImage tempImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
|
|
|
Graphics2D tempG = tempImage.createGraphics();
|
|
|
|
tempG.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
|
|
|
tempG.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
|
|
|
int contentHeight = calculateTotalHeight(dataList, style, tempG, maxTextWidth); // 动态内容高度
|
|
|
|
tempG.dispose();
|
|
|
|
|
|
|
|
// 新图总高度 = 顶部logo + 内容区 + 底部logo
|
|
|
|
int finalHeight = style.topLogoHeight + contentHeight + style.bottomLogoHeight;
|
|
|
|
BufferedImage newImage = new BufferedImage(width, finalHeight, BufferedImage.TYPE_INT_ARGB);
|
|
|
|
Graphics2D g = newImage.createGraphics();
|
|
|
|
|
|
|
|
// 绘制顶部 logo 区域
|
|
|
|
Color pureBackgroundColor = new Color(baseImage.getRGB(width / 2, centerY));
|
|
|
|
|
|
|
|
// 计算内容区高度
|
|
|
|
BufferedImage tmp = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
|
|
|
Graphics2D gTemp = tmp.createGraphics();
|
|
|
|
gTemp.setFont(style.contentFont);
|
|
|
|
FontMetrics fmTemp = gTemp.getFontMetrics();
|
|
|
|
int contentHeight = calculateTotalHeight(dataList, style, gTemp, maxTextWidth);
|
|
|
|
gTemp.dispose();
|
|
|
|
|
|
|
|
// 加载 logo 和二维码
|
|
|
|
BufferedImage logoImg = ImageIO.read(new File("logo_baise.png"));
|
|
|
|
BufferedImage qr1 = ImageIO.read(new File(style.qrCodePath1));
|
|
|
|
BufferedImage qr2 = ImageIO.read(new File(style.qrCodePath2));
|
|
|
|
int qrWidth = qr1.getWidth(), qrHeight = qr1.getHeight();
|
|
|
|
|
|
|
|
// 准备文字高度
|
|
|
|
BufferedImage tmp2 = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
|
|
|
Graphics2D gTemp2 = tmp2.createGraphics();
|
|
|
|
gTemp2.setFont(style.contentFont);
|
|
|
|
FontMetrics fm2 = gTemp2.getFontMetrics();
|
|
|
|
int textHeight = fm2.getHeight();
|
|
|
|
gTemp2.dispose();
|
|
|
|
|
|
|
|
// 间距配置
|
|
|
|
int qrTextSpacing = style.qrTextSpacing; // 二维码下文字间距
|
|
|
|
int minBottomPadding = style.minBottomPadding; // 底部最少空白
|
|
|
|
int qrSpacing = style.qrSpacing;
|
|
|
|
|
|
|
|
// 计算底部总高度:二维码 + 文字 + spacing + padding
|
|
|
|
int bottomBlockHeight = qrHeight + qrTextSpacing + textHeight + minBottomPadding;
|
|
|
|
|
|
|
|
// 总高度
|
|
|
|
int finalHeight = style.topLogoHeight + contentHeight + bottomBlockHeight;
|
|
|
|
|
|
|
|
// 真正画图
|
|
|
|
BufferedImage newImg = new BufferedImage(width, finalHeight, BufferedImage.TYPE_INT_ARGB);
|
|
|
|
Graphics2D g = newImg.createGraphics();
|
|
|
|
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
|
|
|
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
|
|
|
|
|
|
|
// 顶部 logo 区
|
|
|
|
BufferedImage topPart = baseImage.getSubimage(0, 0, width, style.topLogoHeight);
|
|
|
|
g.drawImage(topPart, 0, 0, null);
|
|
|
|
|
|
|
|
// 绘制底部 logo 区域
|
|
|
|
BufferedImage bottomPart = baseImage.getSubimage(0, baseImage.getHeight() - style.bottomLogoHeight, width, style.bottomLogoHeight);
|
|
|
|
g.drawImage(bottomPart, 0, finalHeight - style.bottomLogoHeight, null);
|
|
|
|
|
|
|
|
// 绘制中部纯色背景(中间拉伸区域)
|
|
|
|
// 中部背景
|
|
|
|
g.setColor(pureBackgroundColor);
|
|
|
|
g.fillRect(0, style.topLogoHeight, width, contentHeight);
|
|
|
|
|
|
|
|
// 开启抗锯齿和文字抗锯齿
|
|
|
|
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
|
|
|
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
|
|
|
// 顶部右上角 logo
|
|
|
|
int logoW = logoImg.getWidth(), logoH = logoImg.getHeight();
|
|
|
|
g.drawImage(logoImg, width - style.startX - logoW, 20, null);
|
|
|
|
|
|
|
|
// 内容绘制起始 Y 坐标
|
|
|
|
// 内容绘制
|
|
|
|
int currY = style.topLogoHeight + style.startY;
|
|
|
|
|
|
|
|
for (int i = 0; i < dataList.size(); i++) {
|
|
|
|
String title = (i + 1) + ". " + dataList.get(i).getKey(); // 标题编号 + 标题
|
|
|
|
String content = dataList.get(i).getValue(); // 内容文本
|
|
|
|
String title = (i + 1) + ". " + dataList.get(i).getKey();
|
|
|
|
String content = dataList.get(i).getValue();
|
|
|
|
|
|
|
|
// 获取标题高度
|
|
|
|
g.setFont(style.titleFont);
|
|
|
|
FontMetrics titleMetrics = g.getFontMetrics();
|
|
|
|
int titleHeight = titleMetrics.getHeight();
|
|
|
|
FontMetrics tfm = g.getFontMetrics();
|
|
|
|
int th = tfm.getHeight();
|
|
|
|
|
|
|
|
// 获取内容文本行
|
|
|
|
g.setFont(style.contentFont);
|
|
|
|
FontMetrics contentMetrics = g.getFontMetrics();
|
|
|
|
List<String> lines = splitTextIntoLines(content, contentMetrics, maxTextWidth);
|
|
|
|
int lineHeight = contentMetrics.getHeight() + style.lineSpacing;
|
|
|
|
int contentBlockHeight = lines.size() * lineHeight;
|
|
|
|
FontMetrics cfm = g.getFontMetrics();
|
|
|
|
List<String> lines = splitTextIntoLines(content, cfm, maxTextWidth);
|
|
|
|
int lineH = cfm.getHeight() + style.lineSpacing;
|
|
|
|
int blockContentH = lines.size() * lineH;
|
|
|
|
|
|
|
|
// 整体文字高度 = 标题 + 间距 + 内容块
|
|
|
|
int totalTextHeight = titleHeight + 10 + contentBlockHeight;
|
|
|
|
int blockHeight = totalTextHeight + 2 * style.padding;
|
|
|
|
int totalTextH = th + 10 + blockContentH;
|
|
|
|
int blockH = totalTextH + 2 * style.padding;
|
|
|
|
int blockW = maxTextWidth + 2 * style.padding;
|
|
|
|
|
|
|
|
int blockWidth = maxTextWidth + 2 * style.padding;
|
|
|
|
int blockX = style.startX - style.padding;
|
|
|
|
int blockY = currY;
|
|
|
|
|
|
|
|
// 绘制圆角背景块(可配置是否显示)
|
|
|
|
int bx = style.startX - style.padding;
|
|
|
|
int by = currY;
|
|
|
|
if (style.showBackgroundBlock) {
|
|
|
|
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, style.backgroundAlpha));
|
|
|
|
g.setColor(style.backgroundColor);
|
|
|
|
g.fillRoundRect(blockX, blockY, blockWidth, blockHeight, style.cornerRadius, style.cornerRadius);
|
|
|
|
g.fillRoundRect(bx, by, blockW, blockH, style.cornerRadius, style.cornerRadius);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 恢复正常透明度,绘制文本内容
|
|
|
|
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
|
|
|
|
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));
|
|
|
|
g.setColor(style.textColor);
|
|
|
|
|
|
|
|
int textStartY = blockY + style.padding;
|
|
|
|
int ty0 = by + style.padding;
|
|
|
|
g.setFont(style.titleFont);
|
|
|
|
int titleY = textStartY + titleHeight;
|
|
|
|
g.drawString(title, style.startX, titleY);
|
|
|
|
g.drawString(title, style.startX, ty0 + th);
|
|
|
|
|
|
|
|
g.setFont(style.contentFont);
|
|
|
|
int contentStartY = titleY + 10 + lineHeight;
|
|
|
|
int contentStart = ty0 + th + 10 + lineH;
|
|
|
|
for (int j = 0; j < lines.size(); j++) {
|
|
|
|
int y = contentStartY + j * lineHeight;
|
|
|
|
g.drawString(lines.get(j), style.startX, y);
|
|
|
|
g.drawString(lines.get(j), style.startX, contentStart + j * lineH);
|
|
|
|
}
|
|
|
|
|
|
|
|
currY = blockY + blockHeight + style.blockSpacing; // 下一个块起始位置
|
|
|
|
currY = by + blockH + style.blockSpacing;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 绘制水印文字(右下角)
|
|
|
|
// 底部背景填充
|
|
|
|
int bottomY = style.topLogoHeight + contentHeight;
|
|
|
|
g.setColor(pureBackgroundColor);
|
|
|
|
g.fillRect(0, bottomY, width, bottomBlockHeight);
|
|
|
|
|
|
|
|
// 绘制二维码(右对齐)
|
|
|
|
int totalQW = 2 * qrWidth + qrSpacing;
|
|
|
|
int qr2X = width - style.startX - qrWidth;
|
|
|
|
int qr1X = qr2X - qrSpacing - qrWidth;
|
|
|
|
int qrY = bottomY + minBottomPadding / 2;
|
|
|
|
|
|
|
|
g.drawImage(qr1, qr1X, qrY, null);
|
|
|
|
g.drawImage(qr2, qr2X, qrY, null);
|
|
|
|
|
|
|
|
// 绘制每个二维码下方说明文字
|
|
|
|
g.setFont(style.tishiFont);
|
|
|
|
g.setColor(style.textColor);
|
|
|
|
String txt1 = "学习渔业知识";
|
|
|
|
String txt2 = "掌握渔价信息";
|
|
|
|
int w1 = fm2.stringWidth(txt1), w2 = fm2.stringWidth(txt2);
|
|
|
|
int textY = qrY + qrHeight + qrTextSpacing;
|
|
|
|
|
|
|
|
g.drawString(txt1, qr1X + (qrWidth - w1) / 2, textY);
|
|
|
|
g.drawString(txt2, qr2X + (qrWidth - w2) / 2, textY);
|
|
|
|
|
|
|
|
drawTextWatermark(g, width, finalHeight, style);
|
|
|
|
g.dispose();
|
|
|
|
|
|
|
|
// 生成保存文件名
|
|
|
|
String saveFile = style.savePath + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + "_xinwen.png";
|
|
|
|
ImageIO.write(newImage, "png", new File(saveFile));
|
|
|
|
return saveFile;
|
|
|
|
String savePath = style.savePath + new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + "_xinwen.png";
|
|
|
|
ImageIO.write(newImg, "png", new File(savePath));
|
|
|
|
return savePath;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 将文本按像素宽度进行自动换行
|
|
|
|
*/
|
|
...
|
...
|
@@ -196,18 +234,48 @@ public class MultiTextImageGenerator { |
|
|
|
return currY;
|
|
|
|
}
|
|
|
|
|
|
|
|
// /**
|
|
|
|
// * 绘制右下角文字水印
|
|
|
|
// */
|
|
|
|
// private static void drawTextWatermark(Graphics2D g, int width, int height, StyleConfig style) {
|
|
|
|
// g.setFont(style.watermarkFont);
|
|
|
|
// g.setColor(style.watermarkColor);
|
|
|
|
// FontMetrics metrics = g.getFontMetrics();
|
|
|
|
// int textWidth = metrics.stringWidth(style.watermarkText);
|
|
|
|
// int x = width - textWidth - style.watermarkTextMarginX;
|
|
|
|
// int y = height - style.watermarkTextMarginY;
|
|
|
|
// g.drawString(style.watermarkText, x, y);
|
|
|
|
// }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 绘制右下角文字水印
|
|
|
|
* 在图像上多个随机位置绘制水印文字
|
|
|
|
*/
|
|
|
|
private static void drawTextWatermark(Graphics2D g, int width, int height, StyleConfig style) {
|
|
|
|
g.setFont(style.watermarkFont);
|
|
|
|
g.setColor(style.watermarkColor);
|
|
|
|
FontMetrics metrics = g.getFontMetrics();
|
|
|
|
int textWidth = metrics.stringWidth(style.watermarkText);
|
|
|
|
int x = width - textWidth - style.watermarkTextMarginX;
|
|
|
|
int y = height - style.watermarkTextMarginY;
|
|
|
|
g.drawString(style.watermarkText, x, y);
|
|
|
|
int textHeight = metrics.getHeight();
|
|
|
|
|
|
|
|
int count = style.watermarkCount; // 水印数量
|
|
|
|
Random random = new Random(); // 在方法内生成随机数对象
|
|
|
|
|
|
|
|
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, style.watermarkAlpha)); // 半透明
|
|
|
|
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
|
|
int maxX = Math.max(1, width - textWidth - style.watermarkTextMarginX * 2);
|
|
|
|
int maxY = Math.max(1, height - textHeight - style.watermarkTextMarginY * 2);
|
|
|
|
|
|
|
|
int x = random.nextInt(maxX) + style.watermarkTextMarginX;
|
|
|
|
int y = random.nextInt(maxY) + style.watermarkTextMarginY + textHeight;
|
|
|
|
|
|
|
|
g.setColor(style.watermarkColor);
|
|
|
|
g.drawString(style.watermarkText, x, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
...
|
...
|
|