Spring Boot使用zxing生成、識別二維碼

二維碼簡介

二維碼又稱為QR Code,QR全稱是Quick Response,是一個近幾年來移動設備上超流行的一種編碼方式。

二維碼是用某種特定的幾何圖形按一定規(guī)律在平面(二維方向上)分布的黑白相間的圖形記錄數據符號信息的。

主要應用場景如下:

  • 信息獲?。?、地圖、WIFI密碼、資料)
  • 網站跳轉(跳轉到微博、手機網站、網站)
  • 廣告推送(用戶掃碼,直接瀏覽商家推送的視頻、音頻廣告)
  • 手機電商(用戶掃碼、手機直接購物下單)
  • 防偽溯源(用戶掃碼、即可查看生產地;同時后臺可以獲取最終消費地)
  • 優(yōu)惠促銷(用戶掃碼,下載電子優(yōu)惠券,抽獎)
  • 會員管理(用戶手機上獲取電子會員信息、VIP服務)
  • 手機支付(掃描商品二維碼,通過銀行或第三方支付提供的手機端通道完成支付)
  • 賬號登錄(掃描二維碼進行各個網站或軟件的登錄)

生成二維碼

/**
 * 生成二維碼
 */
public static String test1() throws IOException, WriterException {
    return QrCodeUtil.encodeQrCode("java開發(fā)", 500, 500, "src/main/resources/static/img/二維碼(通過方式一生成).jpg");
}
在這里插入圖片描述
/**
 * 生成二維碼
 */
public static String test2() throws IOException, WriterException {
    return QrCodeUtil.encodeQrCodeAnotherWay("java開發(fā)", 500, 500, "src/main/resources/static/img/二維碼(通過方式二生成).jpg");
}
在這里插入圖片描述

生成帶圖片的二維碼

/**
 * 生成帶圖片的二維碼
 */
public static String test3() throws IOException, WriterException {
    return QrCodeUtil.encodeQrCodeWithEmbeddedImg("java開發(fā)", 500, 500, "src/main/resources/static/img/1.jpg",
            "src/main/resources/static/img/帶圖片的二維碼.jpg");
}
在這里插入圖片描述

生成帶文字、帶圖片的二維碼

/**
 * 生成帶文字、帶圖片的二維碼
 */
public static String test4() throws IOException, WriterException {
    QrImage para = QrImage.builder().qrCodeFileOutputPath("src/main/resources/static/img/帶文字帶圖片的二維碼.jpg")
            .qrCodeContent("https://blog.csdn.net/weixin_45730091").qrCodeWidth(500).qrCodeHeight(500)
            .embeddedImgFilePath("src/main/resources/static/img/2.png").wordContent("我的博客").wordSize(20).build();
    return QrCodeUtil.encodeQrCodeWithEmbeddedImgAndFonts(para);
}
在這里插入圖片描述

生成帶文字、不帶圖片的二維碼

/**
 * 生成帶文字、不帶圖片的二維碼
 */
public static String test5() throws IOException, WriterException {
    QrImage para = QrImage.builder().qrCodeFileOutputPath("src/main/resources/static/img/帶文字不帶圖片的二維碼.jpg")
            .qrCodeContent("二維碼").qrCodeWidth(500).qrCodeHeight(500).wordContent("java開發(fā)").wordSize(20).build();
    return QrCodeUtil.encodeQrCodeWithEmbeddedImgAndFonts(para);
}
在這里插入圖片描述

識別二維碼

/**
 * 識別二維碼
 */
public static String test6() throws IOException, NotFoundException {
    return QrCodeUtil.decodeQrCode(new File("src/main/resources/static/img/二維碼(通過方式一生成).jpg"));
}
/**
 * 識別二維碼
 */
public static String test7() throws IOException, NotFoundException {
    return QrCodeUtil.decodeQrCode(new FileInputStream("src/main/resources/static/img/帶文字帶圖片的二維碼.jpg"));
}
在這里插入圖片描述

核心依賴

<dependencies>
    <dependency>
        <groupId>com.google.zxing</groupId>
        <artifactId>core</artifactId>
        <version>3.4.0</version>
    </dependency>
    <dependency>
        <groupId>com.google.zxing</groupId>
        <artifactId>javase</artifactId>
        <version>3.4.0</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

模型

