看到二维码,很容易猜到黑白相间的小方格就是二进制比特。那么这些比特是怎么获得的?小方格又是按照什么规则排布的?今天我们就从零开始将一个 url 画成二维码。html
考虑到大多数人可能不太了解二维码,因此先讲下基础概念。你也能够先看看左耳朵耗子写的二维码的生成细节和原理。node
二维码一共有 40 个尺寸,官方叫做版本 Version。最小的 Version 1 是 21 × 21 的矩阵,Version 2 是 25 × 25 的矩阵 … 每增长 1 version,就增长 4 的尺寸。最高是 177 × 177 的矩阵(Version 40)。算法
根据平常使用经验,不管是残缺、遮挡仍是污损,二维码都可以被正确识别,这得益于二维码的纠错机制。shell
纠错是经过纠错码实现的,当咱们将源数据编码后,会采用一种纠错算法算出编码结果对应的纠错码,连同编码结果一块儿填充到二维码。编码
规范里明确了四个纠错等级,不一样等级的纠错比例不一样。url
每一个版本的纠错码个数都是固定的,好比 version 1-L 须要 7 个纠错码,version 1-M 须要 10 个纠错码,版本越大、数据越多、等级越高,须要的纠错码就越多。pwa
二维码能够简单划分为三个部分:固定的功能区(用于定位)、固定的格式信息区(好比二维码用的纠错等级和版本信息)、数据区。code
二进制数据都是严格按 8 位一组填入数据区的,每一组称为一个 codeword。若是数据区的格子数不是 8 的倍数,就会剩下留白(remainder)。每一个版本的功能区、格式信息区和留白的位置及其占用的面积都是固定的。cdn
更详细的划分能够参考规范里的这张图:htm
理论上凡是能够被编译为二进制的数据,均可以用二维码来表示,只要有相应的解码程序便可。规范里规定了几种国际通用的编码模式:
好比 Numeric 仅支持 0 ~ 9 的数字,它的编码规则是:将数据字符串拆分为每三个一组,每组做为一个十进制的百位数,而后转化为 10 位二进制。为何是 10 位呢,由于 2^10 = 1024,刚好能覆盖最大的百位数 999。若是最后一组只有两位,则转化为 7 位二进制,若是只有一位,则转化为 4 位二进制。
Alphanumeric 支持 45 个字符,0 ~ 九、A ~ Z 和几个特殊字符。
Alphanumeric 的编码规则是:根据上表查出每一个 char 对应的 value,而后两两分组,按 A * 45 + B 算出一个值,并将这个值转化为 11 位二进制。若是最后一组只有一位,则将 value 转化为 6 位二进制。
Byte 模式的编码规则最简单,就是针对单字节字符集(例如 ISO-8859-1)的编码,每一个字符刚好用 8 个比特表示。
咱们还能够针对不一样的数据片断采用不一样的编码模式,这就是混合模式。
二维码包含的二进制串具体是由如下几个部分组成的:
最开头必定是编码模式,是固定的 4 位(见编码一节的表格)。
接下来是原字符的长度,不一样版本和不一样编码模式用多少个比特表示长度是不同的,具体是几个,规范里有明确的规定。毕竟二维码要在有限的空间里容纳更多的信息,就不能浪费比特,用过长的二进制去表示一个较小的值。
而后就是对数据按照指定的编码模式编译出的二进制序列。若是以上三部分的数据长度不是 8 的倍数,那么必须添零凑整(terminator)。
上述数据不见得能填满整个数据区,那么剩余的空间就用两个补齐码(11101100、00010001)交错填满。这两个补齐码是规范指定的。
以上整个比特串做为输入,用纠错算法输出特定个数的纠错码,两者相接,就是整个二维码数据区的数据了。
------------------ 分割线 ------------------
接下来,我们就选用最小的版本,和最低的纠错等级,用 Alphanumeric 模式将 I.MEITUAN.COM 这个字符串画成一个二维码。
查表能够获得每一个字符对应的码点,而后两两分组,恰好分红 10 组:(17, 29)(29, 25)(44, 43)(43, 18)(42, 22)(14, 18)(29, 30)(10, 23)(42, 12)(24, 22)。
按照 A * 45 + B 算出:794, 1330, 2023, 1953, 1912, 648, 1335, 473, 1902, 1102。
而后将每一个数字依次转化为二进制(toString(2)):01100011010 10100110010 11111100111 11110100001 11101111000 01010001000 10100110111 00111011001 11101101110 10001001110。
Alphanumeric 的 mode indicator 是 0010,在 version 1 里须要 9 个比特表示长度(20 => 000010100),将它们添加到头部:0010 000010100 01100011010 10100110010 11111100111 11110100001 11101111000 01010001000 10100110111 00111011001 11101101110 10001001110。
按 8 位一组重排,添 0 凑整:00100000 10100011 00011010 10100110 01011111 10011111 11010000 11110111 10000101 00010001 01001101 11001110 11001111 01101110 10001001 11000000。
version 1-L 共有 26 个 codeword,其中有 7 个纠错码(error correction codeword),那么 data codeword 应该有 19 个,目前咱们只有 16 个,所以剩下 3 个须要用补齐码凑足:00100000 10100011 00011010 10100110 01011111 10011111 11010000 11110111 10000101 00010001 01001101 11001110 11001111 01101110 10001001 11000000 11101100 00010001 11101100。
而后算出 7 个纠错码:11010100 01110000 01110111 10010001 00101110 10010101 11001111。(我是用 node-qrcode 反算出来的,搞不懂那个纠错算法)。
最终咱们获得了整个编码结果:
这一步最简单,按照规范里的规定画便可。为了便于区分,用淡紫色表示功能区的白色区域。
将数据按 8 位一组,从右下角开始蛇形往复填入 2 × 4 的矩形。具体怎么填规范里有明确规定,upwards 方向,将第一个数填入位置 7,第二个数填入位置 6 ... 这里是略有点坑的,由于不是从图中所示的 0 到 7,而是反过来的。方便起见,咱们将数据倒过来,再按 0 ~ 7 的顺序依次填入。
masking 的做用是使二维码的分布更加均匀。具体操做就是将数据区和 mask pattern 对应位置的比特作异或运算。好比数据区第 8 行第 10 列是白色 0,mask pattern 第 8 行第 10 列是黑色 1,异或运算的结果是黑色 1,那么就将数据区第 8 行第 10 列改为黑色。官方规定了八种 mask pattern(000 ~ 111),咱们选用 000。
对全部的版本,格式信息都是固定的 15 位。其中两个是纠错等级,三个是 mask pattern,剩下十个是纠错码。
能够想象,没有格式信息,根本没法进行解码。所以格式信息很是重要,会重复画两次(相似于双机房备份)。
左下角那一列开头是永远固定的占位符。
格式信息也须要经历纠错码 + masking 的处理。
一样的,应该反过来,从位置 14 开始填。固然,也能够先把字符串倒过来。
最终结果: