满填充透明背景二维码生成
前几天项目上线的时候发现一个问题:通过Hutool工具包生成的二维码在内容较少时无法填满(Margin 已设置为 0)给定大小的图片。因此导致前端在显示二维码时样式异常。
从图片中我们可以看到,相同大小的图片,留白内容是不一样的。其中上半部分的图片是一个短字符串,下半部分的图片是一个长的字符串。因此基于Hutool包进行了裁边和缩放。代码如下:
Maven配置
<dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.3.3</version>
</dependency>
<dependency><groupId>com.google.zxing</groupId><artifactId>javase</artifactId><version>3.3.3</version>
</dependency>
QrCodeConfig.java
import com.google.zxing.EncodeHintType;
import com.google.zxing.client.j2se.MatrixToImageConfig;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;import java.awt.*;
import java.util.HashMap;
import java.util.Map;/*** 二维吗配置信息*/
@Getter
@Setter
@ToString
public class QrCodeConfig {/*** 塞入二维码的信息*/private String msg;/*** 生成二维码的宽*/private Integer w;/*** 生成二维码的高*/private Integer h;/*** 生成二维码的颜色*/private MatrixToImageConfig matrixToImageConfig;private Map<EncodeHintType, Object> hints;@ToStringpublic static class QrCodeConfigBuilder {/*** The message to put into QrCode*/private String msg;/*** qrcode image width*/private Integer w;/*** qrcode image height*/private Integer h;/*** qrcode message's code, default UTF-8*/private String code;/*** 0 - 4*/private Integer padding;/*** error level, default H*/private ErrorCorrectionLevel errorCorrection;public String getMsg() {return msg;}public QrCodeConfigBuilder setMsg(String msg) {this.msg = msg;return this;}public Integer getW() {return w == null ? (h == null ? 200 : h) : w;}public QrCodeConfigBuilder setW(Integer w) {if (w != null && w < 0) {throw new IllegalArgumentException("???????????0");}this.w = w;return this;}public Integer getH() {if (w != null && w < 0) {throw new IllegalArgumentException("???????????0");}return h == null ? (w == null ? 200 : w) : h;}public QrCodeConfigBuilder setH(Integer h) {this.h = h;return this;}public String getCode() {return code == null ? "UTF-8" : code;}public QrCodeConfigBuilder setCode(String code) {this.code = code;return this;}public Integer getPadding() {if (padding == null) {return 1;}if (padding < 0) {return 0;}if (padding > 4) {return 4;}return padding;}public QrCodeConfigBuilder setPadding(Integer padding) {this.padding = padding;return this;}public ErrorCorrectionLevel getErrorCorrection() {return errorCorrection == null ? ErrorCorrectionLevel.H : errorCorrection;}public QrCodeConfigBuilder setErrorCorrection(ErrorCorrectionLevel errorCorrection) {this.errorCorrection = errorCorrection;return this;}private void validate() {if (msg == null || msg.length() == 0) {throw new IllegalArgumentException("????????????!");}}private QrCodeConfig create() {this.validate();QrCodeConfig qrCodeConfig = new QrCodeConfig();qrCodeConfig.setMsg(getMsg());qrCodeConfig.setH(getH());qrCodeConfig.setW(getW());Map<EncodeHintType, Object> hints = new HashMap<>(3);hints.put(EncodeHintType.ERROR_CORRECTION, this.getErrorCorrection());hints.put(EncodeHintType.CHARACTER_SET, this.getCode());hints.put(EncodeHintType.MARGIN, this.getPadding());qrCodeConfig.setHints(hints);qrCodeConfig.setMatrixToImageConfig(new MatrixToImageConfig(new Color(0, 0, 0, 255).getRGB(),new Color(0, 0, 0, 0).getRGB()));return qrCodeConfig;}/*** create qrcodeConfig** @return 返回构造的 QrCodeConfig 对象*/public QrCodeConfig build() {return create();}}
}
MatrixToImageUtil.java
import com.google.zxing.common.BitMatrix;
import java.awt.*;
import java.awt.image.BufferedImage;public class MatrixToImageUtil {public static BufferedImage toBufferedImage(QrCodeConfig qrCodeConfig, BitMatrix bitMatrix) {int qrCodeWidth = bitMatrix.getWidth();int qrCodeHeight = bitMatrix.getHeight();BufferedImage qrCode = new BufferedImage(qrCodeWidth, qrCodeHeight, BufferedImage.TYPE_INT_ARGB);int onColor = qrCodeConfig.getMatrixToImageConfig().getPixelOnColor();int offColor = qrCodeConfig.getMatrixToImageConfig().getPixelOffColor();for (int x = 0; x < qrCodeWidth; x++) {for (int y = 0; y < qrCodeHeight; y++) {boolean pixelOn = bitMatrix.get(x, y);int pixelColor = pixelOn ? onColor : offColor;// 设置透明度int alpha = pixelOn ? 255 : 0;Color color = new Color(pixelColor, true);Color colorWithAlpha = new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);qrCode.setRGB(x, y, colorWithAlpha.getRGB());}}// 缩放二维码图片int realQrCodeWidth = qrCodeConfig.getW();int realQrCodeHeight = qrCodeConfig.getH();if (qrCodeWidth != realQrCodeWidth || qrCodeHeight != realQrCodeHeight) {BufferedImage tmp = new BufferedImage(realQrCodeWidth, realQrCodeHeight, BufferedImage.TYPE_INT_ARGB);tmp.getGraphics().drawImage(qrCode.getScaledInstance(realQrCodeWidth, realQrCodeHeight, Image.SCALE_SMOOTH),0, 0, null);qrCode = tmp;}return qrCode;}
}
QrCodeGenWrapper.java
import cn.hutool.core.img.ImgUtil;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.google.zxing.qrcode.encoder.ByteMatrix;
import com.google.zxing.qrcode.encoder.Encoder;
import com.google.zxing.qrcode.encoder.QRCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Map;/*** 对 zxing 的 QRCodeWriter 进行扩展, 解决白边过多的问题,原参考 <a href="https://my.oschina.net/u/566591/blog/872770">...</a>*/
@Slf4j
public class QrCodeGenWrapper {private static final Logger logger = LoggerFactory.getLogger(QrCodeGenWrapper.class);private static final int QUIET_ZONE_SIZE = 4;/*** 构造 二维吗配置信息* @return QrCodeConfig*/public static QrCodeConfig.QrCodeConfigBuilder createQrCodeConfig() {return new QrCodeConfig.QrCodeConfigBuilder();}/*** 生成base64格式二维吗* @param content 二维吗内容* @param width 宽度 默认 300* @param height 高度 默认 300* @param imageType 图片类型默认 png* @return 返回base64格式二维码信息*/public static String generateAsBase64(String content, Integer width, Integer height, String imageType){QrCodeConfig qrConfig = QrCodeGenWrapper.createQrCodeConfig().setMsg(content).setH(width == null? 300 : width).setW(height == null? 300 : height).setPadding(0).setErrorCorrection(ErrorCorrectionLevel.L).build();try {return ImgUtil.toBase64DataUri(asBufferedImage(qrConfig), StringUtils.isBlank(imageType)? "png" : imageType);} catch (Exception e) {log.error("QrCodeGenWrapper.generateAsBase64 error", e);throw new RuntimeException("QrCodeGenWrapper.generateAsBase64 生成二维码异常");}}public static BufferedImage asBufferedImage(QrCodeConfig qrCodeConfig) throws WriterException, IOException {BitMatrix bitMatrix = encode(qrCodeConfig);return MatrixToImageUtil.toBufferedImage(qrCodeConfig, bitMatrix);}/*** 对 zxing 的 QRCodeWriter 进行扩展, 解决白边过多的问题* <p/>* 源码参考 {@link com.google.zxing.qrcode.QRCodeWriter#encode(String, BarcodeFormat, int, int, Map)}*/private static BitMatrix encode(QrCodeConfig qrCodeConfig) throws WriterException {ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L;int quietZone = 1;if (qrCodeConfig.getHints() != null) {if (qrCodeConfig.getHints().containsKey(EncodeHintType.ERROR_CORRECTION)) {errorCorrectionLevel = ErrorCorrectionLevel.valueOf(qrCodeConfig.getHints().get(EncodeHintType.ERROR_CORRECTION).toString());}if (qrCodeConfig.getHints().containsKey(EncodeHintType.MARGIN)) {quietZone = Integer.parseInt(qrCodeConfig.getHints().get(EncodeHintType.MARGIN).toString());}if (quietZone > QUIET_ZONE_SIZE) {quietZone = QUIET_ZONE_SIZE;} else if (quietZone < 0) {quietZone = 0;}}QRCode code = Encoder.encode(qrCodeConfig.getMsg(), errorCorrectionLevel, qrCodeConfig.getHints());return renderResult(code, qrCodeConfig.getW(), qrCodeConfig.getH(), quietZone);}/*** 对 zxing 的 QRCodeWriter 进行扩展, 解决白边过多的问题* <p/>* 源码参考** @param code {@link QRCode}* @param width 高* @param height 宽* @param quietZone 取值 [0, 4]* @return {@link BitMatrix}*/private static BitMatrix renderResult(QRCode code, int width, int height, int quietZone) {ByteMatrix input = code.getMatrix();if (input == null) {throw new IllegalStateException();}// xxx 二维码宽高相等, 即 qrWidth == qrHeightint inputWidth = input.getWidth();int inputHeight = input.getHeight();int qrWidth = inputWidth + (quietZone * 2);int qrHeight = inputHeight + (quietZone * 2);// 白边过多时, 缩放int minSize = Math.min(width, height);int scale = calculateScale(qrWidth, minSize);if (scale > 0) {if (logger.isDebugEnabled()) {logger.debug("qrCode scale enable! scale: {}, qrSize:{}, expectSize:{}x{}", scale, qrWidth, width, height);}int padding, tmpValue;// 计算边框留白padding = (minSize - qrWidth * scale) / QUIET_ZONE_SIZE * quietZone;tmpValue = qrWidth * scale + padding;if (width == height) {width = tmpValue;height = tmpValue;} else if (width > height) {width = width * tmpValue / height;height = tmpValue;} else {height = height * tmpValue / width;width = tmpValue;}}int outputWidth = Math.max(width, qrWidth);int outputHeight = Math.max(height, qrHeight);int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight);int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;int topPadding = (outputHeight - (inputHeight * multiple)) / 2;BitMatrix output = new BitMatrix(outputWidth, outputHeight);for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) {// Write the contents of this row of the barcodefor (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {if (input.get(inputX, inputY) == 1) {output.setRegion(outputX, outputY, multiple, multiple);}}}return output;}/*** 如果留白超过15% , 则需要缩放* (15% 可以根据实际需要进行修改)** @param qrCodeSize 二维码大小* @param expectSize 期望输出大小* @return 返回缩放比例, <= 0 则表示不缩放, 否则指定缩放参数*/private static int calculateScale(int qrCodeSize, int expectSize) {if (qrCodeSize >= expectSize) {return 0;}int scale = expectSize / qrCodeSize;int abs = expectSize - scale * qrCodeSize;// 在这里配置超过多少留白,则进行缩放(这里已经把 0.15 改成 0 了)if (abs < 0) {return 0;}return scale;}
}
最终效果:
---------------------------------- 只能活一次的人生当然要比谁都炽热,浑浑噩噩谁也可以。 ---------------------------------
相关文章:

满填充透明背景二维码生成
前几天项目上线的时候发现一个问题:通过Hutool工具包生成的二维码在内容较少时无法填满(Margin 已设置为 0)给定大小的图片。因此导致前端在显示二维码时样式异常。 从图片中我们可以看到,相同大小的图片,留白内容是不一样的。其中上半部分…...

Python | Leetcode Python题解之第452题用最少数量的箭引爆气球
题目: 题解: class Solution:def findMinArrowShots(self, points: List[List[int]]) -> int:if not points:return 0points.sort(keylambda balloon: balloon[1])pos points[0][1]ans 1for balloon in points:if balloon[0] > pos:pos balloo…...

代码随想录 | Day26 | 二叉树:二叉搜索树中的插入操作删除二叉搜索树中的节点修剪二叉搜索树
代码随想录 | Day26 | 二叉树:二叉搜索树中的插入操作&&删除二叉搜索树中的节点&&修剪二叉搜索树 主要学习内容: 二叉搜索树的插入删除操作 701.二叉搜索树中的插入操作 701. 二叉搜索树中的插入操作 - 力扣(LeetCode&…...

使用Apifox创建接口文档,部署第一个简单的基于Vue+Axios的前端项目
前言 在当今软件开发的过程中,接口文档的创建至关重要,它不仅能够帮助开发人员更好地理解系统架构,还能确保前后端开发的有效协同。Apifox作为一款集API文档管理、接口调试、Mock数据模拟为一体的工具,能够大幅度提高开发效率。在…...

TCP的第三次握手没有回复,会出现哪些问题现象
从三次握手的一开始来讲,刚开始客户端和服务器都处于close状态 这里不能是2次握手的原因就在于,服务器端即女孩子,无法确认客户端即男孩子,是否已经收到了,我也愿意建立连接即我也爱你,这一条最终确认的信息…...
【工具】arxiv_latex_cleaner 去除latex注释
https://github.com/google-research/arxiv-latex-cleaner/issues/24 文章目录 1.修改编码2.如何安装2.1.打包2.2.安装 3.测试功能 注意:需要创建python3.9的环境 1.修改编码 官方提供的arxiv_latex_cleaner的编码格式是有问题的,见这里。这个有位朋友说…...
macOS开发环境配置与应用开发
一、macOS开发环境配置 1. 安装Xcode Xcode 是Apple官方开发环境工具,用于macOS、iOS、watchOS和tvOS应用开发。它集成了代码编辑、编译、调试、性能分析、界面设计等功能。 下载与安装: 打开 App Store,搜索“Xcode”。 点击安装ÿ…...

15分钟学 Python :编程工具 Idea 和 vscode 中配置 Python ( 补充 )
编程工具配置 Python 在 IDE 和 VSCode 中 在编程学习的过程中,选择合适的开发工具至关重要。本文将详细介绍在两种流行的IDE(IntelliJ IDEA 和 Visual Studio Code)中如何配置Python环境,帮助你更高效地进行Python开发。 一、编…...
MyBatis 如何实现延迟加载?深度探讨 MyBatis 的延迟加载:如何优化数据访问效率
在当今的应用程序开发中,尤其是与数据库交互时,性能成为了重中之重。频繁的数据库访问会导致响应时间变慢,甚至影响用户体验。为了优化数据访问,MyBatis 提供了延迟加载(Lazy Loading)的强大功能。本文将详…...

springboot系列--web相关知识探索三
一、前言 web相关知识探索二中研究了请求是如何映射到具体接口(方法)中的,本次文章主要研究请求中所带的参数是如何映射到接口参数中的,也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、…...

AI冲击下的编程职业未来:你缺的不是技术,而是跨学科思维!
随着AIGC技术(如ChatGPT、MidJourney、Claude等大语言模型)的不断进化,AI辅助编程工具迅速普及,程序员的工作方式正在经历前所未有的转型。代码自动补全、智能化代码生成等功能大幅提升了工作效率,但与此同时ÿ…...
是否是 2 的幂次方
给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。 如果存在一个整数 x 使得 n 2x ,则认为 n 是 2 的幂次方。 示例 1: 输入:n 1 输出:tr…...

音视频入门
一个视频,一秒内普遍大于等于25帧。 入门知识: 1.帧,一张画面就是一帧。一个视频就是由许许多多帧组成的。 帧率,单位时间内帧的数量。单位:帧/秒 或 fps。 分类:I帧,P帧,B帧 I…...
C++随心记 续一
C中的模板 在其它语言中如Java或者C#中可能叫做泛型,在C中为模板,泛型的限制通常比模板多。模板可以解决多次的代码重复问题,如以下场景 #include <iostream> #include <string>void print(int value) {std::cout << val…...

消息中间件:RabbitMQ
消息中间件:RabbitMQ 前言安装Window安装Linux安装 管理页面什么是RabbitMQ?入门基本概念简单队列工作队列(Work Queues)发布/订阅(Publish/Subscribe)临时队列 路由(Routing)主题&a…...

sql-labs:42~65
less42(单引号闭合、报错回显) login_useradmin login_password123 and if(11,sleep(2),1) # # 单引号闭合 login_useradmin login_password123and updatexml(1,concat(0x7e,database(),0x7e),1)# # 报错回显…...

KaTeX.js渲染数学公式
什么是KaTeX.js ? KaTeX 是一个集成速度快且功能丰富的数学公式渲染库,专为 Web 设计。它由 Khan Academy 开发,提供接近印刷品质的数学公式展示,同时保持与浏览器的高效互动性。KaTeX 特点包括快速渲染速度、高质量的输出、独立运行、跨平…...

算法训练营打卡Day19
目录 1.二叉搜索树的最近公共祖先 2.二叉树中的插入操作 3.删除二叉搜索树中的节点 题目1、二叉搜索树的最近公共祖先 力扣题目链接(opens new window) 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为:“对于有…...
H.264编解码工具 - FFmpeg
一、简介 FFmpeg是一款用于处理多媒体数据的开源软件,可以完成音频、视频和多媒体流的编解码、转码、解码、录制、流媒体播放等功能。它提供了丰富的命令行工具和库函数,适用于各种平台和操作系统。 FFmpeg支持多种常见的音视频格式,包括MP3、WAV、FLAC、MP4、AVI、MKV等。它…...

60 序列到序列学习(seq2seq)_by《李沐:动手学深度学习v2》pytorch版
系列文章目录 文章目录 系列文章目录一、理论知识比喻机器翻译Seq2seq编码器-解码器细节训练衡量生成序列的好坏的BLEU(值越大越好)总结 二、代码编码器解码器损失函数训练预测预测序列的评估小结练习 一、理论知识 比喻 seq2seq就像RNN的转录工作一样,非常形象的比…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...

Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
用鸿蒙HarmonyOS5实现中国象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...

Android写一个捕获全局异常的工具类
项目开发和实际运行过程中难免会遇到异常发生,系统提供了一个可以捕获全局异常的工具Uncaughtexceptionhandler,它是Thread的子类(就是package java.lang;里线程的Thread)。本文将利用它将设备信息、报错信息以及错误的发生时间都…...

6.9-QT模拟计算器
源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…...
JavaScript 标签加载
目录 JavaScript 标签加载script 标签的 async 和 defer 属性,分别代表什么,有什么区别1. 普通 script 标签2. async 属性3. defer 属性4. type"module"5. 各种加载方式的对比6. 使用建议 JavaScript 标签加载 script 标签的 async 和 defer …...