/**
 * 二維碼圖片對象
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class QrImage {

    /** 二維碼的內容(非空) */
    private String qrCodeContent;

    /** 二維碼的寬度(非空) */
    private Integer qrCodeWidth;

    /** 二維碼的高度(非空) */
    private Integer qrCodeHeight;

    /** 二維碼內嵌圖片的文件路徑(為空則表示:二維碼中間不嵌套圖片) */
    private String embeddedImgFilePath;

    /** 文字的大小(即:正方形文字的長度、寬度)(非空) */
    private Integer wordSize;

    /** 文字的內容(非空) */
    private String wordContent;

    /** 二維碼文件的輸出路徑(非空) */
    private String qrCodeFileOutputPath;

}

工具類

package cn.com.javakf.qrcode.utils;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

import javax.imageio.ImageIO;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.NotFoundException;
import com.google.zxing.Result;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import cn.com.javakf.qrcode.model.QrImage;
import lombok.extern.slf4j.Slf4j;

/**
 * 二維碼 工具類
 * <p>
 * 說明: 二維碼是一種編碼形式, 將內容encode,得到二維碼;, 將內容decode,得到數據內容。
 *
 */
@Slf4j
@SuppressWarnings("unused")
public class QrCodeUtil {

    /**
     * 嵌入的圖片(被程序處理后)的高度(px)
     * <p>
     * 注:內嵌圖片不應占據遮擋二維碼太多,否者可能導致生成的二維碼無法識別。 建議內嵌圖片的尺寸設置為二維碼尺寸的1/5 或 1/4
     */
    private static int embeddedImgDefaultWidth;

    /** 嵌入的圖片(被程序處理后)的高度(px) */
    private static int embeddedImgDefaultHeight;

    /** 嵌入的圖片與二維碼圖片之間的框的寬度(px) */
    private static int frameWidth;

    /** 嵌入的圖片與二維碼圖片之間的框的顏色. */
    private static int frameWidthColor;

    /** 二維碼 碼的顏色 */
    private static int qrCodeColor;

    /** 二維碼 背景的顏色 */
    private static int qrCodeBackgroundColor;

    /** 圖片縮小后,對那些"空"下的區(qū)域補色 */
    private static int fillColor;

    /** 二維碼寫碼器 */
    private static MultiFormatWriter multiFormatWriter = new MultiFormatWriter();

    /*
     * 初始化基本參數
     */
    static {
        QrCodeUtil.embeddedImgDefaultWidth = 80;
        QrCodeUtil.embeddedImgDefaultHeight = 80;
        QrCodeUtil.frameWidth = 0;
        QrCodeUtil.frameWidthColor = Color.BLUE.getRGB();
        QrCodeUtil.qrCodeColor = Color.BLACK.getRGB();
        QrCodeUtil.qrCodeBackgroundColor = Color.WHITE.getRGB();
        QrCodeUtil.fillColor = Color.RED.getRGB();
    }

    /**
     * 設置修改基本參數的參數值
     *
     * @param embeddedImgDefaultWidth  嵌入的圖片(被程序處理后)的高度(px)
     * @param embeddedImgDefaultHeight 嵌入的圖片(被程序處理后)的高度(px)
     * @param frameWidth               嵌入的圖片與二維碼圖片之間的框的寬度(px)
     * @param frameWidthColor          嵌入的圖片與二維碼圖片之間的框的顏色
     * @param qrCodeColor              二維碼 碼的顏色
     * @param qrCodeBackgroundColor    二維碼 背景的顏色
     * @param fillColor                圖片縮小后,對那些"空"下的區(qū)域補色
     * @date 2019/9/9 22:47
     */
    public static void modifyBasicParamsValues(Integer embeddedImgDefaultWidth, Integer embeddedImgDefaultHeight,
            Integer frameWidth, Color frameWidthColor, Color qrCodeColor, Color qrCodeBackgroundColor,
            Color fillColor) {
        if (embeddedImgDefaultWidth != null) {
            QrCodeUtil.embeddedImgDefaultWidth = embeddedImgDefaultWidth;
        }
        if (embeddedImgDefaultHeight != null) {
            QrCodeUtil.embeddedImgDefaultHeight = embeddedImgDefaultHeight;
        }
        if (frameWidth != null) {
            QrCodeUtil.frameWidth = frameWidth;
        }
        if (frameWidthColor != null) {
            QrCodeUtil.frameWidthColor = frameWidthColor.getRGB();
        }
        if (qrCodeColor != null) {
            QrCodeUtil.qrCodeColor = qrCodeColor.getRGB();
        }
        if (qrCodeBackgroundColor != null) {
            QrCodeUtil.qrCodeBackgroundColor = qrCodeBackgroundColor.getRGB();
        }
        if (fillColor != null) {
            QrCodeUtil.fillColor = fillColor.getRGB();
        }
    }

