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

Java-如何使用Java将图片和文字拼接在一起(并非是给图片加水印)

    之前有遇到一个问题

    问题背景:项目中,有一个功能,管理端可以将客户创建的小程序码下载到本地,方便客户将对应门店的小程序码打印出来并张贴到门店,做门店的引流和会员入会。

    具体问题:当小程序码的数量较少的时候,我们是后端将小程序码的分组信息和小程序码的图片以树的数据结构形式,返回给前端,由前端拿到分组信息和小程序图片链接,在前端进行下载小程序码图片,并将分组信息拼接在小程序码的下方,类似这样:

            但是,当这个门店的结构复杂之后,小程序码的数量也多了起来,由前端来下载就显得非常让人焦灼了,前端只能使用下载的这台电脑的性能来一张一张的下载小程序码并拼接门店的信息,1000多张小程序码的话,就需要10分钟左右的等待时间,有的客户的电脑性能比较差的话,干脆就没办法下载,怎么办呢?

        一句话,放后端并行下载呗,然后直接返回zip包的流数据文件给前端不就行了。。。

        当时接到这个任务,我也天真的认为,搞个线程并行下载,然后打包不就OK了么,能有多费事呢?服务器随随便便不就16核+64G的配置,下载个文件就算网络差点,千把个图片还不是分分钟的事儿嘛,领导面前胸口拍得梆梆响,小事一桩嘛~~~

        下载倒是好说,并发 CountDownLatch cdl = new CountDownLatch(size); 控制下下载的次序下载完再一起打包。。。

        但是,把文字怎么搞到这张小程序码图片的下面呢,又不能拉伸这张图片,那就要把文字先转成图片(跟小程序码图片宽度保持一致)

        a.先把远程的图片下载到本地

String localFilePath = "D:\\Download\\0402" + File.separator;
String localFileName = "test3.png";
downloadFile("http://0.0.0.0:8080/photo/gh_ff959c80f0d7_1280.jpg", localFilePath, localFileName);/*** 下载远程文件并保存到本地*/
public static void downloadFile(String remoteFilePath, String localFilePath, String fileName) {FileUtil.mkdir(localFilePath);ReadableByteChannel rbc = null;FileOutputStream fos = null;try {URL website = new URL(remoteFilePath);rbc = Channels.newChannel(website.openStream());fos = new FileOutputStream(localFilePath + fileName);fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);} catch (Exception e) {log.warn(e.getMessage());} finally {IOUtils.closeQuietly(fos);IOUtils.closeQuietly(rbc);}}

        b.将第一行文字和第二行文字转换成跟小程序码图片一样宽度的图片

    /*** 小程序码 导出组名 二维码名 字体*/public static final String SOURCE_HAN_SANS = "思源黑体 CN Regular";/*** 小程序码 导出 二维码名 字号*/public static final int FONT_SIZE_TITLE = 24;/*** 小程序码 导出组名 字号*/public static final int FONT_SIZE_GROUP_NAME = 14;/*** 小程序码 导出二维码图片 缩略图 宽度*/public static final int MA_QR_CODE_IMAGE_WIDTH = 552;/*** 小程序码 导出二维码图片 缩略图 高度*/public static final int MA_QR_CODE_IMAGE_HEIGHT = 552;/*** 小程序码 文字生成图片 背景高度*/public static final int BACK_GROUND_IMAGE_HEIGHT = 35;/*** 小程序码 导出二维码名 每行长度*/public static final int MA_QR_CODE_SPLIT_SIZE = 21;/*** 小程序码 导出组名 每行长度*/public static final int MA_GROUP_SPLIT_SIZE = 38;//第一行文字
String fileName = "ZSHMD上海市浦东新区东方体育中心万达购物中心店";
String targetFile = localFilePath + "test3_t1.png";
TextToImage.textToImage(TextToImage.ImageContent.buildOf(MA_QR_CODE_IMAGE_WIDTH, MA_QR_CODE_SPLIT_SIZE, Color.BLACK, SOURCE_HAN_SANS, FONT_SIZE_TITLE, fileName, targetFile));
//第二行文字
String groupFullName = "全部-中国-上海-浦东新区-三林镇";
String groupTargetFile = localFilePath + "test3_t2.png";
TextToImage.textToImage(TextToImage.ImageContent.buildOf(MA_QR_CODE_IMAGE_WIDTH, MA_GROUP_SPLIT_SIZE, Color.GRAY, SOURCE_HAN_SANS, FONT_SIZE_GROUP_NAME, groupFullName, groupTargetFile));

