Java——二维码详解 深藏阁楼爱情的钟 2024-05-09 16:23 13阅读 0赞 #### 目录 #### * 二维码 * * 1、概述 * 2、QR Code * * 2.1、特点 * 2.2、基本结构 * 2.3、版本 * 2.4、纠错级别 * 2.5、存储容量 * 2.6、编码过程 * * 1)、数据分析 * 2)、数据编码 * 3)、纠错编码生成 * 4)、构造数据信息 * 5)、构造矩阵 * 6)、掩摸 * 7)、格式和版本信息 * 3、Java应用 * * 3.1、生成二维码 * * 1)、通用生成方法 * 2)、生成QRCode * 3)、Hutool工具包生成 * 4)、测试 * 3.2、识别二维码 ## 二维码 ## ### 1、概述 ### 二维码又称二维条码,常见的二维码为`QR Code`,QR全称Quick Response,是一种编码方式。它比传统的Bar Code条形码能存更多的信息,也能表示更多的数据类型。 二维条码/二维码(`2-dimensional bar code`)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的、黑白相间的、记录数据符号信息的图形;在代码编制上巧妙地利用构成计算机内部逻辑基础的“0”、“1”比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息,通过图象输入设备或光电扫描设备自动识读以实现信息自动处理:它具有条码技术的一些共性:每种码制有其特定的字符集;每个字符占有一定的宽度;具有一定的校验功能等。同时还具有对不同行的信息自动识别功能、及处理图形旋转变化点。 二维码按原理分类: * **堆叠式/行排式**:堆叠式/行排式二维条码又称堆积式二维条码或层排式二维条码),其编码原理是建立在一维条码基础之上,按需要堆积成二行或多行。它在编码设计、校验原理、识读方式等方面继承了一维条码的一些特点,识读设备与条码印刷与一维条码技术兼容。但由于行数的增加,需要对行进行判定,其译码算法与软件也不完全相同于一维条码。有代表性的行排式二维条码有: * `Code 16K` * `Code 49` * `PDF417` * `MicroPDF417` * **矩阵式**:矩阵式二维条码(又称棋盘式二维条码)它是在一个矩形空间通过黑、白像素在矩阵中的不同分布进行编码。在矩阵相应元素位置上,用点(方点、圆点或其他形状)的出现表示二进制“1”,点的不出现表示二进制的“0”,点的排列组合确定了矩阵式二维条码所代表的意义。矩阵式二维条码是建立在计算机图像处理技术、组合编码原理等基础上的一种新型图形符号自动识读处理码制。具有代表性的矩阵式二维条码有: * `Code One` * `MaxiCode` * `QR Code` * `Data Matrix` * `Han Xin Code` * `Grid Matrix` ### 2、QR Code ### #### 2.1、特点 #### 1. **存储大容量信息**:传统的条形码只能处理20位左右的信息量,与此相比,QR码可处理条形码的几十倍到几百倍的信息量。 2. **支持多种数据类型**:数字、英文字母、日文字母、汉字、符号、二进制、控制码等。 3. **在小空间内打印**:QR码使用纵向和横向两个方向处理数据,如果是相同的信息量,QR码所占空间为条形码的十分之一左右。(还支持Micro QR码,可以在更小空间内处理数据。 4. **有效表现各种字母**:QR码是日本国产的二维码,因此非常适合处理日文字母和汉字。QR码字集规格定义是按照日本标准“JIS第一级和第二级的汉字”制定的,因此在日语处理方面,每一个全角字母和汉字都用13比特的数据处理,效率较高,与其他二维码相比,可以多存储20%以上的信息。 5. **对变脏和破损的适应能力强**:QR码具备“纠错功能”,即使部分编码变脏或破损,也可以恢复数据。数据恢复以码字为单位(是组成内部数据的单位,在QR码的情况下,每8比特代表1码字),最多可以纠错约30%(根据变脏和破损程度的不同,也存在无法恢复的情况)。 6. **可以从任意方向读取**:QR码从360°任一方向均可快速读取。其奥秘就在于QR码中的3处定位图案,可以帮助QR码不受背景样式的影响,实现快速稳定的读取。 7. **支持数据合并功能**:QR码可以将数据分割为多个编码,最多支持16个QR码。使用这一功能,还可以在狭长区域内打印QR码。另外,也可以把多个分割编码合并为单个数据。 #### 2.2、基本结构 #### QR(Quick-Response) code是被广泛使用的一种二维码,解码速度快。它可以存储多用类型,下图是基本结构: ![在这里插入图片描述][1dcbdcaffb64460a95dad396df7de33b.png] * **位置探测图形、位置探测图形分隔符、定位图形**:用于对二维码的定位,对每个QR码来说,位置都是固定存在的,只是大小规格会有所差异 * **矫正图形**:规格确定,校正图形的数量和位置也就确定了 * **格式信息**:表示改二维码的纠错级别,分为L、M、Q、H * **版本信息**:即二维码的规格,QR码符号共有40种规格的矩阵(一般为黑白色),从21x21(版本1),到177x177(版本40) * **数据和纠错码字**:实际保存的二维码信息,和纠错码字(用于修正二维码损坏带来的错误) #### 2.3、版本 #### QR Code设有1到40的不同版本(种类),每个版本都具备固有的码元结构(码元数)。 “码元结构”是指二维码中的码元数。从版本1(21码元×21码元)开始,在纵向和横向各自以4码元为单位递增,一直到版本40(177码元×177码元)。 ![在这里插入图片描述][345df82189ab4ef3922aee10a9a1b0ac.png] #### 2.4、纠错级别 #### QR码具有“纠错功能”。即使编码变脏或破损,也可自动恢复数据。这一“纠错能力”具备4个级别,用户可根据使用环境选择相应的级别。调高级别,纠错能力也相应提高,但由于数据量会随之增加,编码尺寸也也会变大。 用户应综合考虑使用环境、编码尺寸等因素后选择相应的级别。 在工厂等容易沾染赃物的环境下,可以选择级别Q或H,在不那么脏的环境下,且数据量较多的时候,也可以选择级别L。一般情况下用户大多选择级别M(15%)。 <table> <thead> <tr> <th align="left">纠错级别</th> <th align="left">容错率</th> </tr> </thead> <tbody> <tr> <td align="left"><code>L</code></td> <td align="left">7%</td> </tr> <tr> <td align="left"><code>M</code></td> <td align="left">15%</td> </tr> <tr> <td align="left"><code>Q</code></td> <td align="left">25%</td> </tr> <tr> <td align="left"><code>H</code></td> <td align="left">30%</td> </tr> </tbody> </table> #### 2.5、存储容量 #### 通过查询QR码的技术文档,可以发现,如果二维码使用最大版本40,也就是177\*177的的二维码,最大可存储信息是: * **纯数字**:最多7089字符 * **纯字母**:最多4396字符 * **二进制(8bit)**:最多2953字节 * **中文汉字(UTF-8)**:最多984字符 * **中文汉字(GIG5)**:最多18000字符 * **日文汉字/片假名(Shift JIS)**:最多1817字符 #### 2.6、编码过程 #### ##### 1)、数据分析 ##### 1. 确定编码的字符类型,按相应的字符集转换成符号字符; 2. 选择纠错等级,在规格一定的条件下,纠错等级越高其真实数据的容量越小。 ##### 2)、数据编码 ##### 将数据字符转换为位流,每8位一个码字,整体构成一个数据的码字序列。其实知道这个数据码字序列就知道了二维码的数据内容。 <table> <thead> <tr> <th align="left">模式</th> <th align="left">指示符</th> </tr> </thead> <tbody> <tr> <td align="left">ECI</td> <td align="left"><code>0111</code></td> </tr> <tr> <td align="left">数字</td> <td align="left"><code>0001</code></td> </tr> <tr> <td align="left">字母数字</td> <td align="left"><code>0010</code></td> </tr> <tr> <td align="left">8位字节</td> <td align="left"><code>0100</code></td> </tr> <tr> <td align="left">日本汉字</td> <td align="left"><code>1000</code></td> </tr> <tr> <td align="left">中文汉字</td> <td align="left"><code>1101</code></td> </tr> <tr> <td align="left">结构链接</td> <td align="left"><code>0011</code></td> </tr> <tr> <td align="left">FNCI</td> <td align="left"><code>0101(第一位置)</code><br><code>1001(第二位置)</code></td> </tr> <tr> <td align="left">终止符(信息结尾)</td> <td align="left"><code>0000</code></td> </tr> </tbody> </table> 数据可以按照一种模式进行编码,以便进行更高效的解码,例如:对数据:01234567编码(版本1-H): 1. 分组 -> 012 345 67 2. 转成二进制: 012 -> 0000001100 345 -> 0101011001 67 -> 1000011 3. 转成序列:0000001100 0101011001 1000011 4. 字符数量:转成二进制添加到尾部 8 -> 0000001000 5. 加入模式指示符0001: 0001 0000001000 0000001100 0101011001 1000011 对于字母、中文、日文等只是分组的方式、模式等内容有所区别。基本方法是一致的。 ##### 3)、纠错编码生成 ##### 按需要将上面的码字序列分块,并根据纠错等级和分块的码字,产生纠错码字,并把纠错码字加入到数据码字序列后面,成为一个新的序列。 采用的算法是Reed-Solomon编码,其可以检测和修正一定数量的错误。 错等级确定的情况下,其实它所能容纳的码字总数和纠错码字数也就确定了,比如:版本10,纠错等级时H时,总共能容纳346个码字,其中224个纠错码字。 就是说二维码区域中大约1/3的码字时冗余的。对于这224个纠错码字,它能够纠正112个替代错误(如黑白颠倒)或者224个据读错误(无法读到或者无法译码),这样纠错容量为:112/346=32.4%。 ##### 4)、构造数据信息 ##### 在规格确定的条件下,将上面产生的序列按次序放如分块中。 按规定把数据分块,然后对每一块进行计算,得出相应的纠错码字区块,把纠错码字区块 按顺序构成一个序列,添加到原先的数据码字序列后面。 如:D1, D12, D23, D35, D2, D13, D24, D36, … D11, D22, D33, D45, D34, D46, E1, E23,E45, E67, E2, E24, E46, E68,… ##### 5)、构造矩阵 ##### 将探测图形、分隔符、定位图形、校正图形和码字模块放入矩阵中。 ![在这里插入图片描述][1dfba3576b70411e86950d268acabd6b.png] 把上面的完整序列填充到相应规格的二维码矩阵的区域中。 ##### 6)、掩摸 ##### 将掩摸图形用于符号的编码区域,使得二维码图形中的深色和浅色(黑色和白色)区域能够比率最优的分布。 ##### 7)、格式和版本信息 ##### 生成格式和版本信息放入相应区域内。 版本7-40都包含了版本信息,没有版本信息的全为0。二维码上两个位置包含了版本信息,它们是冗余的。 版本信息共18位,6X3的矩阵,其中6位时数据为,如版本号8,数据位的信息时 001000,后面的12位是纠错位。 ### 3、Java应用 ### 基于SpringBoot环境,介绍二维码的生成与识别。 #### 3.1、生成二维码 #### 在Java中生成二维码,你可以使用第三方库,如Google的ZXing。 <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> ##### 1)、通用生成方法 ##### 此种方式可以生成ZXing库支持的各种类型二维码,且可以自定义二维码颜色。 import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.MultiFormatWriter; import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import org.springframework.util.ObjectUtils; import javax.imageio.ImageIO; import javax.swing.filechooser.FileSystemView; import java.awt.image.BufferedImage; import java.io.File; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; /** * 二维码工具类 */ public class CodeUtil { //二维码类型,枚举类 private static final BarcodeFormat CODE_TYPE = BarcodeFormat.QR_CODE; //二维码宽度,单位像素 private static final int CODE_WIDTH = 400; //二维码高度,单位像素 private static final int CODE_HEIGHT = 400; //二维码前景色,0x000000表示黑色 private static final int FRONT_COLOR = 0x000000; //二维码背景色,0xFFFFFF表示白色 private static final int BACKGROUND_COLOR = 0xFFFFFF; /** * 生成二维码并保存为文件 * @param content 二维码内容 * @param codeImgFileSaveDir 生成的二维码图片存储位置 * @param fileName 二维码图片文件名 */ public static void createCodeToFile(String content, File codeImgFileSaveDir, String fileName) { if (ObjectUtils.isEmpty(content) || ObjectUtils.isEmpty(fileName)) { return; } content = content.trim(); if (codeImgFileSaveDir == null || codeImgFileSaveDir.isFile()) { //二维码图片存储目录为空,默认放在桌面 FileSystemView.getFileSystemView().getHomeDirectory(); } if (!codeImgFileSaveDir.exists()) { //二维码图片存储目录不存在,则创建 codeImgFileSaveDir.mkdirs(); } //生成二维码 try { BufferedImage bufferedImage = getBufferedImage(content); File codeImgFile = new File(codeImgFileSaveDir, fileName); ImageIO.write(bufferedImage, "png", codeImgFile); } catch (Exception e) { e.printStackTrace(); } } /** * 生成二维码并输出到输出流,通常用于输出到网页上进行显示 * @param content 二维码内容 * @param outputStream 输出流 */ public static void createCodeToOutputStream(String content, OutputStream outputStream) { if (ObjectUtils.isEmpty(content)) { return; } content = content.trim(); //生成二维码 try { BufferedImage bufferedImage = getBufferedImage(content); ImageIO.write(bufferedImage, "png", outputStream); } catch (Exception e) { e.printStackTrace(); } } /** * 核心代码,生成二维码 * @param content * @return BufferedImage * @throws WriterException */ private static BufferedImage getBufferedImage(String content) throws WriterException { //com.google.zxing.EncodeHintType:编码提示类,枚举类型 Map<EncodeHintType, Object> hints = new HashMap<>(); //EncodeHintType.CHARACTER_SET:设置字符编码类型 hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); //EncodeHintType.ERROR_CORRECTION:设置纠错级别 //ErrorCorrectionLevel:纠错级别,【L:%7】【M:15%】【Q:25%】[H:30%] //默认为L级别,纠错级别不同,生成的图案不同,但扫描结果一致 hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); //EncodeHintType.MARGIN:设置二维码边距,单位像素 hints.put(EncodeHintType.MARGIN, 1); //创建工厂类 MultiFormatWriter multiFormatWriter = new MultiFormatWriter(); //指定二维码类型和参数,返回对应的Writter进行编码后的二维码对象 BitMatrix bitMatrix = multiFormatWriter.encode(content, CODE_TYPE, CODE_WIDTH, CODE_HEIGHT, hints); //创建图像缓冲区 BufferedImage bufferedImage = new BufferedImage(CODE_WIDTH, CODE_HEIGHT, BufferedImage.TYPE_INT_BGR); //填充 for (int x = 0; x < CODE_WIDTH; x++) { for (int y = 0; y < CODE_HEIGHT; y++) { //bitMatrix.get(x, y)返回true,表示黑色即前景色 bufferedImage.setRGB(x, y, bitMatrix.get(x, y) ? FRONT_COLOR : BACKGROUND_COLOR); } } return bufferedImage; } } ##### 2)、生成QRCode ##### import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.client.j2se.MatrixToImageWriter; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import org.springframework.util.ObjectUtils; import javax.swing.filechooser.FileSystemView; import java.io.File; import java.io.OutputStream; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; /** * QRCode工具类 */ public class QRCodeUtil { //二维码宽度,单位像素 private static final int CODE_WIDTH = 400; //二维码高度,单位像素 private static final int CODE_HEIGHT = 400; /** * 生成QRCode并保存为文件 * @param content 二维码内容 * @param codeImgFileSaveDir 生成的二维码图片存储位置 * @param fileName 二维码图片文件名 */ public static void createQRCodeToFile(String content, File codeImgFileSaveDir, String fileName) { if (ObjectUtils.isEmpty(content) || ObjectUtils.isEmpty(fileName)) { return; } content = content.trim(); if (codeImgFileSaveDir == null || codeImgFileSaveDir.isFile()) { //二维码图片存储目录为空,默认放在桌面 FileSystemView.getFileSystemView().getHomeDirectory(); } if (!codeImgFileSaveDir.exists()) { //二维码图片存储目录不存在,则创建 codeImgFileSaveDir.mkdirs(); } //生成QRCode //配置 Map<EncodeHintType, Object> hints = new HashMap<>(); //设置字符编码类型 hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); //设置纠错级别 hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); //设置二维码边距,单位像素 hints.put(EncodeHintType.MARGIN, 1); //设置QRCode版本,1-40 hints.put(EncodeHintType.QR_VERSION, 1); //直接使用QRCodeWriter QRCodeWriter qrCodeWriter = new QRCodeWriter(); try { //编码为灰度的位矩阵 BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_HEIGHT, hints); //获取Path Path path = Paths.get(codeImgFileSaveDir.getPath(), fileName); //写入文件 MatrixToImageWriter.writeToPath(bitMatrix, "PNG", path); } catch (Exception e) { e.printStackTrace(); } } /** * 生成QRCode并输出到输出流,通常用于输出到网页上进行显示 * @param content 二维码内容 * @param outputStream 输出流 */ public static void createQRCodeToOutputStream(String content, OutputStream outputStream) { if (ObjectUtils.isEmpty(content)) { return; } content = content.trim(); //生成QRCode //配置 Map<EncodeHintType, Object> hints = new HashMap<>(); //设置字符编码类型 hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); //设置纠错级别 hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); //设置二维码边距,单位像素 hints.put(EncodeHintType.MARGIN, 1); //设置QRCode版本,1-40 hints.put(EncodeHintType.QR_VERSION, 1); //直接使用QRCodeWriter QRCodeWriter qrCodeWriter = new QRCodeWriter(); try { //编码为灰度的位矩阵 BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_HEIGHT, hints); //写入输出流 MatrixToImageWriter.writeToStream(bitMatrix, "PNG", outputStream); } catch (Exception e) { e.printStackTrace(); } } } ##### 3)、Hutool工具包生成 ##### hutool工具包内部也是使用了ZXing库,但是帮我们封装好了,还提供了配置类,使用上更加简单。 <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.2.5</version> </dependency> @Configuration public class QRCodeConfig { @Bean public QrConfig qrConfig() { QrConfig qrConfig = new QrConfig(); qrConfig.setBackColor(Color.white); qrConfig.setForeColor(Color.black); qrConfig.setCharset(Charset.forName("UTF-8")); qrConfig.setMargin(1); qrConfig.setWidth(400); qrConfig.setHeight(400); //logo图片 qrConfig.setImg(new File("./logo.jpeg")); //logo缩放比例 qrConfig.setRatio(6); return qrConfig; } } @Service public class QRCodeService { @Resource private QrConfig qrConfig; public void generateFile(String content, File file) { //生成到本地文件 QrCodeUtil.generate(content, qrConfig, file); } public void generateStream(String content, OutputStream outputStream) { //输出到流 QrCodeUtil.generate(content, qrConfig, "png", outputStream); } } ##### 4)、测试 ##### @Controller @RequestMapping("/code") public class QRCodeController { @Autowired private QRCodeService qrCodeService; @PostMapping("/toFile") public void toFile(String content) { qrCodeService.generateFile(content, new File("./test.png")); } @PostMapping("/toStream") public void toStream(String content, HttpServletResponse response) { try { qrCodeService.generateStream(content, response.getOutputStream()); } catch (IOException e) { e.printStackTrace(); } } } ![在这里插入图片描述][a2aa214cd7014f1ca1b2f5994c187866.png] #### 3.2、识别二维码 #### 使用ZXing库识别。 import com.google.zxing.BinaryBitmap; import com.google.zxing.MultiFormatReader; import com.google.zxing.Result; import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.common.HybridBinarizer; import org.apache.tomcat.util.codec.binary.Base64; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.*; /** * QRCode识别 */ public class QRCodeReader { /** * 识别二维码文件 * @param file 二维码文件 * @return */ public static String readQRCodeFile(File file) { String content = null; try { //将二维码图片加载为BufferedImage BufferedImage bufferedImage = ImageIO.read(file); //创建BufferedImageLuminanceSource BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(bufferedImage); //使用HybridBinarizer将LuminanceSource转换为二进制位图BinaryBitmap BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); //使用工厂类 MultiFormatReader reader = new MultiFormatReader(); //解码 Result result = reader.decode(bitmap); //获取内容 content = result.getText(); } catch (Exception e) { e.printStackTrace(); } return content; } public static String readQRCodeBase64(String base64Code) { String content = null; try { //解码为字节数组 byte[] imageBytes = Base64.decodeBase64(base64Code); //转换为输入流 ByteArrayInputStream bis = new ByteArrayInputStream(imageBytes); //创建BufferedImage BufferedImage bufferedImage = ImageIO.read(bis); //创建BufferedImageLuminanceSource BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(bufferedImage); //使用HybridBinarizer将LuminanceSource转换为二进制位图BinaryBitmap BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); //使用工厂类 MultiFormatReader reader = new MultiFormatReader(); //解码 Result result = reader.decode(bitmap); //获取内容 content = result.getText(); } catch (Exception e) { e.printStackTrace(); } return content; } public static void main(String[] args) throws Exception { File file = new File("./test.png"); System.out.println(readQRCodeFile(file)); //base64编码 FileInputStream fis = new FileInputStream(file); int length = fis.available(); byte[] bytes = new byte[length]; fis.read(bytes); fis.close(); String base64Code = Base64.encodeBase64String(bytes); System.out.println(readQRCodeBase64(base64Code)); } } [1dcbdcaffb64460a95dad396df7de33b.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/09/e689ff5e5b9040eb845337276ae9c06a.png [345df82189ab4ef3922aee10a9a1b0ac.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/09/974da15583e942f59b0e98e597a541e5.png [1dfba3576b70411e86950d268acabd6b.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/09/be43a5a5a0e5408c8f68bf20ca56251d.png [a2aa214cd7014f1ca1b2f5994c187866.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/05/09/b71d52af380b4d90b03d5c851561bfec.png
还没有评论,来说两句吧...