生成二維碼、識別二維碼的工具類

筆者日常 第一次用markdown寫文章,有點手生~


聲明 此工具類由本人二次整理改造後分享給大家,原編寫者未知,雖然本人重寫了大部分邏輯代碼,但是核心部分仍然是採用的原來的代碼。若涉及侵權問題,請隨時聯繫筆者。


工具類使用示例

  • 生成二維碼

    • 方式一
      執行方法:
     /**
      * 生成二維碼
      *
      * @date 2019/9/10 10:39
      */
     public static String test1() throws IOException, WriterException {
         return QrCodeUtil.encodeQrCode("螞蟻牙~嘿嘿", 500, 500,
                   "C:\\Users\\JustryDeng\\Desktop\\abc\\二維碼(通過方式一生成).jpg");
     }
    

            生成效果如圖:

    • 方式二
      執行方法:
       /**
        * 生成二維碼
        *
        * @date 2019/9/10 10:39
        */
       public static String test2() throws IOException, WriterException {
           return QrCodeUtil.encodeQrCodeAnotherWay("螞蟻~~~", 500, 500,
                   "C:\\Users\\JustryDeng\\Desktop\\abc\\二維碼(通過方式二生成).jpg");
       }
    

            生成效果如圖:

  • 生成帶圖片的二維碼

    執行方法:

    /**
     * 生成帶圖片的二維碼
     *
     * @date 2019/9/10 10:39
     */
    public static String test3() throws IOException, WriterException {
        return QrCodeUtil.encodeQrCodeWithEmbeddedImg("我是二維碼代表的數據內容", 500, 500,
                "C:\\Users\\JustryDeng\\Desktop\\1.png",
                "C:\\Users\\JustryDeng\\Desktop\\abc\\帶圖片的二維碼.jpg");
    }
    

    生成效果如圖:

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

    執行方法:

    /**
     * 生成帶文字、帶圖片的二維碼
     *
     * @date 2019/9/10 10:39
     */
     public static String test4() throws IOException, WriterException {
        QrImage para = QrImage.builder()
                .qrCodeFileOutputPath("C:\\Users\\JustryDeng\\Desktop\\abc\\帶文字帶圖片的二維碼.jpg")
                .qrCodeContent("http://www.baidu.com")
                .qrCodeWidth(500)
                .qrCodeHeight(500)
                .embeddedImgFilePath("C:\\Users\\JustryDeng\\Desktop\\1.png")
                .wordContent("我的二維碼")
                .wordSize(20)
                .build();
        return QrCodeUtil.encodeQrCodeWithEmbeddedImgAndFonts(para);
    }
    

    生成效果如圖:

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

    執行方法:

     /**
      * 生成帶文字、不帶圖片的二維碼
      *
      * @date 2019/9/10 10:39
      */
     public static String test5() throws IOException, WriterException {
         QrImage para = QrImage.builder()
                 .qrCodeFileOutputPath("C:\\Users\\JustryDeng\\Desktop\\abc\\帶文字不帶圖片的二維碼.jpg")
                 .qrCodeContent("二維碼代表的數據內容~")
                 .qrCodeWidth(500)
                 .qrCodeHeight(500)
                 .wordContent("我是文字")
                 .wordSize(20)
                 .build();
         return QrCodeUtil.encodeQrCodeWithEmbeddedImgAndFonts(para);
     }
    

    生成效果如圖:

  • 識別二維碼

    main方法:

    被調用方法(兩種方式均可):

    	/**
    	 * 識別二維碼
    	 *
    	 * @date 2019/9/10 10:39
    	 */
    	public static String test6() throws IOException, NotFoundException {
    	    return QrCodeUtil.decodeQrCode(
    	            new File("C:\\Users\\JustryDeng\\Desktop\\abc\\二維碼(通過方式一生成).jpg")
    	    );
    	}
    	
    	/**
    	 * 識別二維碼
    	 *
    	 * @date 2019/9/10 10:39
    	 */
    	public static String test7() throws IOException, NotFoundException {
    	    return QrCodeUtil.decodeQrCode(
    	            new FileInputStream("C:\\Users\\JustryDeng\\Desktop\\abc\\帶文字帶圖片的二維碼.jpg")
    	    );
    	}
    

    生成效果如圖:

二維碼工具類,使用示例完畢!


軟硬件環境說明: Windows10、IntelliJ IDEA、SpringBoot 2.1.8.RELEASE。

生成二維碼、識別二維碼的工具類

  • 相關核心依賴

 <!-- https://mvnrepository.com/artifact/com.google.zxing/core -->
 <dependency>
     <groupId>com.google.zxing</groupId>
     <artifactId>core</artifactId>
     <version>3.4.0</version>
 </dependency>

 <!-- https://mvnrepository.com/artifact/com.google.zxing/javase -->
 <dependency>
     <groupId>com.google.zxing</groupId>
     <artifactId>javase</artifactId>
     <version>3.4.0</version>
 </dependency>

注:除了上述依賴外,本人還引入了lombok,完整的pom.xml是這樣的:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.szlaozicl</groupId>
    <artifactId>zxing-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>zxing-demo</name>
    <description>使用zxing生成、識別二維碼</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.google.zxing/core -->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.4.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.google.zxing/javase -->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.4.0</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • 相關模型

import lombok.*;

/**
 * 二維碼圖片對象
 *
 * @author JustryDeng
 * @date 2019/9/9 15:22
 */
@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;
}
  • 工具類

import com.google.zxing.*;
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 com.szlaozicl.zxingdemo.model.QrImage;
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.*;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

/**
 * 二維碼  工具類
 * <p>
 * 注:此工具類, 是本人整理,但不是本人編寫。
 * <p>
 * 說明: 二維碼是一種編碼形式,
 * 將內容encode,得到二維碼;,
 * 將內容decode,得到數據內容。
 *
 * @author JustryDeng
 * @date 2019/9/9 15:11
 */
@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;

    /** 圖片縮小後,對那些"空"下的區域補色 */
    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
     *         圖片縮小後,對那些"空"下的區域補色
     * @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;
        // 創建頂部文字的圖片
        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;
    }

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

    /**
     * 獲取文件(順帶創建文件夾,如果需要的話)
     *
     * @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;
                // 二維碼圖片區域
                } 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;
            }
        }
    }
}

聲明 此工具類由本人二次整理改造後分享給大家,原編寫者未知,雖然本人重寫了大部分邏輯代碼,但是核心部分仍然是採用的原來的代碼。若涉及侵權問題,請隨時聯繫筆者。


^_^ 如有不當之處,歡迎指正

^_^ 參考鏈接
        https://blog.csdn.net/jam_fanatic/article/details/82818857

^_^ 本文已經被收錄進《程序員成長筆記(六)》,筆者JustryDeng

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章