工具类方法:

    /*** 将文字转换为png图片*/public static void textToImage(ImageContent content) throws IOException {//小程序码 文字生成图片 背景高度String contentText = content.getText();if (StringUtils.isEmpty(contentText)) {return;}String[] texts = contentText.split("(?<=\\G.{" + content.getSplitSize() + "})");int height = texts.length * BACK_GROUND_IMAGE_HEIGHT;//创建图片int width = content.getWidth();BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);Graphics2D graphics = bufferedImage.createGraphics();//设置背景graphics.fillRect(0, 0, width, height);//定义字体Font font = new Font(content.getFontName(), Font.PLAIN, content.getFontSize());// 防止生成的文字带有锯齿graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);//设置颜色graphics.setColor(content.getColor());//设置字体graphics.setFont(font);//写入 (getWordWidth 计算该字体文本的长度) 居中Lists.mutable.of(texts).forEachWithIndex((x, i) ->graphics.drawString(x, (width - getWordWidth(font, x)) / 2, 20 + 30 * i));graphics.dispose();ImageIO.write(bufferedImage, PNG, new File(content.getTargetFile()));}

工具类-内部类:

    @Datapublic static class ImageContent{private int width;private int splitSize;private Color color;private String fontName;private int fontSize;private String text;private String targetFile;public static ImageContent buildOf(int maQrCodeImageWidth, int maQrCodeSplitSize, Color color, String fontName, int fontSize, String text, String targetFile) {ImageContent content = new ImageContent();content.setWidth(maQrCodeImageWidth);content.setSplitSize(maQrCodeSplitSize);content.setColor(color);content.setFontName(fontName);content.setFontSize(fontSize);content.setText(text);content.setTargetFile(targetFile);return content;}}@Getter@AllArgsConstructorpublic enum SpliceType{/** */TRANSVERSE("横向"),PORTRAIT("纵向");private final String desc;}

        c.将小程序码图片生成552x552大小的缩略图

//生成图片的缩略图 552x552
Thumbnails.of(localFilePath + localFileName).size(MA_QR_CODE_IMAGE_WIDTH, MA_QR_CODE_IMAGE_HEIGHT).keepAspectRatio(false).toFile(localFilePath + "test3_t0.png");maven依赖:
<!-- https://mvnrepository.com/artifact/net.coobird/thumbnailator -->
<dependency><groupId>net.coobird</groupId><artifactId>thumbnailator</artifactId><version>0.4.8</version>
</dependency>

        d.拼接图片,生成想要的那种图片结构

TextToImage.mergeImage(new String[]{localFilePath + "test3_t0.png", localFilePath + "test3_t1.png", localFilePath + "test3_t2.png"}, TextToImage.SpliceType.PORTRAIT, localFilePath + File.separator + "test3.png");/*** @param files 要拼接的图片列表* @param type  1 横向拼接, 2 纵向拼接*              图片拼接 (注意:必须两张图片长宽一致)*/public static void mergeImage(String[] files, SpliceType type, String targetFile) {int len = files.length;if (len < 1) {log.warn("图片数量小于1");return;}File[] src = new File[len];BufferedImage[] images = new BufferedImage[len];int[][] imageArrays = new int[len][];for (int i = 0; i < len; i++) {try {src[i] = new File(files[i]);images[i] = ImageIO.read(src[i]);} catch (Exception e) {log.warn("{}", e.getMessage(), e);}int width = images[i].getWidth();int height = images[i].getHeight();imageArrays[i] = new int[width * height];imageArrays[i] = images[i].getRGB(0, 0, width, height, imageArrays[i], 0, width);}int newHeight = 0;int newWidth = 0;for (BufferedImage image : images) {// 横向if (SpliceType.TRANSVERSE == type) {newHeight = Math.max(newHeight, image.getHeight());newWidth += image.getWidth();}// 纵向if (SpliceType.PORTRAIT == type) {newWidth = Math.max(newWidth, image.getWidth());newHeight += image.getHeight();}}if (SpliceType.TRANSVERSE == type && newWidth < 1) {return;}if (SpliceType.PORTRAIT == type && newHeight < 1) {return;}// 生成新图片try {BufferedImage imageNew = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);int height = 0;int width = 0;for (int i = 0; i < images.length; i++) {if (SpliceType.TRANSVERSE == type) {imageNew.setRGB(width, 0, images[i].getWidth(), newHeight, imageArrays[i], 0, images[i].getWidth());width += images[i].getWidth();}if (SpliceType.PORTRAIT == type) {imageNew.setRGB(0, height, newWidth, images[i].getHeight(), imageArrays[i], 0, newWidth);height += images[i].getHeight();}}//输出想要的图片ImageIO.write(imageNew, PNG, new File(targetFile));} catch (Exception e) {log.warn("{}", e.getMessage(), e);}}

        注意,如果部署Linux上之后,可能会发现文字的位置是空的,那就需要在项目根目录下安装一下字体哦。

 

//始终都删除中间生成的临时文件
Lists.mutable.of("test3_t0.png", "test3_t1.png", "test3_t2.png").forEach(FileUtil::del);

         好了,到此,这个问题已经解决了,并不知道有大佬有没有更好的办法呢,还请不吝赐教呀~

相关文章:

Java-如何使用Java将图片和文字拼接在一起(并非是给图片加水印)

之前有遇到一个问题 问题背景&#xff1a;项目中&#xff0c;有一个功能&#xff0c;管理端可以将客户创建的小程序码下载到本地&#xff0c;方便客户将对应门店的小程序码打印出来并张贴到门店&#xff0c;做门店的引流和会员入会。 具体问题&#xff1a;当小程序码的数量较少…...

Metasploit入门到高级【第三章】

来自公粽号&#xff1a;Kali与编程预计更新第一章&#xff1a;Metasploit 简介 Metasploit 是什么Metasploit 的历史和发展Metasploit 的组成部分 第二章&#xff1a;Kali Linux 入门 Kali Linux 简介Kali Linux 安装和配置常用命令和工具介绍 第三章&#xff1a;Metasploi…...

枚举的使用

Java 枚举是一个特殊的类&#xff0c;一般表示一组常量&#xff0c;比如一年的 4 个季节&#xff0c;一个年的 12 个月份&#xff0c;一个星期的 7 天&#xff0c;方向有东南西北等。1 问题如何在类中使用枚举&#xff0c;例如枚举出一年的四个季度&#xff0c;并且通过迭代枚举…...

Python进阶语法

1.1 Python进阶语法 1.1.1 交换变量 一行代码快速交换两个变量&#xff0c;无需创建临时变量。 from icecream import ica 2 b 4 a, b b, a ic(a, b)ic| a: 4, b: 2 1.1.2 链式比较 from icecream import ica 97 if 90 < a < 100:ic(a)ic| a: 97 1.1.3 初始化列表…...

Pyspark_结构化流4

Pyspark 注&#xff1a;大家觉得博客好的话&#xff0c;别忘了点赞收藏呀&#xff0c;本人每周都会更新关于人工智能和大数据相关的内容&#xff0c;内容多为原创&#xff0c;Python Java Scala SQL 代码&#xff0c;CV NLP 推荐系统等&#xff0c;Spark Flink Kafka Hbase Hi…...

Linux cmp 命令

Linux cmp 命令用于比较两个文件是否有差异。 当相互比较的两个文件完全一样时&#xff0c;则该指令不会显示任何信息。若发现有所差异&#xff0c;预设会标示出第一个不同之处的字符和列数编号。若不指定任何文件名称或是所给予的文件名为"-"&#xff0c;则cmp指令…...

Python入门到高级【第五章】

预计更新第一章. Python 简介 Python 简介和历史Python 特点和优势安装 Python 第二章. 变量和数据类型 变量和标识符基本数据类型&#xff1a;数字、字符串、布尔值等字符串操作列表、元组和字典 第三章. 控制语句和函数 分支结构&#xff1a;if/else 语句循环结构&#…...

C语言中(i++)+ (i++)真的每次都等于3吗?

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言结论证明首先&#xff0c;登场的是我们的VC6.0&#xff08;还有Linux&#xff09;最后一位&#xff0c;我使用了小熊猫C&#xff08;还有Clion&#xff09;请添加…...

Cursor,程序员的 AI 代码编辑助手

相信大家都或多或少地听说过、了解过 chatGPT &#xff0c;半个月前发布的 GPT-4 &#xff0c;可谓是 AI 赛道上的一个王炸 那么今天咸鱼给大家分享一个开源的 AI 代码编辑器——Cursor&#xff0c;让各位程序员在编程之路上一骑绝尘 &#x1f603; 介绍 Cursor 是一个人工智…...

基于XML的自动装配~

基于XML的自动装配之场景模拟&#xff1a; 自动装配&#xff1a;根据指定的策略&#xff0c;在IOC容器中匹配某一个bean&#xff0c;自动为指定的bean中所依赖的类类型或者接口类型赋值 之前我们学过的依赖注入&#xff0c;我们在为不同属性赋值时&#xff0c;例如类类型的属性…...

完全二叉树的4种遍历方式

一张二叉树的图 1&#xff0c;二叉树的特点 每个点p的左儿子是p*2,右儿子是p*21&#xff0c;可以分别表示为p<<1与p<<1|1节点的序号是从左到右&#xff0c;从上到下增加的每个点至多2个儿子&#xff08;屁话&#xff08;bushi&#xff09;&#xff09; 2&#xff…...

【vue2】使用elementUI进行表单验证实操(附源码)

&#x1f973;博 主&#xff1a;初映CY的前说(前端领域) &#x1f31e;个人信条&#xff1a;想要变成得到&#xff0c;中间还有做到&#xff01; &#x1f918;本文核心&#xff1a;vue使用elementUI进行表单验证实操&#xff08;附源码&#xff09; 【前言】我们在构建一…...

JUC之阻塞队列解读(BlockingQueue)

目录 BlockingQueue 简介 BlockingQueue 核心方法 1.放入数据 2.获取数据 入门代码案例 常见的 BlockingQueue ArrayBlockingQueue(常用) LinkedBlockingQueue(常用) PriorityBlockingQueue SynchronousQueue LinkedTransferQueue LinkedBlockingDeque 小结 Bloc…...

LCHub:ChatGPT4和低代码来临,程序员面临下岗?

一个网友吐槽道: “ 建站出来了,你们说程序员会失业。 低代码出来了,你们说程序员会失业。 Copilot出来了,你们说程序员会失业。 Chatgpt出来了,你们说程序员会失业 虽然这只是网友的吐槽,但却引起了小编的好奇。为何程序员那么容易被新技术取代?今天小编打算跟大家…...

【Node.js】Express框架的基本使用

✍️ 作者简介: 前端新手学习中。 &#x1f482; 作者主页: 作者主页查看更多前端教学 &#x1f393; 专栏分享&#xff1a;css重难点教学 Node.js教学 从头开始学习 目录 初识Express Express简介 什么是Express 进一步理解 Express Express能做什么 Express的基本使用 …...

使用docker 和 kubnernetes 部署单节点/多节点 kafka 环境

参考资料 https://kafka.apachecn.org/documentation.html#configuration kafka的broker有三个核心配置 broker.idlog.dirszookeeper.connect docker启动单节点kafka环境 启动zookeeper 可配置的环境变量&#xff0c;https://gallery.ecr.aws/bitnami/zookeeper $ docker …...

Linux使用:环境变量指南和CPU和GPU利用情况查看

Linux使用&#xff1a;环境变量指南和CPU和GPU利用情况查看Linux环境变量初始化与对应文件的生效顺序Linux的变量种类设置环境变量直接运行export命令定义变量修改系统环境变量修改用户环境变量修改环境变量配置文件环境配置文件的区别profile、 bashrc、.bash_profile、 .bash…...

深入浅出 SSL/CA 证书及其相关证书文件(pem、crt、cer、key、csr)

互联网是虚拟的&#xff0c;通过互联网我们无法正确获取对方真实身份。数字证书是网络世界中的身份证&#xff0c;数字证书为实现双方安全通信提供了电子认证。数字证书中含有密钥对所有者的识别信息&#xff0c;通过验证识别信息的真伪实现对证书持有者身份的认证。数字证书可…...

Compose(1/N) - 概念 基本使用

一、概念 1.1 解决的问题 APP展示的数据绝大多数不是静态数据而是会实时更新&#xff0c;传统的命令式UI写法更新界面繁琐且容易同步错误。1.2 Compose优势 由一个个可组合的Composable函数&#xff08;可看作是一个Layout布局&#xff09;拼成界面&#xff0c;方便维护和复用…...

2023高质量Java面试题集锦:高级Java工程师面试八股汇总

人人都想进大厂&#xff0c;当然我也不例外。早在春招的时候我就有向某某某大厂投岗了不少简历&#xff0c;可惜了&#xff0c;疫情期间都是远程面试&#xff0c;加上那时自身也有问题&#xff0c;导致屡投屡败。突然也意识到自己肚子里没啥货&#xff0c;问个啥都是卡卡卡卡&a…...

终极指南:如何免费解锁Cursor Pro完整功能 - 突破AI编辑器限制的完整方案

终极指南&#xff1a;如何免费解锁Cursor Pro完整功能 - 突破AI编辑器限制的完整方案 【免费下载链接】cursor-free-vip [Support 0.45]&#xff08;Multi Language 多语言&#xff09;自动注册 Cursor Ai &#xff0c;自动重置机器ID &#xff0c; 免费升级使用Pro 功能: Youv…...

告别Demo!用EMQX和Java模拟真实物联网设备上报数据流(Windows本地开发环境)

告别Demo&#xff01;用EMQX和Java构建真实物联网数据流模拟方案 在物联网开发中&#xff0c;最令人头疼的莫过于缺乏真实设备进行测试。想象一下&#xff0c;当你精心设计的平台等待设备接入时&#xff0c;硬件团队却告诉你"下周才能交付原型机"。这种等待不仅拖延进…...

5步实现AutoHotkey脚本独立运行:Ahk2Exe编译实战指南

5步实现AutoHotkey脚本独立运行&#xff1a;Ahk2Exe编译实战指南 【免费下载链接】Ahk2Exe Official AutoHotkey script compiler - written itself in AutoHotkey 项目地址: https://gitcode.com/gh_mirrors/ah/Ahk2Exe 你是否遇到过这样的困扰&#xff1f;精心编写的A…...

基于Helm Chart的JupyterHub生产级部署与运维实战指南

1. 项目概述&#xff1a;为什么我们需要一个可扩展的JupyterHub部署方案&#xff1f;如果你在团队里负责过数据科学或机器学习平台的搭建&#xff0c;大概率会为Jupyter Notebook的部署和管理头疼过。单个Jupyter Notebook服务给一两个人用还行&#xff0c;一旦团队规模扩大到十…...

ARM Cortex-X4/X925处理器仿真模型与指令集详解

1. ARM Cortex-X4/X925处理器仿真模型概述处理器仿真模型在现代芯片设计中扮演着至关重要的角色&#xff0c;特别是在Arm架构的生态系统中。作为Arm最新一代高性能核心&#xff0c;Cortex-X4和X925的Iris仿真组件提供了完整的指令集和微架构行为建模&#xff0c;使开发者能够在…...

火灾动力学模拟实战:如何用FDS构建精准的火灾预测系统

火灾动力学模拟实战&#xff1a;如何用FDS构建精准的火灾预测系统 【免费下载链接】fds Fire Dynamics Simulator 项目地址: https://gitcode.com/gh_mirrors/fd/fds 你是否曾面临这样的困境&#xff1a;当设计一栋大型商业建筑时&#xff0c;如何科学评估火灾时的人员疏…...

Arduino与手机蓝牙通信:nRF8001 BLE模块硬件连接与软件配置全解析

1. 项目概述与核心价值如果你手头有一个Arduino项目&#xff0c;想让它和你的手机“说说话”&#xff0c;比如把传感器数据无线传到手机App上显示&#xff0c;或者用手机App远程控制几个LED灯&#xff0c;那么nRF8001这个蓝牙低功耗&#xff08;BLE&#xff09;模块绝对是你绕不…...

碳排放混合时间窗集装箱运输调度【附算法】

✨ 长期致力于集装箱运输VRP、混合时间窗、碳排放、多目标优化、NSGA-Ⅱ、蚁群算法研究工作&#xff0c;擅长数据搜集与处理、建模仿真、程序编写、仿真设计。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流&#xff0c;点击《获取方式》 &#xff08;1&#xff09;经济性与紧急性双目…...

Otter多模态大模型实战:从架构解析到部署应用的完整指南

1. 项目概述&#xff1a;当多模态大模型学会“看”与“说”最近在开源社区里&#xff0c;一个名为Otter的多模态大模型项目引起了我的注意。它来自EvolvingLMMs-Lab&#xff0c;这个实验室的名字就很有意思&#xff0c;“Evolving LMMs”—— 进化中的大型多模态模型。Otter 这…...

训练篇第9节:FlashAttention深度解析(一)——原理与CUDA实现

从 O(N) 到 O(N),FlashAttention 用一记“IO感知”的巧劲,彻底解锁了Transformer处理超长序列的能力 前言 回溯整个训练篇,我们已经系统性地打怪升级:从显存优化的“三板斧”(梯度累积、激活重计算、碎片化管理),到分布式训练的并行策略(数据并行、模型并行、流水线并…...