二维码生成器——当着你的面,把符号一格一格搭出来

模式按比特价码挑,版本按算术定,里德-所罗门校验字在 GF(256) 里算出来,八个掩码全部打分——然后成品符号再从它自己的模块里被读回来,用来核对刚刚做出来的那个符号。你打进去的文字不会离开这台设备。

模式、版本与掩码怎么定

七步,每一步都是算术

七步,每一步都是算术而不是查表:你的文字变成 UTF-8 字节;能承载它的几种模式各按比特标一遍价,取用得上的最便宜那一种;算出数据比特够付这笔价的最小版本;把比特切成分块;每一块在 GF(256) 上除出一个里德-所罗门余数;分块交织,沿着跳过三个角上图形和两条时序列的那条锯齿路径写下去;最后八种掩码全部翻到成品上打分,分最低的那个掩码当选。

格式区和版本区是最后放下的两样,它们出自同一次简短的多项式除法:纠错等级加掩码共十五个比特,version 7 往上再写十八个比特的版本号。这里没有用任何库,也没有上传任何东西——把这一切做完的,就是你正在读这句话的这台机器。

图片下面那几张表不是参考资料。比特账本、八个掩码的分数和那一次读回,全部来自你刚刚跑的这一趟,这就是为什么改一下等级它们跟着变,也为什么在第一个符号存在之前它们是空的。

一个纠错等级到底买到了什么

一个等级就是把码字在你的数据和修复字之间分一刀。version 1 的符号不管怎么分都装 26 个码字:L 级下 19 个承载你的文字、7 个是校验字,H 级下 9 个承载你的文字、17 个是校验字。差别就这么多,也正是为什么同一句话在 H 级要占一个比在 L 级更大的版本:每块的数据字更少,就意味着更多的块。

你常看到被引用出来的那组数字——L 约 7%、M 约 15%、Q 约 25%、H 约 30%——估的是一台读码器还能扛过多大面积的破损地面,不是上面那个校验字比例,更不是把 logo 糊在码的三分之一面积上的许可证。三个理由。那个比例是整个符号上的平均值,它说不出破损落在哪里。损伤是成簇来的——一道擦痕、一圈咖啡渍、一张贴纸——而解码器对一连串被毁掉的字的重建,远比对同样数量、散落在整个网格各处的字要糟。还有,那些完全不承载你文字的部分根本没有被铺开:三个定位图形、两条时序列和格式区都是定死、点了名的地方,而丢了其中一个的符号,在任何一个等级上都读不出来。

你为修复能力付出的是容量和尺寸。版本和等级会一起打出来,再带上每边模块数和每模块像素数,因为 H 级的实际代价就是更密的网格,落到纸上就是更小的模块——而大约 0.4 mm 以下的模块,手机摄像头在胳膊长的距离上分辨不出来,纠错再好也一样。

为什么模式是按价码选的,不是按长相选的

三种模式每个字符花出去的钱不一样,而且差距很大。数字模式每三个数字花 10 个比特,约等于每字符 3.3 比特;字母数字模式从它那个四十五个字符的集合里每两个字符花 11 个比特,也就是每个 5.5;字节模式每个 UTF-8 字节固定 8 个比特。在数据本身之外,每种模式还要付一个头:四比特的模式标识,加上一个宽度取决于版本的字符计数——符号跨过 version 9 时它跳一档,跨过 version 26 时再跳一档。

所以 auto 会把所有能承载你文字的模式各算一遍价,再取落进最小版本的那一个;而头恰恰是为什么答案不一定是每字符最便宜的那种:一个计数字段更宽的模式,整体可能比一个载荷更长、字段更窄的模式更贵。这也解释了一个小写的 URL 为什么会成为一个字节模式的符号,而不是字母数字的——字母数字集合是大写字母、数字和空格,里面没有小写。强制指定模式是允许的,而办不到的事宁可拒绝也不肯帮忙:文字里一旦有所选模式表示不了的字符,那个字符连同它的位置会被念出来,这一趟就停下。因为悄悄换成另一种模式重编、或者把大小写折叠一下好让那个按钮按得下去,两种做法交回来的都是一个读出来跟你打进去的不一致的符号。