    /**
     * 生成二維碼(到destImagePath指向的File) --- 方式一
     *
     * @param content       二維碼的內容
     * @param width         二維碼的寬度(px)
     * @param height        二維碼的高度(px)
     * @param destImagePath 生成二維碼圖片的地址
     *
     * @return 生成的二維碼文件path
     * @throws IOException     IOException
     * @throws WriterException WriterException
     *
     * @date 2019/9/9 16:43
     */
    public static String encodeQrCode(String content, int width, int height, String destImagePath)
            throws IOException, WriterException {

        File dest = getFile(destImagePath);
        // 圖像類型
        String format = "jpg";
        Map<EncodeHintType, Object> hints = new HashMap<>(4);
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        try (FileOutputStream output = new FileOutputStream(dest)) {
            // 生成矩陣
            BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
            // 輸出圖像
            MatrixToImageWriter.writeToStream(bitMatrix, format, output);
        }
        return destImagePath;
    }

    /**
     * 生成二維碼(到destImagePath指向的File) --- 方式二
     *
     * @param content       二維碼的內容
     * @param width         二維碼的寬度(px)
     * @param height        二維碼的高度(px)
     * @param destImagePath 生成二維碼圖片的地址
     *
     * @return 生成的二維碼文件path
     * @throws IOException     IOException
     * @throws WriterException WriterException
     *
     * @date 2019/9/9 16:43
     */
    public static String encodeQrCodeAnotherWay(String content, int width, int height, String destImagePath)
            throws IOException, WriterException {
        boolean result = ImageIO.write(genBufferedImage(content, width, height), "jpg", getFile(destImagePath));
        log.info(" generate Qr code file {} {}", destImagePath, result ? "success" : "fail");
        return destImagePath;
    }

    /**
     * 生成帶圖片的二維碼(到destImagePath指向的File)
     *
     * @param content         二維碼的內容
     * @param width           二維碼的寬度(px)
     * @param height          二維碼的高度(px)
     * @param embeddedImgPath 被鑲嵌的圖片的地址
     * @param destImagePath   生成二維碼圖片的地址
     *
     * @return 生成的二維碼文件path
     * @throws IOException     IOException
     * @throws WriterException WriterException
     *
     * @date 2019/9/9 15:13
     */
    public static String encodeQrCodeWithEmbeddedImg(String content, int width, int height, String embeddedImgPath,
            String destImagePath) throws IOException, WriterException {
        boolean result = ImageIO.write(genBufferedImageWithEmbeddedImg(content, width, height, embeddedImgPath), "jpg",
                getFile(destImagePath));
        log.info(" generate Qr code file {} {}", destImagePath, result ? "success" : "fail");
        return destImagePath;
    }

    /**
     * 生成帶圖片的二維碼(到outputStream流)
     *
     * @param content         二維碼的內容
     * @param width           二維碼的寬度(px)
     * @param height          二維碼的高度(px)
     * @param embeddedImgPath 被鑲嵌的圖片的地址 注:被鑲嵌的圖片,如果尺寸
     *
     * @throws IOException     IOException
     * @throws WriterException WriterException
     *
     * @date 2019/9/9 16:43
     */
    public static void encodeQrCodeWithEmbeddedImg(String content, int width, int height, String embeddedImgPath,
            OutputStream outputStream) throws IOException, WriterException {
        boolean result = ImageIO.write(genBufferedImageWithEmbeddedImg(content, width, height, embeddedImgPath), "jpg",
                outputStream);
        log.info(" generate Qr code file to OutputStream {}", result ? "success" : "fail");
    }

