把一個只有描邊的 logo 轉成 JPG 傳給合作方,對方回了一句「背景怎麼是白的」。來源圖那塊區域確實是透明的,問題出在轉 JPG 這一步:它不接受「留著透明」,而是挑一種底色把透明像素填滿。更彆扭的是同一張圖只換底色能差出幾百個位元組,品質滑桿拉到 90 還是 10,對 PNG 出口一個位元組都不影響。
白底不是編碼器加的,是畫布上先鋪了一層底色
起點不在壓縮,在畫布。範本第 230 到 232 行先用底色把整張畫布 fillRect 塗滿,再把原圖 drawImage 壓上去,alpha=0 的點沒有機會留空,落成的就是底色本身。取樣圖裡這些點記的 RGB 是 (255,0,0):填白底讀回 (255,255,255)、填 #ff0080 讀回 (255,0,128),24 個取樣點的輸出 alpha 一律是 255,逐位元組精確。
勾選方塊在切到「轉為 JPG」那一刻變成 checked=false 且 disabled=true,提示換成「JPG 不支援透明,透明區域自動填充下方底色」;點了那個變灰的方塊,讀回來仍是 checked=false,shouldFillBg() 恆為 true。切到「轉為 PNG」時底色選擇器又 disabled=true,三個出口裡它真正能動的只有 WebP 且不保留透明那一種。
只換底色就出 4 個體積,轉 JPG 也未必變小
填色同時改體積。那張條帶圖的來源是 2576 B,在 圖片格式轉換 裡把品質停在 90 不動、只換底色走 JPG 出口,拿到的是 4 個互不相同的位元組數。
| 填補底色 | JPG 匯出體積 |
|---|---|
#ffffff | 5023 B |
#7f7f7f | 4840 B |
#000000 | 4769 B |
#ff0080 | 4196 B |
方向也是反的。品質 90 下 6 張真實 PNG 走 JPG,5 張變大——2083 B 到 3160 B、5445 B 到 8586 B;唯一變小的那張從 899899 B 到 216635 B,也只到 4.15 倍。16 張一批的匯總條自己寫著「合計 32.5 KB → 49.4 KB(體積增加 52%)」。
圖 1:三張 PNG 的來源體積與品質 90 下的 JPG 轉出體積,橫條按 8586 B 等比縮放。
91 個檔位在 PNG 面前一個都不參與編碼
滑桿是 min=10、max=100,讀回來是 step="1",可取的整數檔位從 10 到 100 共 91 個,把 value 改成 5 會被瀏覽器夾回 10。選中「轉為 PNG」時品質那一行 display=none,第 235 行把傳給 toBlob 的 quality 換成 undefined——純色圖 10 / 55 / 90 / 100 四檔全是 10606 B、照片四檔全是 26190 B。
圖 2:照片那張(來源 5445 B)從 10 到 100 全枚舉的 JPG 位元組曲線,虛線是 PNG 出口恆定值。
JPG 那側才有曲線:91 檔全枚舉得到 91 個互不相同的位元組數,10 檔 2040 B、90 檔 8586 B、100 檔 67341 B,單檔最大跳變落在 99 到 100 之間,多出 38192 B。純色圖上 10 到 60 檔全是 3159 B、70 到 90 檔全是 3160 B,91 個位置只出 3 個體積;WebP 也不單調,10 檔 1392 B 比 90 檔 1294 B 大,100 檔的 520 B 反而最小。
問它要 HEIC,它默默還你一張 PNG
目標格式只有 3 個按鈕,data-mime 是 image/png、image/jpeg、image/webp,第 65 行的副檔名對應也只有 png、jpg、webp 三項。越過介面把 targetMime 設成對應外的值,canvas.toBlob 不報錯也不提示:問 image/heic、image/avif、image/tiff 時全部靜默回退成 image/png,同一張純色圖都是 10606 B、檔案頭 89 50 4e 47,只有 image/webp 真出 1294 B 的 RIFF。MIME 落在對應表外時匯出名直接是 solid_640.undefined,而介面第 9 行的支援清單也只寫 PNG / JPG / WebP / BMP / GIF / SVG,不含 HEIC。
入口只看宣告不看內容,唯一的判斷是第 167 行的 file.type.startsWith('image/'):宣告 image/heic、內容是那張 2083 B PNG 的照收,640×640 讀入;type 為空字串的合法 PNG 反被跳掉,只留一條 toast「已跳過非圖片檔案:no_type」。範本裡 MAX、maxSize、limit 與對 blob.size 的比較全部 0 命中,沒有體積上限也沒有張數上限。
兩張圖撞成同一個名字,包裡就只剩一張
匯出名只替換最後一個點之後的部分:archive.tar.gz 變成 archive.tar.jpg,連尖括號和引號都原樣留著——<b>&"x".png 轉出後是 <b>&"x".jpg。疊上那 3 項副檔名,同批裡的 same.png 和 same.jpg 都匯出成 same.jpg,於是 zip.file() 被呼叫 2 次而最終條目只有 1 個:後寫入的 8948 B 覆蓋先寫入的 3160 B,整包 9062 B,兩張成員圖合計該是 12108 B,被吃掉的那張沒有提示。只有 1 張時不打 ZIP,第 274 行 typeof JSZip 取不到相依時也退化成只下載第 1 張。
尺寸聽解碼器的:9 張輸入的轉出寬高與 naturalWidth、naturalHeight 逐個相同,程式碼裡沒有縮放路徑,真要改尺寸得走 圖片尺寸調整;而 exif6_400x300.jpg 的 SOF 寫 400×300、瀏覽器解成 300×400,三個出口就全是 300×400。
三個還會被再問一遍的點
Q:帶透明的 logo 該走哪個出口?
只有 PNG 和保留透明的 WebP 能讓背景不存在,JPG 那側一定有底色落進透明區。非 JPG 不可就把底色改成投放位置的背景色,比預設白底更難看出接縫。
Q:選 PNG 是不是等於什麼都沒動?
編碼那一層不看滑桿也不填色,來源 alpha 逐檔原樣寫出,但這不等於原樣:全透明那檔的 RGB 在畫布上已被解碼器清成 (0,0,0),出口一律寫成 RGBA,來源圖沒有 alpha 通道的也補一層——純色圖從 2083 B 漲到 10606 B 主要是這層容器開銷。
Q:批次轉 JPG 之前該防什麼?
先把只差副檔名的同名檔案錯開,撞名之後包裡只留後寫入的那一張;轉完看一眼匯總條,它寫「體積增加」的時候就是真的增加了。