块数相同、画面相同,导出差 4 倍
标题那个 43890 的输入:Node 合成的 1080×2400 PNG(只印虚构单号 DEMO-A)、clientWidth=764、innerHeight=800,预览画布 dw×dh=252×560、scale=0.233333,框 4 个选区批量跑。滑块拖到 4 与拖到 8,块数都是 43890,采样点 (20,30) 恒为 [147,123,161];导出侧是 201613 块与 50468 块。
被测页图片马赛克 488 行 / 20006 字节。两套块宽公式:预览 Math.max(2, mosaicSize * scale)(L224,小数、按预览像素),导出 Math.max(2, mosaicSize)(L449,整数、按原图像素),中间夹 applyMosaic 入口 L261–262 的四处 Math.floor。
钳制只认一条判据
scale < 2/mosaicSize 就钳:本页 scale=0.233333,滑块 4 需 <0.5、6 需 <0.333333、8 需 <0.25 全落进去,16 需 <0.125 就够不到。两个分母别混——8 图 × 13 档 = 104 组里 21 组被钳,8 图 × 15 档 = 120 组里 26 组(21.7%)。maxH 在 innerHeight ≥ 760 恒为 560,于是原图高 > 1120px 配滑块 4 必钳。
| 图 | 被钳档 | 预览等效 M |
|---|---|---|
| 1080×2400 | 4…8(5 档) | 8.571px |
| 1170×2532 | 4…9(6 档) | 9.043px |
| 4032×3024 | 4…10(7 档) | 10.800px |
| 1080×4000 | 4…14(11 档) | 14.286px |
| 1080×1080 | 0 档 | M 恒等于 N |
四枚预设里只有「细致」6 会被吃掉,导出侧仍按 6px。窄屏 clientWidth=298 上 4032×3024 的 scale 掉到 0.072917,「细致」与「中等」同时钳成同一个 bs=2.0000、M 都是 27.429。
图 1:同一档位上灰柱矮于描边柱。
三档会崩,被钳的反而不崩
真模板壳页 HeadlessChrome/156,图 1080×2400、dw=252、region (10,20,110,24):mosaicSize=10、12、20 三档抛 IndexSizeError(source width is 0,N=20 报的是 source height is 0),其余档位正常。根因是 getImageData 四参数按 WebIDL long 向零截断,bw 的小数尾巴 0.33333333333341386、0.8000000000000824、0.6666666666666714 分别有 12、9、1 处截成 0。
换一条选区就可能不崩:(60,90,140,210) 在 N=10、12 上截断处数都是 0;4/6/8 更因 bs 钉成整数 2 而完全不崩。模板 try/catch 计数 0,且 L165 先写文案、L166 才重绘:字样已经变成 20px、画面停在上一档网格。
四处 floor 与残列
(10,20,110,24) 换算到原图是 42.857,85.714,471.429,102.857,floor 成 42,85,471,102,右边界净误差 −1.286px、下边界 −1.571px;同图 (60,90,140,210) → 257,385,600,900、(100,20,60,40) → 428,85,257,171。
网格从选区左上角起算,末列常不满:bs=16 时这条选区是 7 列 2 行 14 块、残列宽 14;固定 bs=16 扫选区宽 1…200,非零残列平均 6.851px。
6px 判在预览像素上
tr.w >= 6 && tr.h >= 6(L365)比的是预览像素,原图最小可框高度 = 6/scale:1080×1080 为 11.571px、1080×2400 为 25.714px、4032×3024 为 32.4px、1080×4000 为 42.857px。
按单行文字建模:1080×2400 上原图行高 18px 预览只有 4.2、24px 只有 5.6,两档静默丢弃不给提示,30px → 7 才收得下;4032×3024 的 30px → 5.556 仍丢,要 40px → 7.407。
alpha 与 RGB 同权平均
四通道同权相加(L281–285),L286 把 a/255 拼进 fillStyle,source-over 叠在已画好原图的画布上(L222、L443):a=128 残留 0.498039、a=64 残留 0.749020、a=0 残留 1.000000。128 在串里写成 0.5019607843137255,逐通道 Math.round 最大偏差 0.5,单通道 0.196%。
「导出原图」是一次重编码
0 个选区点下载走「导出原图」分支,产物解码回来与原图逐通道比对,4665600 个值 diffChannels=0、maxChannelDelta=0,字节却从 913850 变 163971、只剩 17.94%——像素相同、字节不同。
toBlob(mime, 0.92) 实测:1080×2400 加 1 个选区 png 70882 / jpeg 320379 / webp 29190;1080×1080 带 alpha 图 png 167624 / jpeg 300531 / webp 245724。「JPG(体积更小)」在这两张噪声图上都是反的(4.520 倍、1.793 倍),jpeg 还先在 L442 铺 #ffffff 白底、把 alpha 抹成 255。
算术层与浏览器层分开标
块网格取整、残列宽、floor 偏移、clamp 临界、alpha 合成出自纯 Node 算术模型,已与 Chrome 156 逐档 15/15 一致;三档 IndexSizeError 与抗锯齿(3.5px 宽块边缘 alpha=64/32 级)是浏览器实测。未实测:真实照片字节分布、drawImage 重采样视觉差异、toBlob 返回 null、下载耗时。要遮哪些字段见站内售后复盘。
常见问题
Q:滑块动了画面为什么不动?
落进 scale < 2/mosaicSize 时预览块宽被钉成 2,标称 4、6、8 跑同一套网格,导出才按标称走。
Q:拖了框却没打上码?
预览里不足 6px 的选区被 L365 当误触丢弃;拖到 w=190 h=280 时切走窗口,blur 与 touchcancel 只置 isDragging=false、不 push,regions 仍为 0 且没有提示。