    /**
     * 生成上方帶內嵌圖片、帶文字的二維碼
     * <p>
     * 注:若 嵌入的圖片的參數值為null 或者為 空的字符,那么 只生成帶文字的二維碼
     *
     * @param param 參數模型
     *
     * @return 生成的二維碼文件path
     * @throws IOException     IOException
     * @throws WriterException WriterException
     *
     * @date 2019/9/10 0:11
     */
    public static String encodeQrCodeWithEmbeddedImgAndFonts(QrImage param) throws IOException, WriterException {
        // 首先生成二維碼圖片
        BufferedImage qrImage;
        String embeddedImgFilePath = param.getEmbeddedImgFilePath();
        String qrCodeContent = param.getQrCodeContent();
        Integer qrCodeWidth = param.getQrCodeWidth();
        Integer qrCodeHeight = param.getQrCodeHeight();
        if (embeddedImgFilePath == null || embeddedImgFilePath.trim().length() == 0) {
            qrImage = genBufferedImage(qrCodeContent, qrCodeWidth, qrCodeHeight);
        } else {
            qrImage = genBufferedImageWithEmbeddedImg(qrCodeContent, qrCodeWidth, qrCodeHeight, embeddedImgFilePath);
        }
        int qrCodeImageWidth = qrImage.getWidth();
        int qrCodeImageHeight = qrImage.getHeight();

        String[] splitStrLines;
        splitStrLines = splitStrLines(param.getWordContent(), qrCodeImageWidth / param.getWordSize());
        // 文字圖片的高度 = 文字行數 * 每行高度(文字高度) + 10px;
        // 為了防止 文字圖片 下面部分顯示不全, 特意在這里額外加10像素的高度。
        int fontsImageHeight = splitStrLines.length * param.getWordSize() + 10;
        // 創(chuàng)建頂部文字的圖片
        BufferedImage imageWithFontsBufferedImage = new BufferedImage(qrCodeImageWidth, fontsImageHeight,
                BufferedImage.TYPE_4BYTE_ABGR);
        Graphics fontsImageGraphics = imageWithFontsBufferedImage.getGraphics();
        fontsImageGraphics.fillRect(0, 0, qrCodeImageWidth, fontsImageHeight);
        fontsImageGraphics.setColor(Color.black);
        fontsImageGraphics.setFont(new Font("宋體", Font.PLAIN, param.getWordSize()));

        // 文字長度大于一行的長度,進行分行
        // 每行限制的文字個數
        if (param.getWordContent().length() > qrCodeImageWidth / param.getWordSize()) {
            for (int i = 0; i < splitStrLines.length; i++) {
                fontsImageGraphics.drawString(splitStrLines[i], 0, param.getWordSize() * (i + 1));
            }
        } else {
            fontsImageGraphics.drawString(param.getWordContent(),
                    // 總長度減去字長度除以2為向右偏移長度
                    ((qrCodeImageWidth / param.getWordSize() - param.getWordContent().length()) / 2)
                            * param.getWordSize(),
                    param.getWordSize());
        }

        // 從圖片中讀取RGB
        int[] imageArrayFonts = new int[qrCodeImageWidth * fontsImageHeight];
        imageArrayFonts = imageWithFontsBufferedImage.getRGB(0, 0, qrCodeImageWidth, fontsImageHeight, imageArrayFonts,
                0, qrCodeImageWidth);

        int[] imageArrayQr = new int[qrCodeImageWidth * qrCodeImageHeight];
        imageArrayQr = qrImage.getRGB(0, 0, qrCodeImageWidth, qrCodeImageHeight, imageArrayQr, 0, qrCodeImageWidth);

        // 生成新圖片(在setsetRGB時,通過控制圖像的startX與startY, 可達到不同的拼接效果)
        BufferedImage newBufferedImage = new BufferedImage(qrCodeImageWidth, qrCodeImageHeight + fontsImageHeight,
                BufferedImage.TYPE_INT_RGB);
        // 圖片在上, 文字在下
        // 設置上半部分的RGB
        newBufferedImage.setRGB(0, 0, qrCodeImageWidth, qrCodeImageHeight, imageArrayQr, 0, qrCodeImageWidth);
        // 設置下半部分的RGB
        newBufferedImage.setRGB(0, qrCodeImageHeight, qrCodeImageWidth, fontsImageHeight, imageArrayFonts, 0,
                qrCodeImageWidth);
        // 文字在上,圖片在下
        // // 設置上半部分的RGB
        /// newBufferedImage.setRGB(0, 0, qrCodeImageWidth, fontsImageHeight,
        // imageArrayFonts, 0, qrCodeImageWidth);
        // // 設置下半部分的RGB
        /// newBufferedImage.setRGB(0, fontsImageHeight, qrCodeImageWidth,
        // qrCodeImageHeight, imageArrayQr, 0, qrCodeImageWidth);

        String qrCodeFileOutputPath = param.getQrCodeFileOutputPath();
        File outFile = getFile(qrCodeFileOutputPath);
        boolean result = ImageIO.write(newBufferedImage, "jpg", outFile);
        log.info(" generate Qr code file {} {}", qrCodeFileOutputPath, result ? "success" : "fail");
        return qrCodeFileOutputPath;

    }

