满填充透明背景二维码生成
前几天项目上线的时候发现一个问题:通过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的转录工作一样,非常形象的比…...

python打卡day49
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...

HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...

srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...