二维码可以包含多少个字符?

目录

  • 一、二维码的种类和容错等级
  • 二、QR-Code的最大字符串容量
  • 三、Python库中的函数流程处理
  • 四、不同字符类型具有不同的数据长度
  • 五、C语言库中的函数流程处理
  • 六、使用Python验证计算结果

一、二维码的种类和容错等级

二维码分为很多种,其中最常用的是QR Code二维码。

QR码的纠错容量有四个级别,分别是:

  1. ERROR_CORRECT_L ,L级别,7%或更少的错误能修正;
  2. ERROR_CORRECT_M ,M级别,15%或更少的错误能修正,是qrcode的默认级别;
  3. ERROR_CORRECT_Q, Q级别,25%或更少的错误能修正;
  4. ERROR_CORRECT_H ,H级别,30%或更少的错误能修正。

二、QR-Code的最大字符串容量

那么把讨论范围限定在QR码,在4种容错等级下,最多能够包含多少字符呢?

这个问题非常简单,测试一下就知道了:

import qrcode

for level in 'LMQH':
    for i in range(200, 1000):
        try:
            qrcode.make('he11o' * i, error_correction=getattr(qrcode, 'ERROR_CORRECT_' + level))
        except Exception:
            print('%s: %d, ' % (level, 5 * (i - 1)), end='')
            break

输出结果:

L: 2950, M: 2330, Q: 1660, H: 1270

三、Python库中的函数流程处理

根据上面的测试结果,当容错等级为M,可以生成长度不超过2330的字符串二维码。

我们可以手动测试一下超过这个长度的情况:

>>> qrcode.make('x' * 2350)
Traceback (most recent call last):
  ...
  File "D:\Python36\lib\site-packages\qrcode\main.py", line 167, in best_fit
    raise exceptions.DataOverflowError()
qrcode.exceptions.DataOverflowError

根据报错提示,发生在qrcode.main库的line 167,报出了exceptions.DataOverflowError类型的错误。

追查这段代码的位置:

self.version = bisect_left(util.BIT_LIMIT_TABLE[self.error_correction], needed_bits, start)
if self.version == 41:
    raise exceptions.DataOverflowError()

其中self.version是二维码的等级大小,合法值应在1-40的范围内。

很显然,当version等于41的时候就非法了,二维码太大了,发生报错。

进入qrcode.util库里面查看BIT_LIMIT_TABLE的定义,是一个比较复杂的列表推导公式。

但是这个数据不依赖于输入,我们可以直接打印出来看看,是一个二维列表,里面总共有4个子列,每个子列里有41个元素。

那就很显然了,4个子表对应二维码的4种纠错等级41个元素对应二维码的41种尺寸等级

把这4个子列的最大值打印出来、并且按从大到小的顺序排序:

>> sorted([row[-1] for row in qrcode.util.BIT_LIMIT_TABLE], reverse=True)
[23648, 18672, 13328, 10208]

好像和之前测试出来的四个数字L: 2953, M: 2331, Q: 1663, H: 1273没有什么关系?

不,等等,这不是差了8倍多一点的关系吗?

咱再看看BIT_LIMIT_TABLE这个参数命名,BIT…1个byte等于8个bit。这个语法命名还是非常清晰易懂的嘛。

那么我们把BIT_LIMIT_TABLE里的数值除以8以后,再次测试:

limit_table = sorted([row[-1] >> 3 for row in qrcode.util.BIT_LIMIT_TABLE], reverse=True)
print('LIMIT_TABLE:', limit_table)

string = 'he11o' * 600
for st, lv in zip(limit_table, 'LMQH'):
    print('%s: ' % lv, end='')
    for i in range(st - 10, st + 10):
        try:
            qrcode.make(string[:i], error_correction=getattr(qrcode, 'ERROR_CORRECT_' + lv))
        except Exception:
            print('%d, ' % (i - 1), end='')
            break

输出结果:

LIMIT_TABLE: [2956, 2334, 1666, 1276]
L: 2953, M: 2331, Q: 1663, H: 1273

发现了很显然的对应关系,和LIMIT_TABLE的每一个级别都相差了3个byte

四、不同字符类型具有不同的数据长度

当我把前面测试文本中的he11o换成12345,同样长度的文本却没有发生报错。