有两样缺席的东西值得点名。这里没有汉字模式,所以中文走字节模式、每字节 8 个比特,一个汉字约 24 个比特——这就是为什么短短一句中文就能自己占满一个版本。而整段文字只写一个模式标识;会分段的编码器会把混合内容拆成一条链——比如一段字母数字外加两位数字——并从里面拿到一个更小的符号。分段是真本事,也是真收益;可它等于替你的文字下了一个判断,而这里不愿意代你做这个判断,所以一段混合文字要比一个会分段的编码器多付一到两个版本。

把成品符号再解码一次

每一个模块都放下之后,成品网格会被同一个文件里的第二条路径解码一次:格式区的两份拷贝都提出来、互相核对;版本大到装得下版本位时,把版本位从那里读出来;掩码编号和等级取自除到那些模块上的 BCH 值;然后是模式标识、字符计数、按编码器访问顺序排好的数据字,以及照着网格现在说每个块装的是什么而重新算出来的里德-所罗门余数。

要把这证明了什么说清楚。这是同一个页面对规范的理解被用了两遍,所以一个共享的误读会完美地原样往返——真正能证明这是不是一张二维码的,是你没有的那台扫码器。往返能抓住的,是放置编码器这一类程序真会写出来的那一家子 bug:在头和载荷之间对不齐的比特游标、跟写进去时不匹配的交织顺序、翻到功能模块上的掩码、分块时的一格之差。即使两半住在同一个文件里、在其他每一件事上都达成一致,这些东西也会让读回失败。

如果你的浏览器自带一个条码读码器,它会被问一遍,而那份回答出自一批从没见过这个文件的人之手,是真正第二方的意见。如果它没有——这确实是相当数量的浏览器,而且是它们故意的,不是巧合——那一行就直说,而不是保持沉默,因为一张缺了最要紧那一列的表,看起来跟一张全通过的表一模一样。

边界,摊开来讲

这世上最大的符号,在每边 177 个模块的网格里装 3706 个码字,L 级下那是 2,953 字节的数据,所以文字被停在 4,000 个字符:一旦超出最大的符号,更长的输入就是一个文件,而一张你拍不到的码也没什么用。画面停在每边 4,096 像素,那是多数浏览器对画布设的上限,不是这里自己定的;符号在两种尺寸下是同一个,所以能改的是模块尺寸或者静默区。

掩码的赢家是一条关于相机怎么应付的启发式,不是正确性规则。四项惩罚加起来算——同一颜色的长连串、它的实心 2×2 块、形状像定位图形的连串,以及整体太深的地面——一个用掩码 6 扫得出来的符号,用掩码 3 也照样扫得出来,只是余量差一些。两个掩码同分时取编号小的那个,这是那些算例用的破平法,也是为什么八个分数全都摆着而不是只给一个数字:你能看出这场竞争有多近。那条关于定位形状的规则是在模块网格自身的每一行、每一列上量的,没有把静默区接在后面,这是常见的约定而不是唯一的约定;一个把空白地面也算进线里的读码器,会给出略微不同的分数。

没有实现、也没打算藏起来的东西:ECI 标签(它告诉读码器这些字节属于哪个字符集,所以写下去的 UTF-8 是一个得由扫码器去猜的约定)、FNC1 与 GS1 的符号变体、结构化追加(把长数据分进好几张码)、以及 micro QR(一种根本没有定位图形的另一种几何)。需要其中任何一样东西的,在这里拿到的会是字节模式或者一次拒绝,而不是一个谎报自己格式的符号。静默区默认四个模块,因为规范要的是四个,不是因为四个印在海报上看着舒服;而反色那个开关是一个画法上的决定,你的扫码器并不欠它接受的义务。

微信
微信
支付宝
支付宝