当前位置: 首页 > news >正文

Java 图片合成

前序

本周接到了新项目中的一个需求:根据给定的内容合成一张图片,需求如下:

  1. 标题自动换行,如果标题中出现英文单词时,以单词为最小单元进行换行。
  2. 如果行数超过5行省略用 … 代替。
  3. 符号是下一行首字母时,自动截留到上一行末尾。
  4. 空格为下一行开头,则删除空格,显示单词,保持内容左对齐
技术栈(JAI)

Java Advanced Imaging (JAI) 是一个用于处理图像的开源Java库。它提供了一个框架,可以用来访问各种图像源,包括本地文件系统、网络资源以及数据库等,并可以对这些图像进行转换和分析。

优点:JDK 自带内容,操作简单,不用引入新的依赖。

代码概述

本功能通过加载本地的背景图片和传入的参数进行图片的合成。主要包含了背景图、二维码插图和文本内容。其中二维码插图通过 HuTool 工具包提供生成方法。

具体代码

QrCodeImageConfig.java(二维码插图配置类)

import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;/*** 插图参数*/
@Getter
@Slf4j
public class QrCodeImageConfig {/*** 二维码内容*/private final String text;/*** 二维码宽度*/private final Integer width;/*** 二维码高度*/private final Integer height;/*** 二维码颜色*/private final Color color;/*** 二维码图片接收对象*/private final File qrCodeImgFile;/*** JAI 图片对象*/private final BufferedImage qrCodeBuffer;/*** 构造函数** @param text 二维码内容* @param width 二维码宽度* @param height 二维码高度* @param color 二维码颜色* @param qrCodeImg 二维码图片接收对象*/public QrCodeImageConfig(String text, Integer width, Integer height, Color color, File qrCodeImg) {if (StringUtils.isBlank(text) || width == null || height == null || color == null || qrCodeImg == null) {throw new IllegalArgumentException("QrCodeImageParam.QrCodeImageParam 参数异常");}this.color = color;this.width = width;this.height = height;this.text = text;this.qrCodeImgFile = qrCodeImg;try {QrConfig config = new QrConfig(width, height);config.setBackColor(color);config.setMargin(1);QrCodeUtil.generate(text, config, qrCodeImg);this.qrCodeBuffer = ImageIO.read(qrCodeImg);} catch (IOException e) {throw new RuntimeException("QrCodeImageParam.QrCodeImageParam2 二维码图片配置创建异常");}}/*** 获取图片高度*/public int getImageHeight() {return qrCodeBuffer.getHeight();}/*** 获取图片宽度*/public int getImageWidth() {return qrCodeBuffer.getWidth();}
}

TextConfig.java(文本内容配置类)

import lombok.Getter;import java.awt.*;/*** 文本内容配置*/
@Getter
public class TextConfig {/*** 文本内容*/private final String text;/*** 字体库*/private final String typeface;/*** 是否加粗*/private final Boolean boldFont;/*** 字号*/private final Integer fontSize;/*** 字体颜色*/private final Color fontColor;/*** 行间距*/private final Integer lineSpacing;/*** 字间距*/private final Integer wordSpace;/*** x轴坐标*/private final Integer x;/*** y轴坐标*/private Integer y;/*** 构造函数** @param text 文本内容* @param typeface 字体库* @param boldFont 是否加粗* @param fontSize 字号* @param fontColor 字体颜色* @param lineSpacing 行间距* @param wordSpace 字间距* @param x x轴坐标* @param y y轴坐标*/public TextConfig(String text, String typeface, Boolean boldFont, Integer fontSize, Color fontColor,Integer lineSpacing, Integer wordSpace, Integer x, Integer y) {this.text = text;this.typeface = typeface;this.boldFont = boldFont;this.fontSize = fontSize;this.fontColor = fontColor;this.lineSpacing = lineSpacing;this.wordSpace = wordSpace;this.x = x;this.y = y;}/*** 构造函数** @param text 文本内容* @param typeface 字体库* @param boldFont 是否加粗* @param fontSize 字号* @param fontColor 字体颜色* @param lineSpacing 行间距* @param wordSpace 字间距* @param x x轴坐标*/public TextConfig(String text, String typeface, Boolean boldFont, Integer fontSize, Color fontColor,Integer lineSpacing, Integer wordSpace, Integer x) {this.text = text;this.typeface = typeface;this.boldFont = boldFont;this.fontSize = fontSize;this.fontColor = fontColor;this.lineSpacing = lineSpacing;this.wordSpace = wordSpace;this.x = x;}
}

GenerateAttendanceImageUtil.java(签到图片生成工具类)

