跳到主要内容

小程序码生成技术分析

· 阅读需 18 分钟
Arce
Unity Developer (Weak Version)

阅读之前

异或操作

在此之前,我们先来学习一下逻辑异或(XOR,符号是⊕ )的基本知识,请看下面这个表格。

A ⊕ B ,当 AB 不等时值为1,AB 相等时值为0。A和同一个 B 经过两次异或操作,结果跟原码相同(A ⊕ B ⊕ B = A)。

背景

QRCode

QR码/图码 (英语:Quick Response Code;全称为 快速响应矩阵图码 )是二维码的一种,于1994年由日本汽车零组件大厂电装公司原昌宏所发明 。

QR码的结构指示图

异形码

小程序码属于异形码,异形二维码并不是微信的首创,Facebook、Snap 等公司都已经推出过类似的异形码:

小程序VS传统二维码

传统二维码往往以有下几个缺点:

  • 扫码预期:每张二维码的背后可能代表一个文件,一个页面、又或者是一个应用
  • 安全性:二维码由于其开放性,很容易成为木马病毒的温床,很多人会担心扫码之后可能使自己的手机感染病毒而放弃扫码
  • 品牌宣传:无法满足小程序的品牌宣传需求

小程序码的优点:

  • 观赏性:小程序码与普通二维码相比,看起来更美观
  • 扫码预期:扫码前能明确知道扫码之后将会体验到一个小程序
  • 安全性:小程序码目前只能通过微信产生,并且只能通过微信识别,安全性更高
  • 品牌宣传:每个小程序码右下角都是固定的微信小程序 Logo,每见到一次小程序码,大家就能多一次联想到微信小程序
  • 高容错性:当一张二维码图片中间嵌有某些 Logo 图片时,其实相当于是把最中间部分有用的编码信息挖掉,再贴一张 Logo 图片上去。而小程序码不同,中间的 Logo 区并不包含数据编码的部分,因此小程序码拥有更高的容错性

小程序码分析

版本与数据分布

目前小程序码一共支持 3 种容量,分别是 36 射线、54 射线和 72 射线。

36线、54线、72线小程序码数据点用途。黑色是固定图案,黄色可绘制广告、头像等。棕色不存储数据(固定填充为黑、白或跟随临近点颜色);绿色存储元数据,表示该码的版本、纠错等级、掩码图案等;剩余的灰色点存储有效数据。

对于三个不同的版本,元数据(绿色)区域的位置是一致的,这是因为在解码元数据之前不一定能知道小程序码的版本,需要在不知道小程序码版本的情况下识别元数据。

通过目测可以发现,上述三个版本数据区域的总数据点数分别为304、416、528个点(都是16的倍数),分别对应304、416、528个比特。

数据点与数据比特映射关系

根据一些测试,输入比特与图案中黑白点的映射关系如下。

以72线为例,首先将朝向左侧(即9点钟方向)的线记为0号线,顺时针依次是1, 2, ..., 71号线,于是18号线位于12点方向,36号线位于3点方向,以此类推。

然后,比特映射的顺序是,将第1个比特映射至0号线由内往外的第1个有效数据点,第k个比特映射至0号线由内往外的第k个有效数据点。0号线映射完毕后继续按照由内往外的方式映射1, 2, ..., 71号线直至映射完毕。

对于36线和54线,数据映射关系也可以参照上述流程,对于不存在的线跳过,继续映射下一线即可。

img

举个例子,对于文献[1]开头的那张小程序码,目测它的版本是36线。通过上一节的数据点分布及这节所述流程,可以读到它的元数据为:

01110111001……

它的有效承载数据为(304比特):

100011010111……

掩码图案以及去掩码图案

根据文献[1],小程序生成的最后会将数据点与32个掩码图案之一进行异或。在解码时,可以通过元数据知道该小程序生成时具体用了32个掩码图案中的哪个。

具体掩码图案的获得方式此处先略去,假设我们已经知道了该小程序具体使用了哪个掩码图案。于是,上述数据经过去掩码(实际的流程是先去掩码,再依次读取数据点)后将得到去掩码以后的数据比特:

0010000011001110011……

纠错编码以及数据编码

经过前几步去掩码、数据比特读取,文献[2]开头的那张二维码已近被转化成一串304位二进制比特。之后的纠错编码与数据编码经过实验发现与QR码完全一致。具体如下。

通过元数据,我们可以知道(具体如何知道的晚些时候再说)上述信息中纠错码的有效数据长度为160比特 = 20字节,纠错码校验位的长度为144比特 = 18字节。

通过对照QR码的校验,巧合的发现,如果使用QR码的纠错码对前20个字节生成校验位,恰好就是后18个字节。当然,这是因为做实验用的小程序码完全没有经过污损,每个比特都是对的。于是,从这个结果可以知道,小程序码的纠错用的是和QR码完全一致的编码,即GF256上的RS码,具体编码及解码方式网上有较多公开文档,此处略去。

将纠错码产出的校验位舍去后可以得到如下有效信息比特载荷:

001000001100111……

我再一次掏出了QR码的编码标准,尝试解码上述有效信息比特载荷,发现数据又是合法的。于是此处又可以省略一大段说明,得到解码后的数据如下。顺便补充说明一下,QR码在编码的过程中会在有效荷载之后依次填充0xEC和0x11两个字节,直至填充满纠错码的数据位长度,小程序码的填充方式也完全一致。

 L+SKD……

通过一些观测,这并不是小程序码承载的真实信息,疑似只是小程序码在生成过程中为了将数据喂给QR码的数据编码而产出的数据。

