最近一段时间由于需要,差不多花了一周的时间,研究了一下二维码的问题。大致的时间分配是这样的:1~2天学习二维码的原理知识,1~2天分析学习网上的例程,1~2天根据需要修改例程。 最开始的时候,对于二维码一无所知,因而到网上去看了很多二维码原理方面的东西。也根据自己看的一些东西准备整理一些自己认为重要的东西(见上篇《二维码原理简介》)。但是,看到最后发现,二维码的原理中涉及到很多数学方面的知识,很多文章在讲解的同时会掺杂很多专业性较强的东西。笔者自认为不是一个数学天才,也不愿意花费过多的时间去阅读数学方面的知识。所以那篇《二维码原理简介》的确称得上是简介了。笔者只是希望能有一个感性的认识,知道诸如“通过识别三个边角上的正方形,即可唯一确定二维码”、“二维码的具体信息对应图片的哪一块区域”、“二维码的容错的能力”等等。
1.初试牛刀 首先,笔者在网上下载了一个用java语言实现的二维码生成和编写的程序。其结构非常简单,如下图:
其主要的底层功能主要由QRCode.jar包来实现(即具体的那些底层东西)。QR Code码是由日本Denso公司于1994年9月研制的一种矩阵二维码符号,它具有一维条码及其它二维条码所具有的信息容量大、可靠性高、可表示汉字及图象多种文字信息、保密防伪性强等优点。 在这个demo中,主要有2个类,一个就是EncodeImg,另一个就是TwoDimensionCodeImage。TwoDimensionCodeImage类中定义了一个BufferedImage类的变量,这个变量就是二维码图片的长、宽和颜色(黑色与白色)的信息。在后面解码二维码图片会调用它。 EncodeImg则实现了所有的逻辑。这个类中定义了6个方法(还有一些方法调用这几个方法,类似于接口,此处略去不提),分别是:2个生成二维码方法、1个生成二维码公共方法(前面2个方法会调用它)、2个解析二维码 和一个main方法。结构也是十分清晰。我们逐个来分析。
生成QRCode图片 1:
/** * 生成二维码(QRCode)图片 * @param content 存储内容 * @param imgPath 图片路径 * @param imgType 图片类型 * @param size 二维码尺寸 */ public void encoderQRCode(String content, String imgPath, String imgType, int size) { try { BufferedImage bufImg = this.qRCodeCommon(content, imgType, size); File imgFile = new File(imgPath); // 生成二维码QRCode图片 ImageIO.write(bufImg, imgType, imgFile); } catch (Exception e) { e.printStackTrace(); } } |
生成QRCode图片 2:
/** * 生成二维码(QRCode)图片 * @param content 存储内容 * @param output 输出流 * @param imgType 图片类型 * @param size 二维码尺寸 */ public void encoderQRCode(String content, OutputStream output, String imgType, int size) { try { BufferedImage bufImg = this.qRCodeCommon(content, imgType, size); // 生成二维码QRCode图片 ImageIO.write(bufImg, imgType, output); } catch (Exception e) { e.printStackTrace(); } } |
大家可以仔细比对一下这两个方法,会发现二者仅仅在参数上有略微不同。一种是以图片的形式保存生成的二维码,一种则把结果写入到数据流中。既然涉及数据流,多半都是和网络传输部分有关(这句是个人臆想)。而二者的共同点就是一开始都调用了this.qRCodeCommon(content, imgType, size)这个方法。那么这个方法具体是实现了什么呢?
生成QRCode的公共方法:
/** * 生成二维码(QRCode)图片的公共方法 * @param content 存储内容 * @param imgType 图片类型 * @param size 二维码尺寸 * @return */ private BufferedImage qRCodeCommon(String content, String imgType, int size) { BufferedImage bufImg = null; try { Qrcode qrcodeHandler = new Qrcode(); // 设置二维码排错率,可选L(7%)、M(15%)、Q(25%)、H(30%),排错率越高可存储的信息越少,但对二维码清晰度的要求越小 qrcodeHandler.setQrcodeErrorCorrect('M'); qrcodeHandler.setQrcodeEncodeMode('B'); // 设置设置二维码尺寸,取值范围1-40,值越大尺寸越大,可存储的信息越大 qrcodeHandler.setQrcodeVersion(size); // 获得内容的字节数组,设置编码格式 byte[] contentBytes = content.getBytes("utf-8"); // 图片尺寸 int imgSize = 67 + 12 * (size - 1); bufImg = new BufferedImage(imgSize, imgSize, BufferedImage.TYPE_INT_RGB); Graphics2D gs = bufImg.createGraphics(); // 设置背景颜色 gs.setBackground(Color.WHITE); gs.clearRect(0, 0, imgSize, imgSize);
// 设定图像颜色> BLACK gs.setColor(Color.BLACK); // 设置偏移量,不设置可能导致解析出错 int pixoff = 2; // 输出内容> 二维码 if (contentBytes.length > 0 && contentBytes.length < 800) { boolean[][] codeOut = qrcodeHandler.calQrcode(contentBytes); for (int i = 0; i < codeOut.length; i++) { for (int j = 0; j < codeOut.length; j++) { if (codeOut[j][i]) { gs.fillRect(j * 3 + pixoff, i * 3 + pixoff, 3, 3); } } } } else { throw new Exception("QRCode content bytes length = " + contentBytes.length + " not in [0, 800]."); } gs.dispose(); bufImg.flush(); } catch (Exception e) { e.printStackTrace(); } return bufImg; } |
相信配上注释,代码的可读性大大提高。可见,红色部分标记的语句是完成输出内容到二维码转换的重要步骤。可惜,这个方法qrcodeHandler.calQrcode(contentBytes)被封装在QRCode.jar文件中,无法继续分析。有了这一步,后面的逻辑就很简单了,遍历二维数组,codeOut[j][i]如果是1则将图片相应位置设为黑色。最后得到二维码图片。
对应地,有2种解码方法。
解析QRCode方法1:
/** * 解析二维码(QRCode) * @param imgPath 图片路径 * @return */ public String decoderQRCode(String imgPath) { // QRCode 二维码图片的文件 File imageFile = new File(imgPath); BufferedImage bufImg = null; String content = null; try { bufImg = ImageIO.read(imageFile); QRCodeDecoder decoder = new QRCodeDecoder(); content = new String(decoder.decode(new TwoDimensionCodeImage(bufImg)), "utf-8"); } catch (IOException e) { System.out.println("Error: " + e.getMessage()); e.printStackTrace(); } catch (DecodingFailedException dfe) { System.out.println("Error: " + dfe.getMessage()); dfe.printStackTrace(); } return content; } |
解析QRCode方法2:
/** * 解析二维码(QRCode) * @param input 输入流 * @return */ public String decoderQRCode(InputStream input) { BufferedImage bufImg = null; String content = null; try { bufImg = ImageIO.read(input); QRCodeDecoder decoder = new QRCodeDecoder(); content = new String(decoder.decode(new TwoDimensionCodeImage(bufImg)), "utf-8"); } catch (IOException e) { System.out.println("Error: " + e.getMessage()); e.printStackTrace(); } catch (DecodingFailedException dfe) { System.out.println("Error: " + dfe.getMessage()); dfe.printStackTrace(); } return content; } | 二者的区别,在此不再赘述。二者蓝色部分就是解码的部分。同样被封装到QRcode.jar中。我们无法看到更多的内容。
最后再来看看main方法:
public static void main(String[] args) { String imgPath = "F:/Michael_QRCode3.png"; String encoderContent = "我名为宇智波斑 "; EncodeImg handler = new EncodeImg(); handler.encoderQRCode(encoderContent, imgPath, "png"); System.out.println("========encoder success"); String decoderContent = handler.decoderQRCode(imgPath); System.out.println("解析结果如下:"); System.out.println(decoderContent); System.out.println("========decoder success!!!"); } |
运行该程序,即可在电脑的F盘找到生成的二维码图片Michael_QRCode3.png。在eclipse的控制台窗口也是可以看到解析的结果。此时,你用手机上的二维码扫描软件扫描一下生成的二维码,同样可以得到正确的结果。
总结:可以看到,这个java程序的接口还是相对较简单的,使用起来也很方便。笔者本来是要写一个Android的程序的。于是想把这个Java程序移植到一个Android工程上。但事情远比想象中的复杂。当把这些文件放到一个Android程序中发现,程序中出现了很多错误。这些错误主要集中在那些绘图相关的包上。网上有人说Android和java在绘图上的实现好像是不兼容的,需要自己做相应的移植。笔者仍不想放弃,看到"JRE System Libruary"下面有很多jar包,将它们全部导入Android程序中。则程序可以成功编译。但是程序在运行时,点击生成二维码时出现闪退的现象。根据打印的Log发现仍然是因为Android中不包含相应绘图APi。至此,笔者决意放弃这个java程序。
|