import lombok.extern.slf4j.Slf4j;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;/*** 生成签到图片工具类*/
@Slf4j
public class GenerateAttendanceImageUtil {private static int y = 0;/*** 生成签到页图片(PNG格式)** @param backgroundImage 背景图片* @param title           会议主题配置* @param dateAddress     时间地址配置* @param qrCodeImgCfg    二维码图配置* @param conferee        与会人配置* @param footer          页脚配置* @param outputFile      图片输出位置*/public static void createCompositeImage(File backgroundImage, TextConfig title, TextConfig dateAddress,QrCodeImageConfig qrCodeImgCfg, TextConfig conferee, TextConfig footer, File outputFile) {try {// 加载背景图片BufferedImage bgImgBuffer = ImageIO.read(backgroundImage);Graphics g = bgImgBuffer.getGraphics();// 插入文字准备Graphics2D g2d = (Graphics2D) g;g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);// 插入会议主题y = title.getY();drawString(title, g, g2d, bgImgBuffer.getWidth() - title.getX(), false);// 插入日期、地点y += 40;drawString(dateAddress, g, g2d, bgImgBuffer.getWidth() - dateAddress.getX(), false);// 插入二维码y = Math.max(y + 30, 310);g.drawImage(qrCodeImgCfg.getQrCodeBuffer(), (bgImgBuffer.getWidth() - qrCodeImgCfg.getWidth()) / 2, y, null);// 插入与会人y += 40 + qrCodeImgCfg.getImageHeight();drawString(conferee, g, g2d, bgImgBuffer.getWidth(), true);// 插入页脚y = bgImgBuffer.getHeight() - 40;drawString(footer, g, g2d, bgImgBuffer.getWidth(), true);g.dispose();// 将结果保存为新的图片ImageIO.write(bgImgBuffer, "png", outputFile);} catch (IOException e) {log.error(e.getMessage());} finally {if (qrCodeImgCfg.getQrCodeImgFile().exists()) {boolean delete = qrCodeImgCfg.getQrCodeImgFile().delete();if (!delete) {log.error("GenerateAttendanceImageUtil.createCompositeImage finally 二维码图片删除失败");}}}}/*** 绘制文字*/private static void drawString(TextConfig textConfig, Graphics g, Graphics2D g2d, Integer bgImgWidth, boolean horizontalCenter) {g.setFont(new Font(textConfig.getTypeface(), textConfig.getBoldFont() ? Font.BOLD : Font.PLAIN, textConfig.getFontSize()));g.setColor(textConfig.getFontColor());FontMetrics fm = g2d.getFontMetrics();int lineHeight = fm.getHeight();int x = horizontalCenter ? (bgImgWidth - fm.stringWidth(textConfig.getText())) / 2 : textConfig.getX();int lingNum = 1;for (String text : convertToArray(textConfig.getText())) {int textWidth = lingNum > 4 ? fm.stringWidth(text + "...") : fm.stringWidth(text);boolean needTrim = false;// 最多显示5行超出部分省略if (x + textWidth > bgImgWidth) {if (lingNum >= 5) {g2d.drawString("...", x, y);break;}// 符号截留在上一行,不做下一行的首字母if (!Character.isLetterOrDigit(text.charAt(0)) && !Character.isWhitespace(text.charAt(0))) {g2d.drawString(text, x, y);continue;}x = textConfig.getX();y += lineHeight + textConfig.getLineSpacing();needTrim = true;lingNum++;}// 取消换行后首字母的空格if (needTrim && text.equals(" ")) {continue;}g2d.drawString(text, x, y);x += fm.stringWidth(text) + textConfig.getWordSpace();}}/*** 判断是否为英文或数字*/private static boolean isLetterOrNumber(char c) {String character = String.valueOf(c);return Pattern.matches("[a-zA-Z0-9]", character);}/*** 将字符串拆分成最小单元*/public static String[] convertToArray(String input) {List<String> resultList = new ArrayList<>();for (int i = 0; i < input.length(); i++) {char s = input.charAt(i);if (isLetterOrNumber(s)) {StringBuilder sb = new StringBuilder();while (i < input.length() && isLetterOrNumber(input.charAt(i))) {sb.append(input.charAt(i));i++;}i--;resultList.add(sb.toString());} else {resultList.add(String.valueOf(s));}}return resultList.toArray(new String[0]);}
}

注:

