66 組檔位實測,55 組省 0 位元組
一張 PNG 壓完只掉幾百位元組,就把產物再餵回去壓第二遍、第三遍,體積幾乎不動;更怪的是有幾張重編碼後其實比原圖大,介面那一行仍然寫著「節省」。我拿 11 張 PNG 跑 6 檔共 66 組輸入到輸出:55 組原位元組退還、省 0 位元組,真正換了編碼的只有 11 組,80 檔下變的只有 noise256,從 224476 B 到 224243 B,省 233 B 即 0.1%。
11 張一起壓完,統計條寫的是「11 張圖片 | 原始: 622.6 KB → 壓縮後: 622.4 KB | 節省: 233 B (0.0%)」:總和 637552 B 變 637319 B,0.0365 經 toFixed(1) 顯示成 0.0%。
IDAT 就是全部,元資料只是零頭
拆開區塊看:meta400 總共 1053 B,IHDR:25,gAMA:16,sRGB:13,pHYs:21,tEXt:39,tEXt:44,tEXt:30,IDAT:845,IEND:12,每塊固定 12 B 頭尾。IDAT 資料體 833 B,gAMA、sRGB、pHYs 加 3 個 tEXt 合計 208 B,全剝掉省 163 B 即 15.5%。11 張裡只有這 1 張帶 ancillary 區塊,其餘 10 張剝離收益 0 位元組。
沒有元資料的那些,IDAT 佔 99.97% 到 99.99%。能動的只剩 DEFLATE 那段碼流,而它還能壓多少早由內容定死:未壓縮掃描線除以實際位元組,solid800 是 1440600 / 2311 = 623.4:1,noise256 只有 262400 / 224476 = 1.17:1。噪聲每行都是新資料,後向引用翻不出可重用片段。
換一個 deflate 等級,多數樣本反而變大
純無損重編碼只改等級與每行過濾器,像素零改動,挑 4 張:
| 樣本 | 原圖 | 等級 1 + 過濾 0 | 等級 9 + 自適應 | 等級 9 + Paeth |
|---|---|---|---|---|
| grad256 | 681 | 181297 | 681 | 681 |
| solid800_loose | 2789 | 7815 | 2312 | 2311 |
| noise256 | 224476 | 224562 | 230212 | 224476 |
| big2560 | 399126 | 1595575 | 399126 | 405031 |
只有編碼寫鬆了的 solid800_loose 從 2789 拿到 2311,省 478 B 即 17.1%;其餘持平或變大,noise256 的 224476 變 230212,漲 5736 B。過濾策略選錯更誇張,grad256 在等級 1 加過濾 0 下是 181297 B,原圖 681 B 的 266 倍。
這條鏈路其實先做了一次 JPEG
那 55 組「省 0 位元組」不是編碼器偷懶,是中間有損了一趟:滑桿 80 除以 100 得 0.8,再按 adjustedQuality = quality * quality 平方,真正交給 JPEG 的是 0.6400000000000001,連 0.64 都不等於;100 檔則整條鏈短路、原始檔案直接交付。畫布第二次 toBlob 只傳 'image/png',品質參數從未出現。位元組我在PNG 壓縮這一頁逐檔取了一遍。
有損那步確實砍下了體積:alpha240 輸入 6429 B,中間 JPEG 只剩 2507 B。可 JPEG 規範不含 Alpha,透明區被整幅合成不透明,封回 PNG 膨脹到 47688 B,是輸入的 7.4 倍;不帶透明的 big2560 也從 359872 B 漲到 1905316 B,4.8 倍。兜底判準 pngBlob.size >= item.file.size 相等也退還,交付體積恆不大於輸入,介面只剩「無變化」:膨脹在中間那步,透明保住是回退的副作用,不是壓縮留下的。
圖 1|80 檔四段位元組,封回 PNG 那步才是膨脹現場。
壓三遍的兩種含義都收斂
同一個清單裡連點 3 次「全部壓縮」,11 張的 out1、out2、out3 逐位元組相同:清單項存的一直是那個原始 File,沒有任何一處改寫它。手動把產物餵回去才是第二層,此時只有 noise256 在動,224476、224243、224050、224050,第一遍 −233 B,第二遍 −193 B,第三遍 0,累計降 426 B 即 0.19%;同批 5 張小圖 115、127、681、2311、2789 三遍累計降幅 0.0%。
圖 2|鏈式三遍唯一在動的樣本,條長按差額放大。
能變小的槓桿是顏色表示
調色盤量化那組才有量級收益:solid800 量化到 1 色,2311 B 變 560 B,省 1751 B 即 75.8%,被改像素數 0、最大歐氏誤差 0,對這張圖它就是真無損;big2560 量化到 8 色,399126 B 變 84226 B,省 314900 B 即 78.9%,代價是 1165703 個像素被改、最大誤差 255。量化也會輸:alpha240 的 254 種 RGBA 誤差 0,索引圖卻要 22144 B,比原圖 6429 B 大;grad256 與 meta400 各有 65536 種顏色,砍到 256 色誤差 285 與 247。順序該是先數顏色與重複模式,再決定減色、縮尺寸還是換格式,取捨見壓縮格式怎麼選。
「節省」這兩個字有兩套算術
單條清單只有 3 個分支:省了寫減掉的位元組(-233 B,或 -17993 B 顯示成 -17.6 KB),相等與變大統一寫「無變化」,沒壓的寫「待壓縮」,沒有百分比。唯一的百分比在批次統計,分母是總和,位元組那一欄還取了絕對值:saved = totalOrigin - totalResult 為負時也顯示成正數,比例卻留著負號,於是沒有體積回退的分支壓完變大時,同一行能讀出「節省: 1.2 KB (-3.4%)」;100 檔那條直通短路見品質調到最右那次。
Q:統計條寫「節省 233 B (0.0%)」,是算錯了嗎?
沒算錯,是口徑太粗:分母是 11 張的總和 637552 B,233 B 只佔 0.0365%,toFixed(1) 後成 0.0%;清單裡 10 張「無變化」、1 張 -233 B。
Q:那 PNG 還能不能再壓小?
高熵又已緊編碼的只會變大,noise256 的 224476 B 換自適應過濾就成 230212 B;寫鬆了的 solid800_loose 才有 478 B 可省。
Q:結果變大了卻顯示「節省」,怎麼發現?
盯負號。PNG 這一路相等都退還,不會把更大的檔案交給你,代價是全顯示成「無變化」;沒有這層兜底才露出「節省: 1.2 KB (-3.4%)」。
不是工具不給力,是 IDAT 佔了 99.97% 以上、DEFLATE 還能找到的重複只剩 1.17:1,能動手的只剩顏色表示。三遍下來 426 B、0.19%,停在 224050 B 不動。