前情提要

早就听闻天枢&天璇战队里面个个都是人才,说话又好听,还会肉眼扫人脸发送微信好友请求。

众所周知,大部分普通人都是通过微信扫二维码添加好友请求的,那“肉眼扫人脸发送微信好友请求”这一看似复杂的动作就可以简单的分成以下两步:

  1. 将人脸转化为微信好友二维码。
  2. 肉眼扫描转化后的二维码。

下面简要讲讲第二步的思路。

二维码基础知识

二维码分为很多种,应用最广泛的一种二维码叫做“快速响应矩阵图码”(QR Code)。除了我们常见的QR码外,使用比较广泛的还有:PDF417、DM、汉信码等。下面所说的“二维码”都指QR码。

二维码格式示例如下:

定位图案

Position Detection Pattern(位置探测图形)是定位图案的一种,就是每个二维码都有的左上、左下和右上三个角的“回”字形的标志。用于标记二维码的矩形大小。这三个定位图案有白边叫Separators for Postion Detection Patterns。之所以三个而不是四个,因为三个就足以标识一个矩形了,用四个反而多余,且会使得能够表示的数据空间变小,扫描器在进行二维码扫描的时候会根据这三个定位标识符来更正二维码的坐标,方便进行扫描。这块区域的尺寸固定,无论是哪个版本的二维码,他的尺寸都是7*7的模块。

Alignment Patterns(校正图形) 只有在Version 2以上(包括Version2)的二维码中需要这个东西,同样是为了定位用的。它的尺寸也是固定的,为5*5的模块。

格式信息

Format Information 存在于所有的版本中,用于存放一些格式化数据的,通过读取这部分的内容,可以知道当前二维码的纠错等级、掩码类别。主要内容为“纠错等级(2bit)+ 掩码类别(3bit)+ BCH code(10bit,用于纠错)”,然后这15个bits还要与101010000010010做XOR操作,主要是为了如果选用了00的纠错级别和000的Mask,从而造成全部为白色,这会增加扫描器的图像识别的困难。

为了增强二维码的容错能力,保证在一定的损坏范围内,不会影响数据的读取,共设计了两个区域来存放两条一模一样的格式信息。
这15个bit在format information区域内的分布以及顺序如下(下图中数字的顺序就是这15个bit的存放顺序,应当注意的是这些数字表示的是位的高低,也就是当获取到格式信息15个bit长度的二进制字符串时,左边为高位,右边为低位,所以最左侧的二进制数字应该在14的位置,最右侧的二进制数字应该在0的位置):

版本信息

Version Information 在>= Version 7的版本中,预留两块3*6的区域存放一些版本信息。

这里可以查询所有版本、掩码及纠错码的格式信息及版本信息。

数据码字和纠错码字

除了上述的那些地方,剩下的地方存放 Data Code 数据码字 和 Error Correction Code 纠错码字。我们后面就简称数据码和纠错码,就是最前面两张图的深灰色区域,一般数据都是从右下角开始填充,先填充数据码,数据码填充完毕之后再填充纠错码,以version1为例,数据的填充顺序,是这样的:

当然,随着版本的升高,会有越来越多的校正图形掺杂在其中,这样的话,数据填充可能就不是这么规矩的矩形了,但是总体的填充顺序不会大变化,都是先右后左的顺序。具体的可参考官方的文档。

解码流程

有了上面这些前置信息,我们就可以开始手撕二维码了。

以封面二维码(如上图)为例,首先我们先读取format information区域内的掩码信息。

对照上面第一张掩码表后(或异或101后对照第二张掩码表),得出异或的图案为(i + j) % 2 = 0
之后再用掩模进行异或,这里推荐一个好用的工具Qrazybox。可以自动实现掩模异或功能。

异或后二维码如下图所示:

从右下角按从右往左、从下往上的顺序依次读取。前四位代表模式编码(具体参照下图),接着八位代表数据长度,接下来就是数据码区域。

前四位(红色框内数据)0100表示UTF-8模式,接下来八位(黄色框内数据)00000110表示6字节的数据长度。那么我们只需要读接下来的6字节数据就可以了,具体如下。

序号 二进制字节 解码后数据
1 01010110
2 11100110
3 10001000
4 10010001
5 00110101
6 00110000

可以看见这是一篇著名文章的结尾部分。

我依稀记得那是一个夏天。
稚童的我和小伙伴正模仿庙会上翻飞的彩狮。
从未见过的人和从未见过的机器喧嚷着碾过村里的田坎。
现代化的浪潮就这样闯入了我的生活。
当时的我没有太多感触,只是觉得路变宽了,大人们都忙了起来。
我们是乡里远近闻名的少年舞狮队,田大爷说,我们将会是以后庙会上的主角。
但是再也没有庙会了。
机器的轰鸣碾碎了村口闲聊的碎语,碾碎了田垄上劳作的农歌,扬起满天尘埃,在阳光的反射中勾勒一副似乎是幸福的画卷。
它像是一条条线,将每个人细细的拆分开。
我考上了高中,然后是大学,我离开了大山。
从前幻想着在庙会上跃动的小伙伴们也四散而去。
城市,城市。
我初次踏上那里的土地,给我的是一种来自灵魂的震荡。
它像是一只猛兽,不停的吞噬四周,大山不能阻挡它,我也不能。
一切好像都会被裹挟其中,然后成为它的一部分。
但是村子最终并没有融入其中,阻挡它的不是大山,也不是任何人,而是枯竭的矿脉。
当我回到村子的时候,记忆里繁忙的路上只剩三两只白鸭阔步而行。
正是一个星期的中点,曾是村子里最喧闹的时节,但现在寂静无声。
再没有机器的轰鸣,也没有工人劳动的号子,更没有农妇们闲聊的细语。
浪潮曾经席卷而过,退潮也几乎带走了它曾带来的一切,还带走了所有的年轻人,只留下零散的历史遗留者。
李老师,现在的李大爷帮我打开了老屋的大门。
老屋还是老屋,只是布满了灰尘。意外的,我在长满了杂草了的杂物房中寻到了一个褪色的狮头,脆弱的它居然能在岁月的刻刀下幸存。
随手拿起它,眼前似乎又浮现当年壮志满怀的小伙伴,耳畔仿佛又响起庙会锣鼓的鼓点。
烈阳如火,依稀还是那个夏天。
“封———矿———啦———”
一声悠远的雷响不知从何而来,在山间回荡不绝,为曾路过小村的潮水刻上一个终点。
封矿星期四,唯我舞狮。

写在最后

为什么没有纠错码的部分?只要将第一步“将人脸转化为微信好友二维码”做好,就不存在数据丢失的问题,自然用不到纠错码。