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);}
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/54920.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring的热部署工具和数据库密码加盐操作

1.布置热部署 引言&#xff1a;在程序运行起来后&#xff0c;如果我们对代码进行了修改&#xff0c;需要重新测试修改后的程序&#xff0c;就得重新启动程序&#xff0c;这样很麻烦。于是引入热部署之后&#xff0c;我们就不需要重新启动程序&#xff0c;会自动更正。 1.配置po…

牛顿迭代法求解x 的平方根

牛顿迭代法是一种可以用来快速求解函数零点的方法。 为了叙述方便&#xff0c;我们用 C C C表示待求出平方根的那个整数。显然&#xff0c; C C C的平方根就是函数 f ( x ) x c − C f(x)x^c-C f(x)xc−C 的零点。 牛顿迭代法的本质是借助泰勒级数&#xff0c;从初始值开始快…

前端大模型入门:使用Transformers.js手搓纯网页版RAG(二)- qwen1.5-0.5B - 纯前端不调接口

书接上文&#xff0c;本文完了RAG的后半部分&#xff0c;在浏览器运行qwen1.5-0.5B实现了增强搜索全流程。但受限于浏览器和模型性能&#xff0c;仅适合于研究、离线和高隐私场景&#xff0c;但对前端小伙伴来说大模型也不是那么遥不可及了&#xff0c;附带全部代码&#xff0c…

【深度学习】(5)--搭建卷积神经网络

文章目录 搭建卷积神经网络一、数据预处理1. 下载数据集2. 创建DataLoader&#xff08;数据加载器&#xff09; 二、搭建神经网络三、训练数据四、优化模型 总结 搭建卷积神经网络 一、数据预处理 1. 下载数据集 在PyTorch中&#xff0c;有许多封装了很多与图像相关的模型、…

机器学习(1):机器学习的概念

1. 机器学习的定义和相关概念 机器学习之父 Arthur Samuel 对机器学习的定义是&#xff1a;在没有明确设置的情况下&#xff0c;使计算机具有学习能力的研究领域。 国际机器学习大会的创始人之一 Tom Mitchell 对机器学习的定义是&#xff1a;计算机程序从经验 E 中学习&#…

Note2024092801_python 日历信息获取

在日历中找标记的日期并保存 1.背景 2.解读视频链接 后续更新后&#xff0c;会放在评论区&#xff0c;感谢大家关注。 3.代码实现过程 代码如下&#xff1a; 刚刚实现了一个python项目 关于 日历中工作日 信息读取的python程序 通过这个程序可以 熟悉 python 对excel表的…

vue3 通过 axios + jsonp 实现根据公网 ip, 查询天气信息

前提 安装 axios 的 jsonp 适配器。 pnpm install pingtou/axios-jsonp 简单使用说明&#xff1a;当与后端约定的请求 callback 参数名称不为为 callback 时&#xff0c;可修改。一般无需添加。 1. 获取当前电脑 ip 和城市信息 请求地址&#xff1a; https://whois.pconl…

Linux之我不会

一、常用命令 1.系统管理 1.1 systemctl start | stop | restart | status 服务名 案例实操 1 查看防火墙状态 systemctl status firewalld2 停止防火墙服务 systemctl stop firewalld3 启动防火墙服务 systemctl start firewalld4 重启防火墙服务 systemctl restart f…

dea插件开发-自定义语言9-Rename Refactoring

Rename 重构操作与Find Usages的重构操作非常相似。它使用相同的规则来定位要重命名的元素&#xff0c;并使用相同的单词索引来查找可能引用了被重命名元素的文件。执行重命名重构时&#xff0c;调用方法PsiNamedElement.setName()会为重命名的元素&#xff0c;调用该方法PsiRe…

【Canvas与诗词】秋夕.杜牧(银烛秋光冷画屏......)

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>金六边形外圈绿色底录杜牧秋夕诗</title><style type"…

AI学习指南深度学习篇-丢弃法Python实践

