自学内容网 自学内容网

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

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

最终效果:

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


原文地址:https://blog.csdn.net/Supreme_Sir/article/details/142662057

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!