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

满填充透明背景二维码生成

前几天项目上线的时候发现一个问题:通过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;}
}

最终效果:

在这里插入图片描述
在这里插入图片描述
---------------------------------- 只能活一次的人生当然要比谁都炽热,浑浑噩噩谁也可以。 ---------------------------------

相关文章:

满填充透明背景二维码生成

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

Python | Leetcode Python题解之第452题用最少数量的箭引爆气球

题目&#xff1a; 题解&#xff1a; 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 | 二叉树&#xff1a;二叉搜索树中的插入操作&&删除二叉搜索树中的节点&&修剪二叉搜索树 主要学习内容&#xff1a; 二叉搜索树的插入删除操作 701.二叉搜索树中的插入操作 701. 二叉搜索树中的插入操作 - 力扣&#xff08;LeetCode&…...

使用Apifox创建接口文档,部署第一个简单的基于Vue+Axios的前端项目

前言 在当今软件开发的过程中&#xff0c;接口文档的创建至关重要&#xff0c;它不仅能够帮助开发人员更好地理解系统架构&#xff0c;还能确保前后端开发的有效协同。Apifox作为一款集API文档管理、接口调试、Mock数据模拟为一体的工具&#xff0c;能够大幅度提高开发效率。在…...

TCP的第三次握手没有回复,会出现哪些问题现象

从三次握手的一开始来讲&#xff0c;刚开始客户端和服务器都处于close状态 这里不能是2次握手的原因就在于&#xff0c;服务器端即女孩子&#xff0c;无法确认客户端即男孩子&#xff0c;是否已经收到了&#xff0c;我也愿意建立连接即我也爱你&#xff0c;这一条最终确认的信息…...

【工具】arxiv_latex_cleaner 去除latex注释

https://github.com/google-research/arxiv-latex-cleaner/issues/24 文章目录 1.修改编码2.如何安装2.1.打包2.2.安装 3.测试功能 注意&#xff1a;需要创建python3.9的环境 1.修改编码 官方提供的arxiv_latex_cleaner的编码格式是有问题的&#xff0c;见这里。这个有位朋友说…...

macOS开发环境配置与应用开发

一、macOS开发环境配置 1. 安装Xcode Xcode 是Apple官方开发环境工具&#xff0c;用于macOS、iOS、watchOS和tvOS应用开发。它集成了代码编辑、编译、调试、性能分析、界面设计等功能。 下载与安装&#xff1a; 打开 App Store&#xff0c;搜索“Xcode”。 点击安装&#xff…...

15分钟学 Python :编程工具 Idea 和 vscode 中配置 Python ( 补充 )

编程工具配置 Python 在 IDE 和 VSCode 中 在编程学习的过程中&#xff0c;选择合适的开发工具至关重要。本文将详细介绍在两种流行的IDE&#xff08;IntelliJ IDEA 和 Visual Studio Code&#xff09;中如何配置Python环境&#xff0c;帮助你更高效地进行Python开发。 一、编…...

MyBatis 如何实现延迟加载?深度探讨 MyBatis 的延迟加载:如何优化数据访问效率

在当今的应用程序开发中&#xff0c;尤其是与数据库交互时&#xff0c;性能成为了重中之重。频繁的数据库访问会导致响应时间变慢&#xff0c;甚至影响用户体验。为了优化数据访问&#xff0c;MyBatis 提供了延迟加载&#xff08;Lazy Loading&#xff09;的强大功能。本文将详…...

springboot系列--web相关知识探索三

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

AI冲击下的编程职业未来:你缺的不是技术,而是跨学科思维!

随着AIGC技术&#xff08;如ChatGPT、MidJourney、Claude等大语言模型&#xff09;的不断进化&#xff0c;AI辅助编程工具迅速普及&#xff0c;程序员的工作方式正在经历前所未有的转型。代码自动补全、智能化代码生成等功能大幅提升了工作效率&#xff0c;但与此同时&#xff…...

是否是 2 的幂次方

给你一个整数 n&#xff0c;请你判断该整数是否是 2 的幂次方。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 如果存在一个整数 x 使得 n 2x &#xff0c;则认为 n 是 2 的幂次方。 示例 1&#xff1a; 输入&#xff1a;n 1 输出&#xff1a;tr…...

音视频入门

一个视频&#xff0c;一秒内普遍大于等于25帧。 入门知识&#xff1a; 1.帧&#xff0c;一张画面就是一帧。一个视频就是由许许多多帧组成的。 帧率&#xff0c;单位时间内帧的数量。单位&#xff1a;帧/秒 或 fps。 分类&#xff1a;I帧&#xff0c;P帧&#xff0c;B帧 I…...

C++随心记 续一

C中的模板 在其它语言中如Java或者C#中可能叫做泛型&#xff0c;在C中为模板&#xff0c;泛型的限制通常比模板多。模板可以解决多次的代码重复问题&#xff0c;如以下场景 #include <iostream> #include <string>void print(int value) {std::cout << val…...

消息中间件:RabbitMQ

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

sql-labs:42~65

less42&#xff08;单引号闭合、报错回显&#xff09; 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 是一个集成速度快且功能丰富的数学公式渲染库&#xff0c;专为 Web 设计。它由 Khan Academy 开发&#xff0c;提供接近印刷品质的数学公式展示&#xff0c;同时保持与浏览器的高效互动性。KaTeX 特点包括快速渲染速度、高质量的输出、独立运行、跨平…...

算法训练营打卡Day19

目录 1.二叉搜索树的最近公共祖先 2.二叉树中的插入操作 3.删除二叉搜索树中的节点 题目1、二叉搜索树的最近公共祖先 力扣题目链接(opens new window) 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有…...

H.264编解码工具 - FFmpeg

一、简介 FFmpeg是一款用于处理多媒体数据的开源软件,可以完成音频、视频和多媒体流的编解码、转码、解码、录制、流媒体播放等功能。它提供了丰富的命令行工具和库函数,适用于各种平台和操作系统。 FFmpeg支持多种常见的音视频格式,包括MP3、WAV、FLAC、MP4、AVI、MKV等。它…...

60 序列到序列学习(seq2seq)_by《李沐:动手学深度学习v2》pytorch版

系列文章目录 文章目录 系列文章目录一、理论知识比喻机器翻译Seq2seq编码器-解码器细节训练衡量生成序列的好坏的BLEU(值越大越好)总结 二、代码编码器解码器损失函数训练预测预测序列的评估小结练习 一、理论知识 比喻 seq2seq就像RNN的转录工作一样&#xff0c;非常形象的比…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

USB Over IP专用硬件的5个特点

USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中&#xff0c;从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备&#xff08;如专用硬件设备&#xff09;&#xff0c;从而消除了直接物理连接的需要。USB over IP的…...

回溯算法学习

一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

并发编程 - go版

1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程&#xff0c;系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...

MySQL 部分重点知识篇

一、数据库对象 1. 主键 定义 &#xff1a;主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 &#xff1a;确保数据的完整性&#xff0c;便于数据的查询和管理。 示例 &#xff1a;在学生信息表中&#xff0c;学号可以作为主键&#xff…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...