最近在工作中一个小伙伴遇到了一个关于二维码的问题:使用zxing,给定指定大小二维码尺寸,当尺寸小于某一范围时,生成的二维码尺寸与规定尺寸差异较大。 举个例子: 比如我给定了二维码生成尺寸为 36 * ️36px,生成出来的二维码实际大小21 * 21 px。
原理
如果对于二维码机制比较了解的同学,应该都知道,二维码有着良好的纠错能力以及较大的携带信息量能力。为了实现不同尺度能够更好的完成的信息处理,二维码规定了40个级别的规格,生成尺寸范围为21 * 21 ~ 177 * 177。
在我们上述场景中,我们使用的zxing生成的二维码为version 1,返回回来的二维码为 21 * 21 的一个矩阵信息,给定的尺寸36 * 36 不是 21 的整数倍,只能容纳1个21 * 21 尺寸的信息,因此我们将信息矩阵转化为二维码图片的时候也只能生成21 * 21 px的二维码。
具体的生成原理及细节在这篇文章中已经做了非常详细描述,请参见二维码(QR code)基本结构及生成原理。在这里我只列举与本文有关的知识点:
1、Version
二维码共有1到40的不同版本,每个版本都具备固有的“码元结构”。(即二维码的方形黑白点)从版本1(21码元×21码元)开始,在纵向和横向各自以4码元为单位递增,一直到版本40(177码元×177码元)。
2、纠错级别
即容错率,共有4个级别,级别越高纠错能力也相应提高,但由于数据量会随之增加,编码尺寸也也会变大。
纠错级别 | 容错能力 |
---|---|
L | 约7% |
M | 约15% |
O | 约25% |
Q | 约30% |
3、编码方式
即二维码信息编码方式,二维码可按照数字、英文字母数字、二进制、汉字、日文等方式对信息进行编码。
如何解决
从上述二维码原理得知,二维码尺寸总是40个版本中的某一尺寸的倍数,因此对于较小尺寸二维码的生成,可以通过调整二维码 version与容错能力以及编码方式来尽量与给定尺寸的需求。在我们实际生产环境中,我们使用的是utf8来编码信息,因此二维码尺寸就取决于所选择的版本以及纠错级别。 具体思路为:
- 1、查找最接近给定尺寸的version
- 2、判定该version是否能够容纳给定信息,查找最优容错级别
- 3、返回编码尺寸及容错级别 代码如下:
public class QrCodeTest {
//各尺寸最大容量,具体数据请参考 https://www.qrcode.com/zh/about/version.html
static final int[][] VERSION_ERROR_ARR = new int[][]{
{10,8,7,4},{20,16,12,8},{32,26,20,15},{48,38,28,21},
{65,52,37,27},{82,65,45,36},{95,75,53,39},{118,93,66,52},
{141,111,80,60},{167,131,93,74},{198,155,109,85},{226,177,125,96},
{262,204,149,109},{282,223,159,120},{320,254,180,136},{361,277,198,154},
{397,310,224,173},{442,345,243,191},{488,384,272,208},{528,410,297,235},
{572,438,314,248},{618,480,348,270},{672,528,376,284},{721,561,407,315},
{784,614,440,330},{842,652,462,365},{902,692,496,385},{940,732,534,405},
{1002,778,559,430},{1066,843,604,457},{1132,894,634,486},{1201,947,684,518},
{1273,1002,719,553},{1347,1060,756,590},{1417,1113,790,605},{1496,1176,832,647},
{1577,1224,876,673},{1661,1292,923,701},{1729,1362,972,750},{1817,1435,1024,784}
};
public static int[] getQrCodeVersionAndError(String str, int size) throws Exception{
if(21 > size){
throw new IllegalAccessException("二维码尺寸不能小于21*21px");
}
//查找最接近给定尺寸的version
int tVersion = (int)Math.floor((size - 21)/4.0);
int errorCode = -1;
int len = str.length();
int minLen = len;
int tLen1 = Math.abs(size - tVersion * 4 - 21);
int tLen2 = Math.abs(size - (tVersion + 1) * 4 - 21);
int version = tLen1 <= tLen2 ? tVersion : tVersion + 1;
if(VERSION_ERROR_ARR[version][0] > len){
return new int[]{version, 0};
}
//尺寸过小,不能容纳用户给定数据
for(int i = version+1; i < VERSION_ERROR_ARR.length; i++){
for(int j = 0; j < VERSION_ERROR_ARR[i].length; j++){
int min = Math.abs(len - VERSION_ERROR_ARR[i][j]);
if(min == 0){
minLen = min;
version = i;
errorCode = j;
break;
}else if(min <= minLen){
minLen = min;
version = i;
errorCode = j;
}
}
if(-1 != errorCode){
break;
}
}
if(-1 == errorCode){
throw new IllegalAccessException("该字符串内容过大");
}
return new int[]{version, errorCode};
}
public static void main(String[] args) throws Exception{
print("hello",21);
print("hello",22);
print("hellohellohellohellohello",42);
print("hello",43);
}
private static void print(String str, int size) throws Exception{
int[] result = getQrCodeVersionAndError(str,size);
System.out.println(str+","+size + ":["+result[0] + "," + result[1]+"]");
}
}
复制代码