    /**
     * 識別二維碼內容信息
     *
     * @param file 二維碼圖片文件
     *
     * @return 二維碼內容
     * @throws NotFoundException NotFoundException
     * @throws IOException       IOException
     * @date 2019/9/10 1:59
     */
    public static String decodeQrCode(File file) throws NotFoundException, IOException {
        BufferedImage image;
        image = ImageIO.read(file);
        if (image == null) {
            return null;
        }
        String data = decodeQrCode(image);
        log.info(" Qr code from [{}] data is -> {}", file.getAbsolutePath(), data);
        return data;
    }

    /**
     * 識別二維碼內容信息
     *
     * @param is 二維碼圖片文件流
     *
     * @return 二維碼內容
     * @throws NotFoundException NotFoundException
     * @throws IOException       IOException
     * @date 2019/9/10 1:59
     */
    public static String decodeQrCode(InputStream is) throws NotFoundException, IOException {
        BufferedImage image;
        image = ImageIO.read(is);
        if (image == null) {
            return null;
        }
        String data = decodeQrCode(image);
        log.info(" Qr code from InputStream data is -> {}", data);
        return data;
    }

    /// --------------------------------------------------------以下為輔助方法、輔助類

    /**
     * 獲取文件(順帶創(chuàng)建文件夾,如果需要的話)
     *
     * @param filePath 文件path
     * @return 文件對象
     * @date 2019/9/10 10:48
     */
    private static File getFile(String filePath) {
        File file = new File(filePath);
        if (!file.getParentFile().exists()) {
            boolean result = file.getParentFile().mkdirs();
            log.info(" create directory {} {}", file.getParent(), result);
        }
        return file;
    }

    /**
     * 識別二維碼內容信息
     *
     * @param image 二維碼圖片信息BufferedImage
     *
     * @return 二維碼內容
     * @throws NotFoundException NotFoundException
     * @date 2019/9/10 1:59
     */
    private static String decodeQrCode(BufferedImage image) throws NotFoundException {
        BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
        Result result;
        HashMap<DecodeHintType, Object> hints = new HashMap<>(4);
        hints.put(DecodeHintType.CHARACTER_SET, "utf-8");
        result = new MultiFormatReader().decode(bitmap, hints);
        return result.getText();
    }

    /**
     * 生成二維碼圖片信息BufferedImage
     * <p>
     * 注:BufferedImage即為二維碼圖片的數據容器,可以利用其進一步生成二維碼圖片
     *
     * @param content 二維碼的數據內容
     * @param width   二維碼的寬(px)
     * @param height  二維碼的高(px)
     *
     * @return 二維碼圖片信息BufferedImage
     * @throws WriterException WriterException
     * @date 2019/9/9 16:39
     */
    private static BufferedImage genBufferedImage(String content, int width, int height) throws WriterException {
        Map<EncodeHintType, Object> hints = getHints();
        // 生成一個矩陣(即:生成一個二維數組)
        BitMatrix matrix;
        matrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
        int[] pixels = new int[width * height];
        for (int y = 0; y < matrix.getHeight(); y++) {
            for (int x = 0; x < matrix.getWidth(); x++) {
                // 控制二維碼顏色, 前面那個為 二維碼的顏色, 后面那個為底色
                pixels[y * width + x] = matrix.get(x, y) ? qrCodeColor : qrCodeBackgroundColor;
            }
        }
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        image.getRaster().setDataElements(0, 0, width, height, pixels);
        return image;
    }