  1. 工具的核心方法有两个,一是 convertToArray() 函数,我们需要通过该方法将文本内容拆分成最小单元,每一个最小单元为数组中的一个元素。换行时需要通过判断元素和本行以生成内容的长度来判断是否进行换行。
  2. 二是drawString()函数,通过该函数实现了自动换行,符号截留,首字母空格消除等功能。
  3. 该工具类由于高度耦合需求所以没有抽取成一个大家都能公用的类库大家使用,需要使用的同学请根据自己的需求进行逻辑的调整。
测试类
import java.awt.*;
import java.io.File;
import java.util.UUID;/*** 测试 生成签到二维码图片*/
public class Test {public static void main(String[] args) {File bgImg = new File("/Users/Desktop/background.png");TextConfig title = new TextConfig("aaaaaaaaaaaaaaaaa aaaaaaaaaaaa aa aaaaaaaaaa, aaaaaaaaa, aaa aaaaaaaa aaaaaaaaaaaaaaaaa aaaaaaaaaaaa aa aaaaaaaaa aaaaaaaaaa aaaaaaaaaa","Arial", true, 24, Color.BLACK, 2, 1, 24, 144);TextConfig dateAddress = new TextConfig("January 7, 2023-March 7, 2024 | Hong Kong, China", "Arial",false, 16, Color.GRAY, 2, 0, 24);QrCodeImageConfig qrCodeImgCfg = new QrCodeImageConfig("https://www.baidu.com", 200, 200,Color.WHITE, new File("/Users/Desktop/" + UUID.randomUUID() + "qrcode.png"));TextConfig conferee = new TextConfig("Hello World", "Arial", false, 30, Color.BLACK,0, 0, 24);TextConfig footer = new TextConfig("保存图片用于XXXX", "Arial", false, 14,Color.GRAY, 0, 0, 24);File outputFile = new File("/Users/Desktop/composite_image.png");GenerateAttendanceImageUtil.createCompositeImage(bgImg, title, dateAddress, qrCodeImgCfg, conferee, footer, outputFile);}
}

--------------------------------------------人生哪能多如意,万事只求半称心--------------------------------------------

相关文章:

  • Unity3D 客户端多开
  • 5个python多线程简单示例
  • 一次实践:给自己的手机摄像头进行相机标定
  • 用于视觉的MetaFormer基线模型
  • 数据结构-4.1.特殊矩阵的压缩存储
  • C++11 多线程编程-小白零基础到手撕线程池
  • 秋招内推--招联金融2025
  • 论文阅读:多模态医学图像融合方法的研究进展
  • 负载均衡架构解说
  • C++ Linux多进程同步-命名信号量
  • HarmonyOS NEXT:实现电影列表功能展示界面
  • IDEA相关设置总结
  • 03Frenet与Cardesian坐标系(Frenet转Cardesian公式推导)
  • Win10 QT 配置Android开发环境-jdk/sdk/gradle
  • 探究Spring的单例设计模式--单例Bean
  • CSS实用技巧
  • Java 内存分配及垃圾回收机制初探
  • Java小白进阶笔记(3)-初级面向对象
  • Laravel 实践之路: 数据库迁移与数据填充
  • Laravel核心解读--Facades
  • python3 使用 asyncio 代替线程
  • SpiderData 2019年2月25日 DApp数据排行榜
  • UEditor初始化失败(实例已存在,但视图未渲染出来,单页化)
  • WordPress 获取当前文章下的所有附件/获取指定ID文章的附件(图片、文件、视频)...
  • 初识 beanstalkd
  • 规范化安全开发 KOA 手脚架
  • 力扣(LeetCode)357
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 说说动画卡顿的解决方案
  • 学习笔记TF060:图像语音结合,看图说话
  • 不要一棍子打翻所有黑盒模型,其实可以让它们发挥作用 ...
  • 如何用纯 CSS 创作一个货车 loader
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • # 利刃出鞘_Tomcat 核心原理解析(二)
  • #NOIP 2014#Day.2 T3 解方程
  • $jQuery 重写Alert样式方法
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (Java)【深基9.例1】选举学生会
  • (pt可视化)利用torch的make_grid进行张量可视化
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (八)Flask之app.route装饰器函数的参数
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (非本人原创)我们工作到底是为了什么?​——HP大中华区总裁孙振耀退休感言(r4笔记第60天)...
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • .chm格式文件如何阅读
  • .net core 6 redis操作类
  • .net core 外观者设计模式 实现,多种支付选择
  • .NET 发展历程
  • .NET 漏洞分析 | 某ERP系统存在SQL注入
  • .Net 路由处理厉害了
  • .NET 使用 XPath 来读写 XML 文件
  • .net 中viewstate的原理和使用
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .Net通用分页类(存储过程分页版,可以选择页码的显示样式,且有中英选择)