经过大量测试,我发现上述数据产出过程经过了Base82 -> Base256 -> Base45两次转换。于是,解码过程即首先将上述字符串重新转化成QR码Alphanumeric模式的Base45数组,然后通过一个非标准进制转换将其转换成Base256的数组,再转换成Base82的数组。

需要说明一下,为什么此处想到是Base82,这是因为在这篇 小程序码生成文档 中,scene的参数选取可以是数字、大小写字母及文档中给出的20个字符,加起来就是82个字符。

经过上述转换后,最终就得到了文献[2]开头的小程序码中存储的信息:

k1;/~z……

看起来似乎是加密的信息。

小程序码的生成与识别

定位点

首先确定 3 个定位点和右下角的官方 logo 区,经过第一步小程序码的大小也随着确定。

信息编码区

编码的过程主要是把原始信息(例如某个小程序的首页)转化成计算机能识别的语言——二进制序列(例如 0110…110)的过程。

听起来是不是有点抽象?你可以这么理解,六个月大的婴儿吃不了大米,但是我们可以把大米砸碎研磨变成米浆米糊,这样他就可以食用消化的,原理是差不多的,大而化小 :)

img

编码完的下一步是加纠错码。

这个过程有点复杂,这里我也尝试用大家能听懂的语言给大家解释一下。

假设桌子上先是放了 100 个生鸡蛋(代表上一步已经转换好的二进制序列),然后再加入120个熟鸡蛋(代表纠错码,具体个数就视纠错率而定了,这里只是一种假设)。表面上看起来生鸡蛋和熟鸡蛋并没有太大的区别,但是其实还是有办法能辨别出来的。例如,生鸡蛋由于蛋黄悬浮到鸡蛋中间,重心不稳,无法旋转,而熟鸡蛋是可以旋转的。

经过纠错码这个步骤,数据量变大了(从生熟鸡蛋的例子来看,桌子上的鸡蛋由 100 个变为 220 个),而回到我们上一个步骤,相当于把二进制序列 0110…110** 进行了扩展(假设原来 0 和 1 加起来一共有 170 位,经过纠错编码之后就变成了一共 400 位的 1010…101)。

这里需要补充说明的一点是,加纠错码这个阶段不只是让数据量简单地增大。

一旦小程序码的版本、纠错级别确定了,其对应的纠错码都是固定的了,这样解码阶段才能通过对应的规则去消除纠错码,把真正有用的数据保留下来(回到生熟鸡蛋的例子就是只留下生鸡蛋,而把熟鸡蛋排除掉)。

经过信息转换和纠错编码之后,我们得到一串最终的二维码序列,就可以把信息按一定的编码顺序填充到小程序码的编码区域(1对应的是黑色,0对应的是白色)。

填充之后我们发现小程序上花瓣看起来很不均匀,比如下图:

所以为了让小程序码的花瓣看起来更加均衡,需要再多做一步操作。

掩码操作

填充好编码区之后,我们发现图案与设计稿大相径庭,并没有发射状线条的感觉。究其原因,是因为黑色点过于稀疏。所以我们还要做 mask,加上掩码图案。

mask 的原理其实就是拿一个掩码图案与原图做 XOR 操作,在解码阶段,再做一次 XOR 操作,两次 XOR 操作,我们得到了原始的数据区。

将小程序码跟 32 种掩膜(又称「mask」,可依照一定的规则生成)进行异或运算,最终选取效果最佳的作为最终的小程序码。

功能性数据

元信息区

前面我们提到,小程序码分为多个版本,每个版本有 4 个纠错 Level,同时 mask 阶段又有一个独特的 mask id。这些信息,我们称之为元信息,需要独立编码到图案中,并且本身具备纠错能力。

至此,我们已经把所有必不可少的信息写入到图案中,码本身已经是可识别的了。为了让整体更加美观,需要对内外圈再进行一些处理。

轮廓填充区

为了凸显 logo 的形状,我们在内圈留了一些区域作为轮廓填充区。

边缘补全区

最外圈也不带有编码信息,用于勾勒图案的轮廓,总体上我们有以下两种方案

方案一更突出图案更加圆的特点,但方案二可以让线条显得更加错落有致,也是我们的最终选择。

最终效果如下:

至此,小程序码就完成了它的绽放过程 :)

识别

小程序码识别过程跟小程序码的生成过程是反过来的,大家可以通过简单的流程图来感受一下。

各类新型二维码编码方式对比

名称/别名码眼码点分布版本掩码纠错码编码内容
微信小程序码/菊花码3同心圆+圆形logo射线状36/54/72线32RS(GF256 0x11d)加密信息+偶有少量明文
抖音码3同心圆+圆形logo完整环形4/5/6环、3/5度、新旧式8RS(GF256 0x11d)完整URL
QQ小程序码3同心圆+圆形logo方形环绕唯一版本8RS(GF256 0x11d)识别码
淘宝码同低版本QR码QR码四角唯一版本1RS(GF64 0x43)URL后缀
鸿蒙码/鸿蒙多功能码3同心圆+小同心圆双半圆环绕4/5/8/10/11/13环8RS(GF256 0x11d)协议+识别码
支付宝码3同心圆+圆形logo完整环形唯一版本7RS(GF256 0x11d)识别码

参考资料

[1] QR码 - 维基百科,自由的百科全书

[2] 微信小程序码私有编码协议分析(继续中) - 知乎

[3] 小程序码设计篇-菊花绽放-腾讯云开发者社区-腾讯云

[4] 你一定不知道,小程序码是这样绽放的 - 腾讯CDC

[5] 各类新型二维码编码方式对比(待续) - 知乎