二维码生成器

发布时间 2023-04-11 19:03:56作者: 进击的小蔡鸟

主要的api

  • 生成二维码
  • 生成中间嵌套图片的二维码
  • 解析二维码

导入依赖

        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.4.1</version>
        </dependency>

代码

import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.List;
import java.util.*;

/**
 * 二维码生成器
 *
 * @author common
 */
@Slf4j
public class QrCodeUtil {

    private QrCodeUtil() {
        throw new UnsupportedOperationException("cannot be instantiated");
    }

    /**
     * 二维码尺寸List
     */
    public static final List<Integer> SIZE_LIST = Collections.unmodifiableList(Arrays.asList(258, 344, 430, 860, 1280));

    /**
     * 图片的默认设置
     */
    private static final int IMAGE_WIDTH = 100;
    private static final int IMAGE_HEIGHT = 100;
    private static final int IMAGE_HALF_WIDTH = IMAGE_WIDTH / 2;
    private static final int FRAME_WIDTH = 2;

    /**
     * 二维码写码器
     */
    private static MultiFormatWriter multiWriter = new MultiFormatWriter();

    /**
     * 生成中间嵌套图片的二维码(写入文件)
     *
     * @param content       二维码显示的文本
     * @param width         二维码的宽度
     * @param height        二维码的高度
     * @param srcImageInput 中间嵌套的图片流
     * @param destImagePath 二维码生成的地址
     */
    @SneakyThrows
    public static void writeScrQrCode(String content, int width, int height,
                                      InputStream srcImageInput, String destImagePath, String fileName) {
        BufferedImage srcImage = ImageIO.read(srcImageInput);
        //创建文件
        File file = createFile(destImagePath, fileName);
        // ImageIO.write 参数 1、BufferedImage 2、输出的格式 3、输出的文件
        //创建二维码
        BufferedImage image = genBarcode(content, width, height, srcImage);
        //写入文件
        ImageIO.write(image, "png", file);

    }

    /**
     * 生成中间嵌套图片的二维码(写入流)
     *
     * @param content       二维码显示的文本
     * @param width         二维码的宽度
     * @param height        二维码的高度
     * @param srcImageInput 中间嵌套的图片流
     * @param stream
     */
    @SneakyThrows
    public static void writeScrQrCode(String content, int width, int height,
                                      InputStream srcImageInput, OutputStream stream) {
        BufferedImage srcImage = ImageIO.read(srcImageInput);
        //创建二维码
        BufferedImage image = genBarcode(content, width, height, srcImage);
        //写入流
        ImageIO.write(image, "png", stream);

    }

