同一个字没改,格子从 25 个变成 33 个
同一条 23 字节的内容,纠错等级从 L 拨到 H,统计那行从「尺寸:25x25」变成「尺寸:33x33」,导出的画布却从 297×297 缩到 287×287。一个字没改,格子变密、画布反而变小,这两件事各从哪来?
这一页的编码器是自己写的:二维码生成器 模板第 158–465 行是一段从 GF(256) 表到矩阵生成的码流,不引用外部库。
一条码流:模式指示器恒 0100
第 284 行是全文件唯一的模式指示器写入 bits.push(0, 1, 0, 0),不在任何条件分支里,于是模式恒 0100、每字节恒 8 bit;纯数字、字母数字、汉字、emoji 实测都只切成 1 段,numeric、alphanumeric、KANJI 三个关键字命中 0 次。长度字段 8 bit(v10 取 16 bit),头部合计 12 或 20 bit。汉字没有单独的段:「中文测试」4 字符 12 字节、一个字符 3 字节,段账与 12 个 ASCII 逐值相同(dataBits 96、rawBits 108),没有 13 bit/字的码流。
图 1 同一条内容在四个等级下的段账
容量阶梯与那个不够用的 +2
版本选择只有一句判据:第 236–238 行拿容量表比对 byteLen + 2,1 到 10 逐个试,全不中返回 -1。每档右端点恰好是表值减 2:可用上限 L 269 / M 211 / Q 149 / H 117 字节,270 与 118 起就返回 null。预留的 2 字节按 12 bit 头算,v10 是 20 bit:117 字节在 H 级选中 v10(表值 119),实测 dataBytes 120 大于 cap 119、padBytes −1。
模块数是死公式 size = 17 + 4 × version,80 个「等级 × 输入」格子无一例外:尺寸变的是版本,不是内容;v10 的容量从 271 降到 119,H 的实际可用只有 L 的 43.5%。
纠错码字:个数对得上,字节对不上
ec 值直接进 rsEncode 的 eccLen,12 组实测输出长度全命中;数据码字与纠错码字永远拼成 1 个块(第 317–318 行 concat,interleave 命中 0 次),v10 总码字 344。
长度对,内容不对:与标准生成多项式(既约多项式 0x11d)对照,eccLen 224 时 224/224 全不同;data 拼 ecc 求伴随式,L 级 v1 是 7/7、H 级 v1 是 17/17、L 级 v10 是 72/72,全非零。反解出的生成多项式除 n 等于 1 外,每个恰好错 n − 1 个系数。
固定掩码把时序行抹成一片白
实现里只有一条掩码:第 422 行 function mask(r, c) { return (r + c) % 2 === 0; },maskPattern、penalty、score 命中都是 0 次,不存在 8 种掩码打分选优的分支。
图 2 时序第 6 行与 5×5 对齐块改动后的黑格数
代价落在功能图案上:第 426–428 行的 isFunction 只排除三个定位角邻域,而时序值本就是 (i % 2 === 0) ? 1 : 0,与这枚棋盘同相,异或后恒 0:v10 的 40 个时序位黑格 0/40。5×5 对齐块应是环加中心 17 个黑格,实测只剩 12 个,25 个模块里 13 个与标准图案不符。
格式信息:值算对,位置写错
第 437–445 行 eccLevelBits 是 {L:1, M:0, Q:3, H:2},BCH 生成式 0x537、异或 0x5412,H 级得 0x1689,第 438 行掩码位硬编码 0。30 条写入只落在 21 个模块上、9 个被写两次,标准左下副本该占的 7 格从不出现在写入目标里。
文件名里的 33 是模块数
第 778 行 qrSize = result.size 是模块数,滑杆 128–512 才是像素期望值;静默区开时 totalSize 是模块数加 8,pixelSize = Math.floor(displaySize / totalSize)。33 模块这一档,13 档滑杆只产出 11 种像素,公式复算 13 组全等、偏差只小不大,最大 38 px。
| 滑杆标称 | 下载文件名 | IHDR 实际 | 字节 |
|---|---|---|---|
| 128 px | qrcode_33x33.png | 123×123 | 2059 |
| 320 px | qrcode_33x33.png | 287×287 | 4478 |
| 512 px | qrcode_33x33.png | 492×492 | 8547 |
第 800 行的文件名也取 qrSize,三档同名 qrcode_33x33.png;SVG 侧名同 33x33、根元素 width="410"。开头那对反差就是这么来的:模块越少、totalSize 越小,floor 之后画布反而更大。
报错态与导出字节
超容量时页面不清状态:第 770–776 行只改显示、不置空 qrMatrix,第 794 行 if (!qrMatrix) 照样放行——报错态下点下载仍拿到 4478 字节、cksum 511620 的上一张码;只有第 844 行的清空能把它置 null,此后下载给「请先生成二维码」。
导出字节都是 PNG 口径:第 796 行 canvas.toBlob 不带第二个 mime 参数,实测魔数 89504e470d0a1a0a。码点方块 4478、圆角 7558、圆点 9466;渐变打开 47542,是默认值的 10.6 倍。SVG 只认版本与配色,恒 32316 字节 / 532 个 rect;两遍运行 11 个可比场景的 bytes 与 cksum 配对逐值相同。
四件事与几段追问
模式指示器恒 0100、阶梯右端点恒等于表值减 2、纠错码字只保证个数、掩码是一枚固定棋盘,这就是这一页的编码器走到的位置。
Q:为什么等级越高,码就越大?
同一版本的可用字节被高等级吃掉:23 字节在 L/M 还是 v2,到 Q 要 v3、到 H 要 v4,模块数按 size = 17 + 4 × version 涨上去。
Q:这些实测能说明码能不能被扫出来吗?
不能,本篇没跑任何解码器;「H (30%)」只是 option 文本、Logo 滑杆上限是 35,都不在本篇结论里。解码侧与每模块像素的生死线,另一篇二维码解码实测记的是另一面。
Q:报错以后为什么还能下载出一张码?
失败分支只改显示、不清状态,导出用的是上一次成功的矩阵;唯一能把矩阵清零的入口是清空。