雜記 › 技術筆記 › 拖到 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