先把三層帳擺出來
只有一張 JPG,交付端要 PNG。轉完之後三件事同時發生:27760000 個像素一位沒變,體積漲到 15.881 倍,JPEG 留下的區塊效應與色度重複被原樣鎖進無損容器。全部數字來自實測:探針頁原樣注入 image-convert.html 的 8908 位元組 inline script,由無頭 Chrome 直接呼叫 addFiles() 與 convertAll(),取真實匯出函式的產物。
恆等:三路互比逐通道零差異
來源 JPEG 解碼像素、模板畫布像素、產物 PNG 解回像素三路互比,pixelsDiffering、maxChannelDiff 與逐通道 rDiff/gDiff/bDiff/aDiff 全是 0:7 張合計 3760000 個像素,再加 6000x4000 那張的 24000000 個像素。8 張產物的 IHDR 一律 bitDepth 8、colorType 6、interlace 0,8 對 8 無二次量化。魔數 89504e470d0a1a0a、blob.type 與下載名三處都認定 PNG,後綴偽裝成 .webp 的 39587 位元組 JPEG 照樣吐出 504277 位元組的 .png。
代價:漲幅只用每像素位元組就算得清
| 樣張 | 來源位元組 | 產物位元組 | 倍數 |
|---|---|---|---|
| exif6_400x300 | 4802 | 7515 | 1.565 |
| jp_flat 純色 | 1698 | 4785 | 2.818 |
| jp_detail 高細節 | 107934 | 513021 | 4.753 |
| photo_400 | 9712 | 116541 | 12.0 |
| fake_webp_is_jpeg | 39587 | 504277 | 12.738 |
| jp_grad 漸層 | 7185 | 114102 | 15.881 |
同一 400x400 畫幅下純色漲 2.818 倍、漸層漲 15.881 倍,差距全在熵上:來源 0.0449 位元組每像素對比產物 0.7131 位元組每像素,商正好是 15.881。容器封裝只是小項:簽名 8 加 IHDR 25 加 IEND 12 共 45 位元組,每片 IDAT 再收 12 位元組,45+360+116136=116541、45+348+113709=114102 都能逐項重算,漲的位元組全在熵編碼本體裡。
圖 1 柱高依位元組對數等比,暗柱是來源、亮柱是產物。
JPEG 的失真痕跡被無損固化
產物解回像素與來源 JPEG 解出像素的 8×8 塊統計,42 個欄位序列化後逐字元相等——相等本身就是壞消息:區塊效應一絲都沒被抹掉。photo_1600 的塊邊界梯度 hRatio 9.286;同一張漸層圖從畫布直接存 PNG 只有 0.136,先過一趟 JPEG 再轉就變成 1.319。
4:2:0 的色度二次取樣也原樣留著:相鄰像素 Cb 完全相同的比例從對照組 38.15% 漲到 72.93%,cbRatio 從 0.698 變成 4.606,cbByResidue 從約 28.7 的齊平變成 6.283 與 3.379 交替。有損那一步全在 JPEG 編碼裡:原始畫布對比 JPEG 解碼,高細節樣張 159994 個差異像素、最大單通道差 142、sumAbsDiff 12524610;同一畫布對比 PNG 產物是同一組數字;JPEG 解碼對比產物卻恆為 0。所以 PNG 這一跳是不再額外掉畫質,不是畫質恢復。
往返:只有低熵圖收斂
jpg→png→jpg 跑 7 組,回程位元組是源頭的 0.622、0.79、0.921、0.926、0.941 與兩個 1;反向的 PNG 轉 JPG 另算。中間那跳 PNG 一像素不動,只把 8948 位元組抬回 116078。照片每趟繼續掉:photo_400 第一趟 12185 個差異像素、sumAbs 45085,第二趟 13762 與 59824。低熵圖收斂:jp_grad 從第 2 跳起固定在 7185 位元組、70 個差異像素、sumAbs 93,第 3 跳 PNG 114131 位元組、差 29 位元組的抖動;jp_flat 回程與來源一模一樣是 1698。jp_detail 第 3 趟從 101576 到 99952 只降 1.6%,卻再多壞 412 個像素。
圖 2 7 組往返的回程位元組比值,虛線是比值為一的參照。
中繼資料無處容身,方向已經烘焙
exif6_400x300.jpg 的源段表裡有 34 位元組的 APP1-EXIF(orientation 為 6),7515 位元組的產物裡卻沒留下任何 eXIf chunk,tEXt/iTXt/zTXt/iCCP/bKGD/pHYs/sRGB/gAMA/cHRM 全為 0、metaBytes 0;jp_ 樣張源裡的 APP2-ICC 472 位元組同樣沒進。方向沒被轉壞:解碼寬高與產物 IHDR 都是 300x400,瀏覽器已把 orientation 烘焙進像素,丟的只是那 34 位元組的記錄。
模板不設上限,失敗分支一次沒觸發
與 convertOne 同一個 toBlob 呼叫,在 16384x16384(編碼輸入 1073741824 位元組)上吐出 5012121 位元組 / 2320 毫秒,12000x12000 吐出 2707968 位元組 / 1203 毫秒;第 244 行的轉換失敗分支實測 0 次觸發,真正會拒絕的分支只有第 167 行的非 image/ 前綴與第 178 行的讀不出圖,15 筆輸入實測 11 成功 4 被拒。第 143 行讓 PNG 方向的品質那一列 display 為 none。
批次 10 張:匯總條與位元組級冪等
10 張一批 loadMs 21、convertMs 162,匯總條寫著「已轉換 10 / 10 張 · 合計 281.2 KB → 1.94 MB(體積增加 606%)」。再跑一次 convertAll(),產物的 sha 前綴仍是 d72921d5dd519312 與 1f85ef0821cd4944;7 張打包 zip 時 DEFLATE 與 STORE 都是 1584975 位元組、部件合計 1584197,壓縮增益 0 位元組——PNG 已經把熵壓到位了。入口在 JPG 轉 PNG。
常見疑問
Q:體積漲十幾倍,是不是轉換把圖搞壞了?
沒有。產物與 JPEG 解碼逐像素相同,漲的只是記帳方式:JPEG 拿塊邊界梯度換位元組,PNG 用無損熵編碼如實記錄每個像素,0.0449 位元組每像素變成 0.7131 位元組每像素。
Q:轉成 PNG 能把 JPEG 的區塊效應去掉嗎?
不能。42 個區塊統計欄位逐字元相等就是答案,區塊效應與 72.93% 的色度重複一塊被無損固化。
Q:有 alpha 通道是不是就有透明背景?
colorType 6 帶 alpha 槽位,但 alphaMin 與 alphaMax 同為 255,3760000 個像素裡 0 個非全不透明——JPG 不攜帶透明資訊,換了容器也長不出來。