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

news2024/12/28 20:50:47

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

    @ToString
    public 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 == qrHeight
        int 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 barcode
            for (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;
    }
}

最终效果:

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

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

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

相关文章

RuoYi-Vue实现后台管理系统去掉首页/默认跳转动态路由第一个路由

云风网 云风笔记 云风知识库 RuoYi-Vue 是一个 Java EE 企业级快速开发平台&#xff0c;基于SpringBoot、Spring Security、Jwt、Vue的前后端分离的后台管理系统 内置模块如&#xff1a;部门管理、角色用户、菜单及按钮授权、数据权限、系统参数、日志管理、代码生成等。在线定…

【代码实现】opencv 高斯模糊和pytorch 高斯模糊

wiki百科 Gaussian Blur&#xff0c;也叫高斯平滑&#xff0c;是在Adobe Photoshop、GIMP以及Paint.NET等图像处理软件中广泛使用的处理效果&#xff0c;通常用它来减少图像噪声以及降低细节层次。 opencv实现 opencv实现高斯滤波有两种方式&#xff0c; 1、是使用自带的cv2…

怎样把两个视频合并成一个视频?批量合并视频看好这6个工具!

★ 怎样把两个视频合并成一个视频&#xff1f; 随着视频内容的日益丰富&#xff0c;我们常常需要将多个视频片段合并成一个完整的视频文件&#xff0c;不管将2个视频合并拼接到一个进行播放&#xff0c;还是直接合并2个短视频变成长视频&#xff0c;都可以通过这6个工具进行处…

个人项目简单https服务配置

1.SSL简介 SSL证书是一种数字证书&#xff0c;由受信任的证书颁发机构&#xff08;CA&#xff09;颁发&#xff0c;用于在互联网通信中建立加密链接。SSL代表“安全套接层”&#xff0c;是用于在互联网上创建加密链接的协议。SSL证书的主要目的是确保数据传输的安全性和隐私性…

PWA(Progressive web APPs,渐进式 Web 应用): manifest.json、 Service Worker

文章目录 引言I 什么是 PWA功能特性技术上分为三个部分安装应用II Web 应用清单将Web 应用清单文件链接到站点manifest.json字段说明III Service Worker( 缓存管理)IV 结合构建工具让项目支持 PWA应用使用插件vite-plugin-pwaworkbox-webpack-plugin插件扩展知识将 PWA 作为脱机…

dwceqos网络驱动性能优化

文章介绍 本文会介绍优化QNX系统下io-pkt-v6-hc驱动模块cpu loading过高问题&#xff0c;经过优化可以降低约一半的cpu loading. 问题背景 激光雷达通过以太网发送数据到ADAS域控中&#xff0c;测试发现在激光功能激活的情况下&#xff0c;会出现比较明显的网络丢帧现象。 …

HT8731 内置自适应H类升压和防破音功能的10W D类及AB类音频功率放大器

1、特点 防削顶失真功能(防破音,Anti-Clipping Function, ACF) 免滤波器数字调制&#xff0c;直接驱动扬声器 输出功率 10W(VBAT4.2V,RL3Ω,THDN10%, fiN 1kHz) 6W(VBAT3.3~4.2V,RL4Ω,THDN<1%,20-20kHz 全频段) 3W (VBAT3.3~4.2V,RL8Ω, THDN<1%, 20- 20kHz 全频段 VB…

DPLL的DCO与PLL的VCO(数控振荡器与压控振荡器)

1.概述 无论DPLL还是PLL都是由PD&#xff0c;LPF和DCO/VCO组成。 PD&#xff1a;鉴相器&#xff0c;是将VCO/DCO输出的频率信号分频后与refclk进行相位比对&#xff0c;输出一个相位差信号 LFP&#xff1a;是将相位差信号转换为VCO的压控信号或DCO的延迟信号。 VCO&#xf…

js将对象的键和值分别归纳进对象,并将多层对象转化成数据的方法

前言&#xff1a; 后端传给我一个没有处理过的json串&#xff0c;但是我要传入el-tree做渲染&#xff0c;此篇来记录一下整个数据处理过程以及el-tree的使用 需求描述&#xff1a; 一、树结构可以展开可以收缩&#xff0c;默认全部展开 二、有一些关键词需要高亮展示红色 …

第十三届蓝桥杯真题Python c组D.数位排序(持续更新)

