至于什么是二维码,大家都使用过,其实比较形象,对比之前的条形码,就很容易理解,就是基于水平方向排列的通过小竖条的宽度不同表示不同的信息,而二维码,表达信息的方式是基于二维的黑白相间(不一定就是黑白,多数看到的可能是黑白,其实颜色是可以随着自己的需要,灵活调整的)的小方块,按照一定的规则排列的一个矩形区域内,形成一个传递信息的编码方式。
二维码(本博客重点介绍的是目前主流的矩形二维码,堆叠的或者其他的模式,不做介绍),有其固有的特点,由定位矩形框,信息表达区域等功能识别器,特征明显,如下图:
一个可视的二维码,乍一看,就是上面这个样子,只是因为里面有很多信息码元(黑白相间的矩形块),看起来比这个更丰富。
就算这种矩阵式的二维码,也有几个不同的分支,请看下面的概要图片:
关于二维码的理论,这里先不做过多的介绍,后面会专题介绍理论,今天主要从代码实现的角度,介绍如何生成二维码,以及一些基本的参数调整,对二维码的影响。
下面直接上代码:
package qcode; import com.swetake.util.Qrcode; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; /** * * 基于QRcode包实现的矩阵式二维码的创建过程研究和学习 * * Created by shihuc on 2020/12/13. */ public class QRCodeCreate { public static void main(String[] args) throws IOException { int v = 3; for (v = 1; v <= 40; v++) { try { qrCreator(v, "Quick Response", 15, 3, 3); } catch (IOException e) { e.printStackTrace(); } } } public static void qrCreator(int ver, String info, int margin, int w, int h) throws IOException { /** * 计算二维码图片的高宽比 * API文档规定计算图片宽高的方式 ,v是本次测试的版本号,v为0时,表示生成的二维码尺寸随存储内容的变化而变化 */ int v = ver; int width = 67 + 12 * (v - 1); int height = 67 + 12 * (v - 1); Qrcode x = new Qrcode(); /** * 纠错等级分为 * level L : 最大 7% 的错误能够被纠正; * level M : 最大 15% 的错误能够被纠正; * level Q : 最大 25% 的错误能够被纠正; * level H : 最大 30% 的错误能够被纠正; * 设置二维码排错率,可选L(7%)、M(15%)、Q(25%)、H(30%),排错率越高可存储的信息越少,但对二维码清晰度的要求越小 */ x.setQrcodeErrorCorrect('L'); //注意版本信息 N代表数字 、A代表 a-z,A-Z、B代表 其他) x.setQrcodeEncodeMode('B'); //版本号1-40, 设置设置二维码尺寸,取值范围1-40,值越大尺寸越大,可存储的信息越大 x.setQrcodeVersion(v); //设置二维码的边框留白信息,就是在原有的二维码外边框留下白色的边距,让二维码显得有结构感 int qr_margin = margin; //String qrData = "https://www.cnblogs.com/shihuc";//内容信息 String qrData = info + " " + v; // 获得内容的字节数组,设置编码格式,汉字转格式需要抛出异常 byte[] d = qrData.getBytes("utf-8"); //缓冲区 BufferedImage bufferedImage = new BufferedImage(width+2*qr_margin, height+2*qr_margin, BufferedImage.TYPE_INT_BGR); //绘图, 用来画出二维码信息 Graphics2D gs = bufferedImage.createGraphics(); //设置二维码背景颜色和前景颜色,这里,可以设计出自己想要的颜色的二维码了,很方便 gs.setBackground(Color.WHITE); gs.setColor(Color.RED); gs.clearRect(0, 0, width+2*qr_margin, height+2*qr_margin); /** * 偏移量 3, 可以将二维码较好的控制在最后输出的二维码图片的中央位置,太大,会导致向右下角偏移, * 太小,会导致向左上角偏移, 偏移太多,二维码识别时,无法找到三个定位的正方形,从而无法识别二维码 */ int pixOff = 3 + qr_margin; /** * 容易踩坑的地方 * 1.注意for循环里面的i,j的顺序, * s[j][i]二维数组的j,i的顺序要与这个方法中的 gs.fillRect(j*3+pixOff,i*3+pixOff, 3, 3); * 顺序匹配,否则会出现解析图片是一串数字 * * 另外,还有需要注意的地方,fillRect里面后面的两个参数 width,height,指的是二维码里面填充的小方块,通常是指那个黑色 * 的方块,太大了,导致整个二维码图片里面黑色太多,太小,里面的白色区域偏多,通常是选择长宽都为3居多, 不管是偏大还是偏小, * 都会导致最后的二维码识别有困难,甚至识别不出来 */ /** * 仔细观察,下面s数组的维度,就是码元的维度,这里将体现出版本v和码元大小的关系, 21 + 4*(v - 1) * v=1: 21*21 * v=2: 25*25 * v=3: 29*29 * v=4: 33*33 * ....... */ boolean[][] s = x.calQrcode(d); //计算需要打印前景色的位置,二维码核心算法函数 for (int i = 0; i < s.length; i++) { for (int j = 0; j < s.length; j++) { if (s[j][i]) { gs.fillRect(j * 3 + pixOff, i * 3 + pixOff, w, h); } } } gs.dispose(); bufferedImage.flush(); //设置图片格式,与输出的路径 ImageIO.write(bufferedImage, "png", new File("D:\\ProTempHome\\margin\\qrcode" + v + ".png")); System.out.println( "v: " + v + ",二维码生成完毕"); } }
这里要说明的是,生成二维码所需的包,只有一个QRcode.jar,可以到我的网盘下载(https://pan.baidu.com/s/1X8dNd47-5cYPQ7hgMUZB3g,提取密码:8v5q),就是一个jar文件,将其copy到项目的lib路径下,并添加到classpath路径下。
若想在maven项目里面使用这个包,其实可以自己基于maven的安装指令,将这个jar变成maven格式的dependency配置信息。
mvn install:install-file -Dfile=D:\Program\QRCode\QRCode.jar -DgroupId=QRCode -DartifactId=QRCode -Dversion=3.0 -Dpackaging=jar
安装完成后,在maven的pom文件中,添加下面的内容:
<dependency>
<groupId>QRCode</groupId>
<artifactId>QRCode</artifactId>
<version>3.0</version>
</dependency>
这里,代码里面的注释信息已经很丰富,下面,需要对几个重点内容强调一下:
1. 上述案例的代码,支持设置边框留白的大小,其实,二维码生成逻辑还是蛮容易理解的,基于一套算法(纠错级别L、M、Q、H,及Model,再就是版本v都能影响这个算法的输出),得出一个布尔值类型的二维数组,然后,控制绘图程序,在矩形区域将码元信息绘制出来,最后得到我们熟悉的二维码。
1.1 这里纠错级别,和版本号,模式等参数,影响生成的二维码的大小,能够容纳的信息,抗干扰的能力。下面,给出了版本v只有前3个的变化表,可以粗略对比,就能有所了解。
1.2 在model,纠错参数配置不变的情况下,版本v的取值(1-40)越大,得到的二维码的尺寸也越大,最终识别这个二维码的难度也越大(识别的时间会加长),但是能够存储的信息也越多,下面给几个基于上面的代码生成的二维码的图片,大家可以对比观察下。
v=1
v=6
v=11
v=16
v=21
v=26
v=40
大家若有兴趣,可以用微信的扫一扫功能,可以体会一下,这个识别的过程是不是会越来越有困难,时间会变得比较长。
2. 基于上诉代码,不仅可以实现各种尺寸,各种纠错码模式,各种编码模式的二维码的生成(纠错模式越高,【L最低,Q最高】,存储信息越少,抗干扰,或者说越容易识别,反之亦然)。另外,还有一个很有意思的点,需要分享,那就是码元块的大小是可以调整的,就是上述代码中的这个函数的后面两个参数w,h。
gs.fillRect(j * 3 + pixOff, i * 3 + pixOff, w, h);
当这个参数变大时,生成的二维码显得更厚重,这两个参数的值越小时,生成的二维码给人的感觉很稀疏,如下图:
2.1 上面的图片是基于上述的代码(w,h有变化)生成的,v取值5。 从左向右,w=h=5, 3,2,1. 大家可以用微信扫一扫试试,最右边的这个w=1的二维码,我的手机微信识别不出来。基于这里的调整试验,得出一个信息,二维码的定位矩形框的大小(v不变的情况下),受到代码中pixOff的影响【准确的说是受到代码中pixOff = 3 + qr_margin中的3的影响,这个3变大,二维码中的定位矩形框越大,反之亦然;然而,仅仅调整这个数字是不科学的,会导致二维码不能完整的打印在公式中BufferImage定义的图片中】,基于上述代码,3已经是一个调整的算是不错的大小因子。
2.2 接下来说明w,h,他们的取值,在二维码定位矩形框大小不变时,w,h值越大,二维码看起来很厚重,w,h值越小,二维码越稀薄,上述的图片,当定位矩形框不是线条时(此时的码元大小也是缺失的),会导致二维码识别不出来。
3. 程序识别二维码
package qcode; import jp.sourceforge.qrcode.data.QRCodeImage; import java.awt.image.BufferedImage; /** * 实现QRCodeImage接口, * 设置解码的图片信息 * 核心就是告诉codeDecoder.decode()将要解析的图片类型 * * Created by shihuc on 2020/12/13. */ public class MyQRCodeImage implements QRCodeImage{ BufferedImage bufferedImage; public MyQRCodeImage(BufferedImage bufferedImage){ this.bufferedImage=bufferedImage; } //宽 @Override public int getWidth() { return bufferedImage.getWidth(); } //高 @Override public int getHeight() { return bufferedImage.getHeight(); } //获取给定坐标位置的RGB点位的像素值 @Override public int getPixel(int i, int j) { return bufferedImage.getRGB(i,j); } }
package qcode; import jp.sourceforge.qrcode.QRCodeDecoder; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; /** * 程序进行识别二维码的信息 * 有点类似模拟手机微信扫一扫识别二维码的逻辑,只是这里图片已经存在,不像APP里面扫描二维码是拍摄照片,然后再识别。 * * Created by shihuc on 2020/12/13. */ public class QRCodeRead { public static void main(String[] args) throws IOException { int v = 1; for ( v = 1; v <= 40; v++) { qrReader(v); } } public static void qrReader(int v) throws IOException { //图片路径 File file = new File("D:\\ProTempHome\\margin\\qrcode" + v + ".png"); //读取图片到缓冲区 BufferedImage bufferedImage = ImageIO.read(file); //QRCode解码器 QRCodeDecoder codeDecoder = new QRCodeDecoder(); //通过解析二维码获得信息 String result = new String(codeDecoder.decode(new MyQRCodeImage(bufferedImage)), "utf-8"); System.out.println("识别二维码 v=" + v + "的内容: " + result); } }
直接上了代码,这里需要说明的是,基于前面的二维码生成算法,得到的二维码图片,经过上述的二维码解码程序,都能很好的识别出来。 但是,针对上面的调整w,h(其他参数都不变的情况下),会导致有些二维码识别不出来,直接报错了(v变高的时候,w,h值对识别的影响越发明显,具体的变化根源,有待后续进一步研究,有人若知道,可以给我留言,告知根源是什么)。
微信的扫一扫对二维码识别的能力还是比较强的,如前面,我将二维码变得厚重后,微信基本还是能识别出二维码的内容(不能厚重的太过分。。。)
好了,今天,博文就分享到这里,还是有很多内容需要进一步去研究,希望看官,可以分享一下你的二维码研究心得。
下一篇博客,打算研究一下给二维码中间插入一个漂亮的logo的解决方案,并且研究一下矩阵式二维码的原理和理论,欢迎探讨!