    /**
     * 生成(帶內嵌圖片的)二維碼圖片信息BufferedImage
     * <p>
     * 注:BufferedImage即為二維碼圖片的數據容器,可以利用其進一步生成二維碼圖片
     *
     * @param content           二維碼的數據內容
     * @param width             二維碼的寬(px)
     * @param height            二維碼的高(px)
     * @param embeddedImagePath 二維碼中要嵌套的圖片path
     *
     * @return 二維碼圖片信息BufferedImage
     * @throws IOException     IOException
     * @throws WriterException WriterException
     * @date 2019/9/9 16:39
     */
    private static BufferedImage genBufferedImageWithEmbeddedImg(String content, int width, int height,
            String embeddedImagePath) throws WriterException, IOException {
        // 讀取要嵌套的圖像,并將其壓縮到指定的寬高
        BufferedImage scaleImage = scale(embeddedImagePath, embeddedImgDefaultWidth, embeddedImgDefaultHeight, false);
        int embeddedImgFinalWidth = scaleImage.getWidth();
        int embeddedImgFinalHalfWidth = embeddedImgFinalWidth / 2;
        int embeddedImgFinalHeight = scaleImage.getHeight();
        int embeddedImgFinalHalfHeight = embeddedImgFinalHeight / 2;
        int[][] srcPixels = new int[embeddedImgFinalWidth][embeddedImgFinalHeight];
        for (int i = 0; i < embeddedImgFinalWidth; i++) {
            for (int j = 0; j < embeddedImgFinalHeight; j++) {
                srcPixels[i][j] = scaleImage.getRGB(i, j);
            }
        }
        Map<EncodeHintType, Object> hint = getHints();
        // 生成一個矩陣(即:生成一個二維數組)( 注:下面會在這個矩陣上繪圖)
        BitMatrix matrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, width, height, hint);
        // 二維碼矩陣轉為一維像素數組
        int qrCodeHalfWidth = matrix.getWidth() / 2;
        int qrCodeHalfHeight = matrix.getHeight() / 2;
        int[] pixels = new int[width * height];
        // 是否處于【嵌入圖片 與 二維碼圖片 之間的框】里
        boolean atEmbeddedImgFrameArea;
        // 是否處于【嵌入圖片的位置】里
        boolean atEmbeddedImgArea;
        // 內嵌的圖片(左下角) 在 二維碼圖片 中的坐標X
        int embeddedImgCoordinatesX = qrCodeHalfWidth - embeddedImgFinalHalfWidth;
        // 內嵌的圖片(左下角) 在 二維碼圖片 中的坐標Y
        int embeddedImgCoordinatesY = qrCodeHalfHeight - embeddedImgFinalHalfHeight;
        for (int y = 0; y < matrix.getHeight(); y++) {
            for (int x = 0; x < matrix.getWidth(); x++) {

                atEmbeddedImgArea = x > qrCodeHalfWidth - embeddedImgFinalHalfWidth
                        && x < qrCodeHalfWidth + embeddedImgFinalHalfWidth
                        && y > qrCodeHalfHeight - embeddedImgFinalHalfHeight
                        && y < qrCodeHalfHeight + embeddedImgFinalHalfHeight;

                atEmbeddedImgFrameArea = (x > qrCodeHalfWidth - embeddedImgFinalHalfWidth - frameWidth
                        && x < qrCodeHalfWidth - embeddedImgFinalHalfWidth + frameWidth
                        && y > qrCodeHalfHeight - embeddedImgFinalHalfHeight - frameWidth
                        && y < qrCodeHalfHeight + embeddedImgFinalHalfHeight + frameWidth)

                        ||

                        (x > qrCodeHalfWidth + embeddedImgFinalHalfWidth - frameWidth
                                && x < qrCodeHalfWidth + embeddedImgFinalHalfWidth + frameWidth
                                && y > qrCodeHalfHeight - embeddedImgFinalHalfHeight - frameWidth
                                && y < qrCodeHalfHeight + embeddedImgFinalHalfHeight + frameWidth)

                        ||

                        (x > qrCodeHalfWidth - embeddedImgFinalHalfWidth - frameWidth
                                && x < qrCodeHalfWidth + embeddedImgFinalHalfWidth + frameWidth
                                && y > qrCodeHalfHeight - embeddedImgFinalHalfHeight - frameWidth
                                && y < qrCodeHalfHeight - embeddedImgFinalHalfHeight + frameWidth)

                        ||

                        (x > qrCodeHalfWidth - embeddedImgFinalHalfWidth - frameWidth
                                && x < qrCodeHalfWidth + embeddedImgFinalHalfWidth + frameWidth
                                && y > qrCodeHalfHeight + embeddedImgFinalHalfHeight - frameWidth
                                && y < qrCodeHalfHeight + embeddedImgFinalHalfHeight + frameWidth);

                // 在二維碼矩陣數像素組里 嵌入 嵌入圖片的像素數組
                if (atEmbeddedImgArea) {
                    pixels[y * width + x] = srcPixels[x - embeddedImgCoordinatesX][y - embeddedImgCoordinatesY];
                }
                // 嵌入圖片 與 二維碼圖片 之間的框(設置其顏色)
                else if (atEmbeddedImgFrameArea) {
                    pixels[y * width + x] = frameWidthColor;
                    // 二維碼圖片區(qū)域
                } else {
                    // 控制二維碼顏色, 前面那個為 二維碼的顏色, 后面那個為底色
                    pixels[y * width + x] = matrix.get(x, y) ? qrCodeColor : qrCodeBackgroundColor;
                }
            }
        }
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        image.getRaster().setDataElements(0, 0, width, height, pixels);
        return image;
    }

    /**
     * 獲取hint信息
     *
     * @return hint
     * @date 2019/9/10 1:14
     */
    private static Map<EncodeHintType, Object> getHints() {
        Map<EncodeHintType, Object> hint = new HashMap<>(8);
        hint.put(EncodeHintType.CHARACTER_SET, "utf-8");
        hint.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        // 二維碼整體白框
        hint.put(EncodeHintType.MARGIN, 1);
        return hint;
    }

    /**
     * 把傳入的原始圖像按高度(或寬度)進行縮小,生成符合要求的圖標的BufferedImage信息
     * <p>
     * 注:若圖片原來的大小比給定的寬高小,那么不會進行放大,而是仍然保持原來的大小。
     *
     * @param srcImageFile 源文件地址
     * @param width        (縮小后的)寬度
     * @param height       (縮小后的)高度
     * @param autoFill     是否對按比例縮小后的圖片進行填充,使其完全達到給定的寬高。
     *                     <p>
     *                     注:圖片按照最長邊與對應的寬(或高)的比例縮小后,最長邊縮小后的尺寸與給定的寬(或高)一致了,
     *                     但是其高(或寬),與縮小后的最短邊不一定一樣。此時,我們可以考慮使用給定的顏色對 其進行填充。如:
     *                     想要嵌入進去的圖片的實際寬是80, 高是100; 但是這里指定縮小后的尺寸為 寬為10,高為10。
     *                     由下面的邏輯可知:縮小比例為0.1, 縮小后,圖片的寬就變成了8,高就變
     *                     成了10,此時高是滿足我們的要求的,但是寬差了2個像素。此時就可以將其進行填充了。
     *                     <p>
     *                     注:建議為false。
     *
     * @return 圖片的BufferedImage信息
     * @throws IOException IOException
     * @date 2019/9/10 1:59
     */
    private static BufferedImage scale(String srcImageFile, int width, int height, boolean autoFill)
            throws IOException {
        // 縮小比例
        double ratio;
        File file = getFile(srcImageFile);
        BufferedImage srcImage = ImageIO.read(file);
        Image destImage = srcImage.getScaledInstance(width, height, BufferedImage.SCALE_SMOOTH);
        // 如果原圖片的高或者寬 大于 指定的高或寬時,(以最長的一邊來計算縮放比例,并)進行縮放
        if ((srcImage.getHeight() > height) || (srcImage.getWidth() > width)) {
            if (srcImage.getHeight() > srcImage.getWidth()) {
                ratio = (new Integer(height)).doubleValue() / srcImage.getHeight();
            } else {
                ratio = (new Integer(width)).doubleValue() / srcImage.getWidth();
            }
            AffineTransformOp op = new AffineTransformOp(AffineTransform.getScaleInstance(ratio, ratio), null);
            destImage = op.filter(srcImage, null);
        }
        if (autoFill) {
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics2D graphic = image.createGraphics();
            graphic.setColor(Color.red);
            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();
            destImage = image;
        }
        return (BufferedImage) destImage;
    }

    /**
     * 一行字符串拆分成多行
     */
    private static String[] splitStrLines(String str, int len) {
        int blocks = str.length() / len + 1;
        String[] strArray = new String[blocks];
        for (int i = 0; i < blocks; i++) {
            if ((i + 1) * len > str.length()) {
                strArray[i] = str.substring(i * len);
            } else {
                strArray[i] = str.substring(i * len, (i + 1) * len);
            }
        }
        return strArray;
    }

    /**
     * Writes a {@link BitMatrix} to {@link BufferedImage}, file or stream. Provided
     * here instead of core since it depends on Java SE libraries.
     */
    private static class MatrixToImageWriter {

        private static final MatrixToImageConfig DEFAULT_CONFIG = new MatrixToImageConfig();

        private MatrixToImageWriter() {
        }

        /**
         * Renders a {@link BitMatrix} as an image, where "false" bits are rendered as
         * white, and "true" bits are rendered as black.
         */
        private static BufferedImage toBufferedImage(BitMatrix matrix) {
            return toBufferedImage(matrix, DEFAULT_CONFIG);
        }

        /**
         * As {@link #toBufferedImage(BitMatrix)}, but allows customization of the
         * output.
         */
        private static BufferedImage toBufferedImage(BitMatrix matrix, MatrixToImageConfig config) {
            int width = matrix.getWidth();
            int height = matrix.getHeight();
            BufferedImage image = new BufferedImage(width, height, config.getBufferedImageColorModel());
            int onColor = config.getPixelOnColor();
            int offColor = config.getPixelOffColor();
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {
                    image.setRGB(x, y, matrix.get(x, y) ? onColor : offColor);
                }
            }
            return image;
        }

        /**
         * @deprecated use {@link #writeToPath(BitMatrix, String, Path)}
         */
        @Deprecated
        private static void writeToFile(BitMatrix matrix, String format, File file) throws IOException {
            writeToPath(matrix, format, file.toPath());
        }

        /**
         * Writes a {@link BitMatrix} to a file.
         *
         * @see #toBufferedImage(BitMatrix)
         */
        private static void writeToPath(BitMatrix matrix, String format, Path file) throws IOException {
            writeToPath(matrix, format, file, DEFAULT_CONFIG);
        }

        /**
         * @deprecated use
         *             {@link #writeToPath(BitMatrix, String, Path, MatrixToImageConfig)}
         */
        @Deprecated
        private static void writeToFile(BitMatrix matrix, String format, File file, MatrixToImageConfig config)
                throws IOException {
            writeToPath(matrix, format, file.toPath(), config);
        }

        /**
         * As {@link #writeToFile(BitMatrix, String, File)}, but allows customization of
         * the output.
         */
        @SuppressWarnings("all")
        private static void writeToPath(BitMatrix matrix, String format, Path file, MatrixToImageConfig config)
                throws IOException {
            BufferedImage image = toBufferedImage(matrix, config);
            if (!ImageIO.write(image, format, file.toFile())) {
                throw new IOException("Could not write an image of format " + format + " to " + file);
            }
        }

        /**
         * Writes a {@link BitMatrix} to a stream.
         *
         * @see #toBufferedImage(BitMatrix)
         */
        private static void writeToStream(BitMatrix matrix, String format, OutputStream stream) throws IOException {
            writeToStream(matrix, format, stream, DEFAULT_CONFIG);
        }

        /**
         * As {@link #writeToStream(BitMatrix, String, OutputStream)}, but allows
         * customization of the output.
         */
        private static void writeToStream(BitMatrix matrix, String format, OutputStream stream,
                MatrixToImageConfig config) throws IOException {
            BufferedImage image = toBufferedImage(matrix, config);
            if (!ImageIO.write(image, format, stream)) {
                throw new IOException("Could not write an image of format 【" + format + "】");
            }
        }

        /**
         * 配置類封裝
         * <p>
         * Encapsulates custom configuration used in methods of
         * {@link MatrixToImageWriter}.
         */
        private static class MatrixToImageConfig {

            private static final int BLACK = Color.BLACK.getRGB();

            private static final int WHITE = Color.WHITE.getRGB();

            private final int onColor;

            private final int offColor;

            /**
             * Creates a default config with on color {@link #BLACK} and off color
             * {@link #WHITE}, generating normal black-on-white barcodes.
             */
            private MatrixToImageConfig() {
                this(BLACK, WHITE);
            }

            /**
             * @param onColor  pixel on color, specified as an ARGB value as an int
             * @param offColor pixel off color, specified as an ARGB value as an int
             */
            private MatrixToImageConfig(int onColor, int offColor) {
                this.onColor = onColor;
                this.offColor = offColor;
            }

            private int getPixelOnColor() {
                return onColor;
            }

            private int getPixelOffColor() {
                return offColor;
            }

            private int getBufferedImageColorModel() {
                // Use faster BINARY if colors match default
                return onColor == BLACK && offColor == WHITE ? BufferedImage.TYPE_BYTE_BINARY
                        : BufferedImage.TYPE_INT_RGB;
            }
        }
    }

}

打個廣告,本人博客地址是:風吟個人博客

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容