    /**
     * 生成二维码(写到磁盘上)
     * filePath 存放图片的路径
     * fileName 图片的名称
     * info     内容
     * width    图片的宽度
     * height   图片的高度
     *
     * @return 图片路径
     */
    @SneakyThrows
    public static String writeQrCode(String filePath, String fileName, String info, int width, int height) {
        //创建文件路径
        Path path = createPath(filePath, fileName);
        //生成二维码
        Map<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
        hints.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8);
        //生成矩阵
        BitMatrix bitMatrix = new MultiFormatWriter().encode(info,
                BarcodeFormat.QR_CODE, width, height, hints);
        //输出图像
        MatrixToImageWriter.writeToPath(bitMatrix, "png", path);
        return path.toString();
    }

    /**
     * 生成二维码图片(写到流中)
     *
     * @param stream 流
     * @param info   内容
     * @param width  宽
     * @param height 高
     */
    @SneakyThrows
    public static void writeQrCode(OutputStream stream, String info, int width, int height) {
        //生成二维码
        EnumMap<EncodeHintType, Object> hints = new EnumMap<>(EncodeHintType.class);
        hints.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8);
        hints.put(EncodeHintType.MARGIN, 0);
        //生成矩阵
        BitMatrix bitMatrix = new MultiFormatWriter().encode(info,
                BarcodeFormat.QR_CODE, width, height, hints);
        //去白边
        bitMatrix = deleteWhite(bitMatrix);
        BufferedImage bi = MatrixToImageWriter.toBufferedImage(bitMatrix);
        //调整大小
        bi = zoomInImage(bi, width, height);
        //输出图像
        ImageIO.write(bi, "png", stream);
    }

    /**
     * 解析二维码
     */
    public static String decodeQrCode(InputStream inputStream) {
        try {
            BufferedImage image = ImageIO.read(inputStream);
            LuminanceSource source = new BufferedImageLuminanceSource(image);
            Binarizer binarizer = new HybridBinarizer(source);
            BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
            EnumMap<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
            hints.put(DecodeHintType.CHARACTER_SET, StandardCharsets.UTF_8);

            // 对图像进行解码
            Result result = new MultiFormatReader().decode(binaryBitmap, hints);
            log.info("图片中内容:{}", result.getText());
            return result.getText();
        } catch (Exception e) {
            log.warn("解析二维码失败,异常信息:{}", e.getMessage());
            throw new IllegalArgumentException("解析二维码失败");
        }

    }

    /**
     * 去除白边
     *
     * @param matrix 矩阵
     * @return
     */
    private static BitMatrix deleteWhite(BitMatrix matrix) {
        int[] rec = matrix.getEnclosingRectangle();
        int resWidth = rec[2] + 1;
        int resHeight = rec[3] + 1;

        BitMatrix resMatrix = new BitMatrix(resWidth, resHeight);
        resMatrix.clear();
        for (int i = 0; i < resWidth; i++) {
            for (int j = 0; j < resHeight; j++) {
                if (matrix.get(i + rec[0], j + rec[1])) {
                    resMatrix.set(i, j);
                }
            }
        }
        return resMatrix;
    }

    /**
     * 将图像调整为指定的宽度和高度
     *
     * @param originalImage 员图像
     * @param width         新的宽度
     * @param height        新的高度
     * @return 调整后的图像
     */
    private static BufferedImage zoomInImage(BufferedImage originalImage, int width, int height) {
        BufferedImage newImage = new BufferedImage(width, height, originalImage.getType());
        Graphics g = newImage.getGraphics();
        g.drawImage(originalImage, 0, 0, width, height, null);
        g.dispose();
        return newImage;
    }

    private static File createFile(String path, String fileName) {
        File dir = new File(path);
        if (!dir.exists()) {
            boolean result = dir.mkdirs();
            log.info("mkdirs result:{}", result);
        }
        return new File(path + fileName);
    }

    /**
     * 创建文件路径
     *
     * @param filePath 路径
     * @param fileName 文件名
     * @return
     */
    private static Path createPath(String filePath, String fileName) {
        Path path = FileSystems.getDefault().getPath(filePath, fileName);
        File dir = new File(filePath);
        if (!dir.exists()) {
            boolean result = dir.mkdirs();
            log.info("mkdirs result:{}", result);
        }
        return path;
    }

    /**
     * 补白
     *
     * @param destImage 原图片
     * @param height    高
     * @param width     宽
     * @return
     */
    private static BufferedImage repairWhite(Image destImage, int height,
                                             int width) {
        BufferedImage image = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
        Graphics2D graphic = image.createGraphics();
        graphic.setColor(Color.white);
        graphic.fillRect(0, 0, width, height);
        if (width == destImage.getWidth(null)) {
            graphic.drawImage(destImage, 0, (height - destImage
                            .getHeight(null)) / 2, destImage.getWidth(null),
                    destImage.getHeight(null), Color.white, null);
        } else {
            graphic.drawImage(destImage,
                    (width - destImage.getWidth(null)) / 2, 0, destImage
                            .getWidth(null), destImage.getHeight(null),
                    Color.white, null);
        }
        graphic.dispose();
        return image;
    }

    /**
     * 生成带有中间嵌套图片的二维码
     *
     * @param content  内容
     * @param width    宽
     * @param height   高
     * @param srcImage 中间图片
     * @return
     */
    @SneakyThrows
    private static BufferedImage genBarcode(String content, int width,
                                            int height, BufferedImage srcImage) {
        // 缩放中间嵌套的图片
        BufferedImage scaleImage = scale(srcImage, IMAGE_HEIGHT, IMAGE_WIDTH, false);

        int[] srcPixels = scaleImage.getRGB(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, null, 0, IMAGE_WIDTH);
        // 设置二维码生成的参数
        Map<EncodeHintType, Object> hint = new EnumMap<>(EncodeHintType.class);
        hint.put(EncodeHintType.CHARACTER_SET, StandardCharsets.UTF_8);
        hint.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        hint.put(EncodeHintType.MARGIN, 1);

        // 生成二维码
        BitMatrix matrix = multiWriter.encode(content, BarcodeFormat.QR_CODE,
                width, height, hint);

        int[] pixels = new int[width * height];

        int halfW = matrix.getWidth() / 2;
        int halfH = matrix.getHeight() / 2;
        // 设置嵌套图片的边框大小
        int[] frame = {halfW - IMAGE_HALF_WIDTH - FRAME_WIDTH, halfW + IMAGE_HALF_WIDTH + FRAME_WIDTH,
                halfH - IMAGE_HALF_WIDTH - FRAME_WIDTH, halfH + IMAGE_HALF_WIDTH + FRAME_WIDTH};

        for (int y = 0; y < matrix.getHeight(); y++) {
            for (int x = 0; x < matrix.getWidth(); x++) {
                if (x > frame[0] && x < frame[1] && y > frame[2] && y < frame[3]) {
                    // 填充嵌套图片
                    pixels[y * width + x] = srcPixels[(y - halfH + IMAGE_HALF_WIDTH) * IMAGE_WIDTH + x - halfW + IMAGE_HALF_WIDTH];
                } else if (x <= frame[0] || x >= frame[1] || y <= frame[2] || y >= frame[3]) {
                    // 填充二维码
                    pixels[y * width + x] = matrix.get(x, y) ? 0xff000000 : 0xffffffff;
                }
            }
        }

        return createImage(width, height, pixels);
    }

    /**
     * 将像素数组转换为BufferedImage对象
     *
     * @param width  宽
     * @param height 高
     * @param pixels 像素数组
     * @return
     */
    private static BufferedImage createImage(int width, int height, int[] pixels) {
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        image.setRGB(0, 0, width, height, pixels, 0, width);
        return image;
    }

    /**
     * 把传入的原始图像按高度和宽度进行缩放,生成符合要求的图标
     *
     * @param originalImage 源文件
     * @param height        目标高度
     * @param width         目标宽度
     * @param hasFiller     比例不对时是否需要补白:true为补白; false为不补白;
     */
    @SneakyThrows
    public static BufferedImage scale(BufferedImage originalImage, int height,
                                      int width, boolean hasFiller) {
        // 缩放比例
        double ratio;

        Image destImage = originalImage.getScaledInstance(width, height,
                Image.SCALE_SMOOTH);
        // 计算比例
        if ((originalImage.getHeight() > height) || (originalImage.getWidth() > width)) {
            if (originalImage.getHeight() > originalImage.getWidth()) {
                ratio = (double) height
                        / originalImage.getHeight();
            } else {
                ratio = (double) width
                        / originalImage.getWidth();
            }
            AffineTransformOp op = new AffineTransformOp(AffineTransform
                    .getScaleInstance(ratio, ratio), null);
            destImage = op.filter(originalImage, null);
        }
        // 补白
        if (hasFiller) {
            return repairWhite(destImage, height, width);
        }
        return (BufferedImage) destImage;
    }

    @SneakyThrows
    @SuppressWarnings("all")
    public static void main(String[] args) {
        //生成中间镶嵌图片的二维码
        String content = "https://www.cnblogs.com/lyn8100/";
        int width = 300;
        int height = 300;
        InputStream scrImageInput = new FileInputStream(new File("E:\\project\\scr.png"));
        String path = "E:\\project\\";
        String fileName = "testQrCode2.jpg";
        writeScrQrCode(content, width, height, scrImageInput, path, fileName);

        //解析二维码
//        InputStream scrImageInput = new FileInputStream(new File("E:\\project\\testQrCode.jpg"));
//        //"E:\\project\\testQrCode.jpg"
//        System.out.println(decodeQrCode(scrImageInput));
    }

}