AI学习指南深度学习篇-丢弃法Python实践 引言 在深度学习的领域中&#xff0c;丢弃法&#xff08;Dropout&#xff09;是一种有效的防止过拟合的随机正则化技术。过拟合是指模型在训练集上表现良好&#xff0c;但在测试集或未见过的数据上表现较差的现象。丢弃法通过随机地“…

【电商搜索】现代工业级电商搜索技术-Facebook语义搜索技术QueSearch

【电商搜索】现代工业级电商搜索技术-Facebook语义搜索技术Que2Search 目录 文章目录 【电商搜索】现代工业级电商搜索技术-Facebook语义搜索技术Que2Search目录0. 论文信息1. 研究背景&#xff1a;2. 技术背景和发展历史&#xff1a;3. 算法建模3.1 模型架构3.1.1 双塔与分类 …

Rust调用tree-sitter支持自定义语言解析

要使用 Rust 调用 tree-sitter 解析自定义语言&#xff0c;你需要遵循一系列步骤来定义语言的语法&#xff0c;生成解析器&#xff0c;并在 Rust 中使用这个解析器。下面是详细步骤&#xff1a; 1. 定义自定义语言的语法 首先&#xff0c;你需要创建一个 tree-sitter 语言定义…

NLP:BERT的介绍

1. BERT 1.1 Transformer Transformer架构是一种基于自注意力机制(self-attention)的神经网络架构&#xff0c;它代替了以前流行的循环神经网络和长短期记忆网络&#xff0c;已经应用到多个自然语言处理方向。   Transformer架构由两个主要部分组成&#xff1a;编码器(Encod…

【HarmonyOS】应用引用media中的字符串资源如何拼接字符串

【HarmonyOS】应用引用media中的字符串资源如何拼接字符串 一、问题背景&#xff1a; 鸿蒙应用中使用字符串资源加载&#xff0c;一般文本放置在resoutces-base-element-string.json字符串配置文件中。便于国际化的处理。当然小项目一般直接引用字符串&#xff0c;不需要加载s…

[dp+dfs]砝码称重

题目描述 现有 n n n 个砝码&#xff0c;重量分别为 a 1 , a 2 , … , a n a_1, a_2, \ldots,a_n a1​,a2​,…,an​ &#xff0c;在去掉 m m m 个砝码后&#xff0c;问最多能称量出多少不同的重量&#xff08;不包括 0 0 0 &#xff09;。 输入格式 第一行为有两个整数…

python爬虫:从12306网站获取火车站信息

代码逻辑 初始化 (init 方法)&#xff1a; 设置请求头信息。设置车站版本号。 同步车站信息 (synchronization 方法)&#xff1a; 发送GET请求获取车站信息。返回服务器响应的文本。 提取信息 (extract 方法)&#xff1a; 从服务器响应中提取车站信息字符串。去掉字符串末尾的…

如何通过Dockfile更改docker中ubuntu的apt源

首先明确我们有一个宿主机和一个docker环境&#xff0c;接下来的步骤是基于他们两个完成的 1.在宿主机上创建Dockerfile 随便将后面创建的Dockerfile放在一个位置,我这里选择的是 /Desktop 使用vim前默认你已经安装好了vim 2.在输入命令“vim Dockerfile”之后&#xff0c;…

知识付费APP开发指南:基于在线教育系统源码的技术详解

本篇文章&#xff0c;我们将探讨基于在线教育系统源码的知识付费APP开发的技术细节&#xff0c;帮助开发者和企业快速入门。 一、选择合适的在线教育系统源码 选择合适的在线教育系统源码是开发的关键一步。市场上有许多开源和商业化的在线教育系统源码&#xff0c;开发者需要…

花都狮岭寄宿自闭症学校:开启孩子的生命之门

在花都狮岭这片充满温情的土地上&#xff0c;有一所特别的学校&#xff0c;它像一把钥匙&#xff0c;轻轻旋转&#xff0c;为自闭症儿童们开启了一扇通往无限可能的生命之门——这就是广州星贝育园自闭症儿童寄宿制学校。这所学校不仅是知识的摇篮&#xff0c;更是孩子们心灵成…