推测当二维码中只有数字时,可能会进行某种压缩,以提高信息的传递量。

但是同样的代码测试速度太慢了,改成了二分法测试加速运算:

string = '12345' * 2000
for level in 'LMQH':
    a = 0
    for i in range(16, -1, -1):
        b = 1 << i
        try:
            qrcode.make(string[:a+b], error_correction=getattr(qrcode, 'ERROR_CORRECT_' + level))
            a += b
        except Exception:
            pass
    print('%s: %d, ' % (level, a), end='')

输出结果:

L: 7089, M: 5596, Q: 3993, H: 3057

可以发现规律,这组数字等于LIMIT_TABLE对应数字的2.4倍减5取整

五、C语言库中的函数流程处理

2.4倍是从哪里出现的?我在Python库的源代码中未能追查到线索。

但是我在另一个开源的QR-Code-generator项目的qrcodegen.c文件中,看到这样一段代码:

其中主要调用的函数qrcodegen_encodeText

bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[],
	...
	size_t textLen = strlen(text);
	...
	size_t bufLen = (size_t)qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion);
	struct qrcodegen_Segment seg;
	if (qrcodegen_isNumeric(text)) {
		if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_NUMERIC, textLen) > bufLen)
			goto fail;
	...

fail:
	...
	return false;
}

当调用函数qrcodegen_calcSegmentBufferSize的返回值大于bufLen时,生成二维码失败。

而在函数qrcodegen_calcSegmentBufferSize中,简单调用了函数calcSegmentBitLength

testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars) {
	// All calculations are designed to avoid overflow on all platforms
	if (numChars > (unsigned int)INT16_MAX)
		return -1;
	long result = (long)numChars;
	if (mode == qrcodegen_Mode_NUMERIC)
		result = (result * 10 + 2) / 3;  // ceil(10/3 * n)
	else if (mode == qrcodegen_Mode_ALPHANUMERIC)
		result = (result * 11 + 1) / 2;  // ceil(11/2 * n)
	else if (mode == qrcodegen_Mode_BYTE)
		result *= 8;
	else if (mode == qrcodegen_Mode_KANJI)
		result *= 13;
	else if (mode == qrcodegen_Mode_ECI && numChars == 0)
		result = 3 * 8;
	else {  // Invalid argument
		assert(false);
		return -1;
	}
	assert(result >= 0);
	if (result > INT16_MAX)
		return -1;
	return (int)result;
}

result来自于textLen,是字符串的字节长度。

很显然,当二维码的模式属于NUMERIC时,对字符串长度进行了10/3倍的向上取整转换。表达的含义是一个NUMERIC类型的字符等于10/3bit

bit数目本身是byte的8倍,所以8/(10/3)=2.4,就是这么来的。

那么以此类推,当模式属于ALPHANUMERIC时,倍数等于16/11

模式属于BYTE时,倍数等于1

六、使用Python验证计算结果

for j in range(256):
    c = bytes([j])
    print()
    print(j, c, end=': ')
    for level in 'LMQH':
        a = 0
        for i in range(16, -1, -1):
            b = 1 << i
            try:
                qrcode.make(c * (a + b), error_correction=getattr(qrcode, 'ERROR_CORRECT_' + level))
                a += b
            except Exception:
                pass
        print('%s: %d, ' % (level, a), end='')

qrcode.util库中,可以看到参数ALPHA_NUM的定义:

ALPHA_NUM = b’0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:’

显而易见,当字符集属于这个范围时,模式将被判定为前面提到的NUMERIC

而当字符属于0-9的时候,存在额外的规则可将模式判定为MODE_ALPHA_NUM

如果用一个公式来表示,其中limit表示4种容错等级在LIMIT_TABLE中的对应值,k表示不同字符模式的压缩系数,在3种模式下分别为2.416/111。公式可以表示为:

int(((limit - 2) - 1 / 8) * k)

生成QR-Code二维码,LMQH四个容错等级的二维码最大字符容量是:

字符类别 L M Q H
0-9 7089 5596 3993 3057
ALPHA_NUM 4296 3391 2420 1852
其他 2953 2331 1663 1273
LIMIT_TABLE 2956 2334 1666 1276

在Python中运行的测试程序符合理论计算结果。

你可能感兴趣的:(Python,GUI,python)