纳尼?二维码尺寸居然不能够自定义大小?

最近在工作中一个小伙伴遇到了一个关于二维码的问题:使用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]+"]");
    }
}

复制代码

感谢

猜你喜欢

转载自juejin.im/post/5d88e153518825095f100dd4