杂记 › 技术笔记 › 拖到 4 和拖到 8,屏幕上都是 43890 个块:马赛克预览与导出的两套块网格、三档会崩与 6px 误触阈值

拖到 4 和拖到 8,屏幕上都是 43890 个块:马赛克预览与导出的两套块网格、三档会崩与 6px 误触阈值

同一张 1080×2400 图上,滑块 4 与 8 的预览块数都是 43890、导出却是 201613 与 50468:两套块宽公式、三档 IndexSizeError、6px 阈值换算与「导出原图」重编码字节,逐笔量过。

块数相同、画面相同,导出差 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×24004…8(5 档)8.571px
1170×25324…9(6 档)9.043px
4032×30244…10(7 档)10.800px
1080×40004…14(11 档)14.286px
1080×10800 档M 恒等于 N

四枚预设里只有「细致」6 会被吃掉,导出侧仍按 6px。窄屏 clientWidth=298 上 4032×3024 的 scale 掉到 0.072917,「细致」与「中等」同时钳成同一个 bs=2.0000、M 都是 27.429。

两套块宽对照(1080×2400、scale=0.233333) 8.571 10 16 32 4 6 8 10 16 32 灰=预览等效 M,描边=导出 N。

图 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 且没有提示。

📎 相关文章

位图进 SVG 只换了个盒子:232 字节容器常数、一个从不生效的「base64 嵌入」勾选,与四档缩放下纹丝不动的 142064 个字符 2026-11-10 转完悄悄少了 271 个字符:Markdown 转 HTML 的洞协议、64 轮回填与 20 条提示上限 2026-11-10 解码是四步连做:1194 条语料在 unicode-convert 页上只有 798 条能原样回来 2026-11-09 保留集缩成 66 个字符:这一页三档编码在 128 个码位上的逐字符实测与 9 格编解码矩阵 2026-11-09 八个框的界面顺序和代码执行顺序是反的:文本清理页硬编码八步顺序、逐对换序真值表与统计码元口径实测 2026-11-09