title: 生成二维码的原理 toc: true cover: 'https://img.paulzzh.com/touhou/random?89' date: 2020-12-07 20:57:24 categories: 技术杂谈 tags: [技术杂谈, 二维码]
二维码现在已经是日常生活中随处可见的了,本文讲解了二维码的原理,并且最后手把手教你使用JS创建一个二维码;
本文译自:
<br/>
<!--more-->下面是一个JavaScript生成QR Code的demo:
https://www.nayuki.io/page/creating-a-qr-code-step-by-step
下面以生成HelloWorld
的二维码为例讲述了通过JavaScript是如何实现的;
<br/>
HelloWorld
包含了10个Unicode字符,每个字符的Unicode如下表:
Index | Char | CP hex | NM | AM | BM | KM |
---|---|---|---|---|---|---|
0 | H | U+48 | No | Yes | Yes | No |
1 | e | U+65 | No | No | Yes | No |
2 | l | U+6C | No | No | Yes | No |
3 | l | U+6C | No | No | Yes | No |
4 | o | U+6F | No | No | Yes | No |
5 | W | U+57 | No | Yes | Yes | No |
6 | o | U+6F | No | No | Yes | No |
7 | r | U+72 | No | No | Yes | No |
8 | l | U+6C | No | No | Yes | No |
9 | d | U+64 | No | No | Yes | No |
在对每个字符进行Encode编码时,要选择对应的编码方式,上面的NM、AM、BM、KM分别表示:
如下表:
Mode | Encodable |
---|---|
Numeric | No |
Alphanumeric | No |
Byte | Yes |
Kanji | No |
最后选择各个字符都可以使用的二进制编码进行编码:Byte;
<br/>
将每个字符转换为bit位;
对于数字和字母数字模式而言,连续字符会被编码组合在一起;
对于字节模式,字符会产生8、16、24或32位;
如下表:
Index | Char | Values (hex) | Bits |
---|---|---|---|
0 | H | 48 | 01001000 |
1 | e | 65 | 01100101 |
2 | l | 6C | 01101100 |
3 | l | 6C | 01101100 |
4 | o | 6F | 01101111 |
5 | W | 57 | 01010111 |
6 | o | 6F | 01101111 |
7 | r | 72 | 01110010 |
8 | l | 6C | 01101100 |
9 | d | 64 | 01100100 |
创建的单个数据段如下:
为了简单起见,这个演示程序创建了一个单独的段;
但也可以对文本进行最佳分割,以最小化总比特长度,见:
<br/>
实际的二维码表示每个数据段所需的总位长度,取决于版本:
Range** | Num bits | Num codewords |
---|---|---|
Version 1 ~ 9 | 148 | 19 |
Version 10 ~ 26 | 156 | 20 |
Version 27 ~ 40 | 156 | 20 |
注:一个
codeword
被定义为8位,即一个字节(byte);
每个版本数据段的二维码容量和纠错级别,以及数据格式:
Version | ECC L | ECC M | ECC Q | ECC H |
---|---|---|---|---|
1 | 19 | 16 | 13 | 9 |
2 | 34 | 28 | 22 | 16 |
3 | 55 | 44 | 34 | 26 |
4 | 80 | 64 | 48 | 36 |
5 | 108 | 86 | 62 | 46 |
6 | 136 | 108 | 76 | 60 |
7 | … | … | … | … |
我们选择的版本是:1;
<br/>
将各个位字符串串连接在一起:
Item | Bit data | Num bits | Sum bits |
---|---|---|---|
Segment 0 mode | 0100 | 4 | 4 |
Segment 0 count | 00010001 | 8 | 12 |
Segment 0 data | 01001000<br />01100101<br />01101100<br />01101100<br />01101111<br />00101100<br />00100000<br />01110111<br />01101111<br />01110010<br />01101100<br />01100100<br />00100001<br />00100000<br />00110001<br />00110010<br />00110011 | 136 | 148 |
Terminator | 0000 | 4 | 152 |
Bit padding | 0 | 152 | |
Byte padding | 0 | 152 |
注:
整个数据位序列:
010000010010010010110010110110001101100011101001011000101100001000010111101110111011000110001100011000110001100011000110001100011000110000
若以十六进制显示整个序列,则是:
41 14 86 56 C6 C6 F2 C2 07 76 F7 26 C6 42 12 03 13 23 30
<br/>
上述整个十六进制被分为了多个块,信息如下:
Number of data codewords: | 19 |
---|---|
Number of blocks: | 1 |
Data codewords per short block: | 19 |
Data codewords per long block: | N/A |
ECC codewords per any block: | 7 |
Number of short blocks: | 1 |
Number of long blocks: | 0 |
将数据码字序列(0 ~ 19)拆分为短块和长块;
然后针对每个块,计算ECC码字(20 ~ 26)并将其附加到块的末尾:
:
Block index | |
---|---|
0 | |
0 | 41 |
1 | 14 |
2 | 86 |
3 | 56 |
4 | C6 |
5 | C6 |
6 | F2 |
7 | C2 |
8 | 07 |
9 | 76 |
10 | F7 |
11 | 26 |
12 | C6 |
13 | 42 |
14 | 12 |
15 | 03 |
16 | 13 |
17 | 23 |
18 | 30 |
19 | |
20 | 85 |
21 | A9 |
22 | 5E |
23 | 07 |
24 | 0A |
25 | 36 |
26 | C9 |
注:此处省略了计算Reed-Solomon纠错码的过程;
结合来自不同块的数据/ECC码字形成的最终码字序列:
41 14 86 56 C6 C6 F2 C2 07 76 F7 26 C6 42 12 3 13 30 85 A9 5E 07 0A 36 C9
对应的Z字扫描:
0100000100010100100001100101011011000110110001101111001011000010000001110111011011110111001001101100011001000010000100100000001100010011001000110011000010000101101010010101111000000111000010100011011011001001
<br/>
绘制水平和垂直基准线(在第6行和第6列上,从左上角开始,从0开始计算):
在三个角绘制7×7查找器图案(此时会覆盖一些基准模块):
在查找器相邻区域绘制临时的格式占位图案:
<br/>
计算Z字形扫描(从右下角开始)访问所有未填充的模块(跳过部分已经确定的部分):
根据Z字形扫描顺序和最终码字序列的位值绘制数据/ECC模块:
例如,二进制代码字的字节C5对应二进制为11000101,则生成模块序列:
黑、黑、白、白、白、黑、白、黑;
<br/>
有多种Mask的样式,Mask的样式仅影响非确定部分:
将数据、ECC和剩余其他部分和Mask进行异或:
紧靠查找器绘制实际的格式位:
<br/>
水平相同颜色的区域(每个至少5个单位长):
垂直相同颜色的区域(每个至少5个单位长):
2×2颜色相同的区域:
水平和查找器相同的样式:
垂直和查找器相同的样式:
我认为,此步操作是为了避免识别到多个查找器样式;
结果统计:
Side length: | 21 |
---|---|
Total modules: | 441 |
White modules: | 217 |
Black modules: | 224 |
Proportion black: | 50.794% |
Deviation from half: | 0.794% |
<br/>
Mask | RunP | BoxP | FindP | BalP | TotalP |
---|---|---|---|---|---|
0 | 205 | 159 | 840 | 0 | 1204 |
1 | 187 | 147 | 800 | 0 | 1134 |
2 | 173 | 111 | 800 | 0 | 1084 |
3 | 167 | 114 | 800 | 0 | 1081 |
4 | 195 | 126 | 800 | 0 | 1121 |
5 | 181 | 159 | 760 | 0 | 1100 |
6 | 183 | 126 | 880 | 0 | 1189 |
7 | 183 | 114 | 840 | 0 | 1137 |
最低总罚分:Mask 3;
如何计算罚分:
finder-like
)图案得40分。可以重叠计分;<br/>
本文译自:
延申阅读:
<br/>