最近一段时间由于需要,差不多花了一周的时间,研究了一下二维码的问题。大致的时间分配是这样的: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
boolean[][] codeOut = qrcodeHandler.calQrcode(contentBytes);
for (int i = 0; i
for (int j = 0; 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程序。
最近一段时间由于需要,差不多花了一周的时间,研究了一下二维码的问题。大致的时间分配是这样的: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
boolean[][] codeOut = qrcodeHandler.calQrcode(contentBytes);
for (int i = 0; i
for (int j = 0; 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程序。