Zxing 生成二维码

pom.xml

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.15</version>
</dependency>
<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>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.34</version>
</dependency>

ZxingUtils

import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import com.google.zxing.EncodeHintType;
import com.google.zxing.client.j2se.MatrixToImageConfig;
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 javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * 二维码生成
 *
 * @author baihb
 * @since 2024/9/26
 */
public class ZxingUtils {

    /**
     * hutool 生成 base64
     */
    public static String generateAsBase64(String content, String logoPath) {
        QrConfig config = new QrConfig(100, 100);
        config.setMargin(1);
        config.setErrorCorrection(ErrorCorrectionLevel.H);
        config.setImg(logoPath);
        return QrCodeUtil.generateAsBase64(content, config, "png");
    }


    /**
     * hutool 生成 file
     */
    public static void generateAsFile(String content, String filePath) {
        QrCodeUtil.generate(content, 100, 100, FileUtil.file(filePath));
    }


    /**
     * zxing 生成 base64, 去除白边
     */
    public static String generateZxingAsBase64(String content, String logoPath) {
        try {
            QrCodeConfig config = buildConfig(content, logoPath, 100, 100);
            int quietZone = 1;
            if (quietZone > 4) {
                quietZone = 4;
            } else if (quietZone < 0) {
                quietZone = 0;
            }
            Map<EncodeHintType, Object> hints = new HashMap<>();
            hints.put(EncodeHintType.ERROR_CORRECTION, config.getErrorCorrection().getBits());
            hints.put(EncodeHintType.CHARACTER_SET, config.getCode());
            hints.put(EncodeHintType.MARGIN, config.getMargin());
            QRCode code = Encoder.encode(config.getContent(), config.getErrorCorrection(), hints);
            BitMatrix bitMatrix = renderResult(code, config.getWidth(), config.getHeight(), quietZone);
            BufferedImage img = toBufferedImage(config, bitMatrix);
            return ImgUtil.toBase64DataUri(img, config.getPicType());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * zxing 生成 file, 去除白边
     */
    public static void generateZxingAsFile(String content, String filePath, String logoPath) throws IOException {
        String base64 = generateZxingAsBase64(content, logoPath);
        // 去掉 Base64 字符串的前缀部分(如果有的话)
        if (base64.startsWith("data:image/png;base64,")) {
            base64 = base64.substring("data:image/png;base64,".length());
        }
        // 解码Base64字符串
        byte[] imageBytes = Base64.getDecoder().decode(base64);
        // 将字节数组写入文件
        try (FileOutputStream fos = new FileOutputStream(filePath)) {
            fos.write(imageBytes);
            fos.flush();
        }
    }


    /**
     * 构建配置
     *
     * @param content 二维码内容
     * @param logo    中间 logo
     * @param width   宽度 -> 默认 200
     * @param height  高度 -> 默认 200
     */
    private static QrCodeConfig buildConfig(String content, String logo, Integer width, Integer height) {
        QrCodeConfig qrConfig = new QrCodeConfig();
        qrConfig.setContent(content);
        qrConfig.setLogo(logo);
        qrConfig.setWidth(width == null ? 200 : width);
        qrConfig.setHeight(height == null ? 200 : height);
        qrConfig.setPicType("png");
        qrConfig.setMargin(1);
        qrConfig.setErrorCorrection(ErrorCorrectionLevel.H);
        qrConfig.setOnColor(MatrixToImageConfig.BLACK);
        qrConfig.setOffColor(MatrixToImageConfig.WHITE);
        qrConfig.setCode("UTF-8");
        return qrConfig;
    }


    /**
     * 对 zxing 的 QRCodeWriter 进行扩展, 解决白边过多的问题
     *
     * @param width     高
     * @param height    宽
     * @param quietZone 取值 [0, 4]
     */
    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) {
            int padding, tmpValue;
            // 计算边框留白
            padding = (minSize - qrWidth * scale) / 4 * 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) {
            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;
        if (abs < expectSize * 0.15) {
            return 0;
        }
        return scale;
    }


    /**
     * 根据二维码配置 & 二维码矩阵生成二维码图片
     */
    private static BufferedImage toBufferedImage(QrCodeConfig config, BitMatrix bitMatrix) {
        int qrCodeWidth = bitMatrix.getWidth();
        int qrCodeHeight = bitMatrix.getHeight();
        BufferedImage qrCode = new BufferedImage(qrCodeWidth, qrCodeHeight, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < qrCodeWidth; x++) {
            for (int y = 0; y < qrCodeHeight; y++) {
                qrCode.setRGB(x, y, bitMatrix.get(x, y) ? config.getOnColor() : config.getOffColor());
            }
        }
        // 插入logo
        if (StrUtil.isNotBlank(config.getLogo())) {
            insertLogo(qrCode, config.getLogo());
        }
        // 若二维码的实际宽高和预期的宽高不一致, 则缩放
        int realQrCodeWidth = config.getWidth();
        int realQrCodeHeight = config.getHeight();
        if (qrCodeWidth != realQrCodeWidth || qrCodeHeight != realQrCodeHeight) {
            BufferedImage tmp = new BufferedImage(realQrCodeWidth, realQrCodeHeight, BufferedImage.TYPE_INT_RGB);
            tmp.getGraphics().drawImage(qrCode.getScaledInstance(realQrCodeWidth, realQrCodeHeight, Image.SCALE_SMOOTH), 0, 0, null);
            qrCode = tmp;
        }
        return qrCode;
    }


    /**
     * 在图片中间,插入圆角的 logo
     *
     * @param qrCode 原图
     * @param logo   logo地址
     */
    private static void insertLogo(BufferedImage qrCode, String logo) {
        // 获取logo图片
        BufferedImage bf = getImageByPath(logo);
        if (bf != null) {
            int QRCODE_WIDTH = qrCode.getWidth();
            int QRCODE_HEIGHT = qrCode.getHeight();
            int size = bf.getWidth() > QRCODE_WIDTH * 2 / 10 ? QRCODE_WIDTH * 2 / 50 : bf.getWidth() / 5;
            // 边距为二维码图片的1/10
            bf = makeRoundBorder(bf, 60, size, Color.WHITE);
            // logo的宽高
            int w = Math.min(bf.getWidth(), QRCODE_WIDTH * 2 / 10);
            int h = Math.min(bf.getHeight(), QRCODE_HEIGHT * 2 / 10);
            // 插入LOGO
            Graphics2D graph = qrCode.createGraphics();
            int x = (QRCODE_WIDTH - w) / 2;
            int y = (QRCODE_HEIGHT - h) / 2;
            graph.drawImage(bf, x, y, w, h, null);
            graph.dispose();
            bf.flush();
        }
    }


    /**
     * 根据路径图片图片
     *
     * @param path 本地路径 or 网络地址
     * @return 图片
     */
    private static BufferedImage getImageByPath(String path) {
        try {
            if (path.startsWith("http")) {
                // 从网络获取logo
                return ImageIO.read(new URL(path));
            } else {
                // 从本地路径获取logo
                return ImageIO.read(new File(path));
            }
        } catch (IOException e) {
            System.out.println("logo格式或路径不正确");
            return null;
        }
    }


    /**
     * 生成圆角图片 & 圆角边框
     *
     * @param image        原图
     * @param cornerRadius 圆角的角度
     * @param size         边框的边距
     * @param color        边框的颜色
     * @return 返回带边框的圆角图
     */
    private static BufferedImage makeRoundBorder(BufferedImage image, int cornerRadius, int size, Color color) {
        // 将图片变成圆角
        image = makeRoundedCorner(image, cornerRadius);
        int borderSize = size << 1;
        int w = image.getWidth() + borderSize;
        int h = image.getHeight() + borderSize;
        BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = output.createGraphics();
        g2.setComposite(AlphaComposite.Src);
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setColor(color == null ? Color.WHITE : color);
        g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius, cornerRadius));
        g2.setComposite(AlphaComposite.SrcAtop);
        g2.drawImage(image, size, size, null);
        g2.dispose();
        return output;
    }


    /**
     * 生成圆角图片
     *
     * @param image        原始图片
     * @param cornerRadius 圆角的弧度
     * @return 返回圆角图
     */
    private static BufferedImage makeRoundedCorner(BufferedImage image, int cornerRadius) {
        int w = image.getWidth();
        int h = image.getHeight();
        BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2 = output.createGraphics();
        g2.setComposite(AlphaComposite.Src);
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setColor(Color.WHITE);
        g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius, cornerRadius));
        g2.setComposite(AlphaComposite.SrcAtop);
        g2.drawImage(image, 0, 0, null);
        g2.dispose();
        return output;
    }
}

QrCodeConfig

import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import lombok.Data;

/**
 * 二维码配置信息
 */
@Data
public class QrCodeConfig {

    /**
     * 二维码的信息
     */
    private String content;

    /**
     * 二维码中间的 logo
     */
    private String logo;

    /**
     * 生成二维码的宽
     */
    private Integer width;

    /**
     * 生成二维码的高
     */
    private Integer height;

    /**
     * 生成二维码图片的格式: png, jpg
     */
    private String picType;

    /**
     * 边界大小: 0 -4
     */
    private Integer margin;

    /**
     * 容错级别: M,L,H,Q四个等级由低到高
     */
    private ErrorCorrectionLevel errorCorrection;

    /**
     * 前景色
     */
    private Integer onColor;

    /**
     * 背景色
     */
    private Integer offColor;

    /**
     * 编码
     */
    private String code;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值