博客主页&#xff1a;音符犹如代码系列专栏&#xff1a;蓝桥杯关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 问题描述 小蓝对一个数的数位之和很感兴趣, 今天他要按照数位之和给数排序。…

Knots_3D 9.3.0 一款教你绑绳结的手机应用

Knots 3D (3D绳结)是一款教你绑绳结的手机应用&#xff0c;可以掌握一些必备的绳结系法&#xff0c;拥有 120 个 3D 效果的绳结&#xff0c;教你系上、解开&#xff0c;户外爱好者必备。Knots 3D已经被全世界的园艺师、渔民、消防员、登山者、军人和童子军使用&#xff0c;它将…

9.2 Linux_I/O_标准I/O相关函数

打开与关闭 文件打开就是判断这个文件资源可不可以被占用&#xff0c;如果可以&#xff0c;就能够打开成功&#xff0c;否则打开失败 文件关闭就是释放文件资源 1、打开文件 1.1 函数声明 FILE *fopen(const char *pathname, const char *mode); 返回值&#xff1a;出错返…

永不失联!遨游双卫星三防手机成为高效应急关键所在

今年9月被戏称为“台风月”&#xff0c;台风“摩羯”、“贝碧嘉”以及热带气旋“普拉桑”接连来袭&#xff0c;极端天气不仅导致了电力中断、道路损毁&#xff0c;更使得传统的通信网络遭受重创&#xff0c;给应急通信保障工作带来了极大的压力。面对“三断”的实战难题&#x…

铰链+屏幕齐发力,告诉你 Mate XT 是如何让折痕变得“无存在感”

“折痕”是折叠手机永恒的话题&#xff0c;每一款折叠屏手机产品的问世&#xff0c;都逃不过对折痕的关注和讨论。 为什么会存在折痕&#xff1f; 材料在弯折的状态下会受到力的作用&#xff0c;在内部产生“压缩”的应力和“拉伸”的应力&#xff0c;这些应力集中在弯折的区…

pytorch线性/非线性回归拟合

一、线性回归 1. 导入依赖库 import numpy as np import matplotlib.pyplot as plt import torch from torch import nn, optim from torch.autograd import Variable numpy&#xff1a;用来构建数据matplotlib.pyplot&#xff1a; 将构建好的数据可视化torch.nn&#xff1a…

《向量数据库指南》——Fivetran 的 Partner SDK:构建自定义连接器和目标

哈哈,说到 Fivetran 的 Partner SDK,这可真是个好东西啊!作为向量数据库领域的“老司机”,我今天就来给大家详细讲讲这个 SDK 的厉害之处,以及如何用它来构建自定义连接器和目标,实现与 Fivetran 自动化数据移动平台的无缝集成。 一、Fivetran Partner SDK:开启自定义连…

二叉树深搜专题篇

目录 计算布尔二叉树的值 求根节点到叶节点数字之和 二叉树剪枝 验证二叉搜索树 二叉搜索树中第K小的元素 二叉树的所有路径 计算布尔二叉树的值 题目 思路 这道题其实是比较简单的&#xff0c;对二叉树来一次后序遍历即可&#xff0c;当遇到叶子结点直接返回叶子节点中…

干部画像系统怎么实现人岗智能匹配的?

人岗匹配的核心在于实现“岗得其人”和“人适其岗”&#xff0c;即根据不同人的个体特征将不同的人安排在各自最合适的岗位上&#xff0c;达到人尽其才的目标。干部画像系统作为一种辅助领导智慧识才的工具&#xff0c;通过集成多种技术手段和分析方法&#xff0c;对干部的定性…

【代码实现】torch实现F.pixel_shuffle和F.pixel_unshuffle

原理 pixel_shuffle 和 pixel_unshuffle 常用于神经网络减少特征图尺寸以减少计算量&#xff0c;由于有些硬件不支持这两个算子&#xff0c;可以根据原理使用torch实现。 代码实现 import torch.nn.functional as F import torch def pixelshuffle_inv(tensor, scale2):N, c…

C++详解vector

目录 构造和拷贝构造 赋值运算符重载&#xff1a; vector的编辑函数&#xff1a; assign函数&#xff1a; push_back和pop_back函数&#xff1a; insert函数&#xff1a; erase函数&#xff1a; swap函数&#xff1a; clear函数&#xff1a; begin函数&#xff1a; e…