編按:本文綜合整理自 Maurycy 的原始技術實驗、MDN 的漸進式媒體說明、美國國會圖書館 JPEG Progressive 格式說明 與 Hacker News 討論串,並加入 Siami 編輯部觀點與分析。
JPEG 明明是一種靜態圖片格式,波蘭開發者 Maurycy 卻讓一張 .jpg 在瀏覽器裡接連換畫面,甚至塞進約 90 格黑貓影像。這不是把 GIF 偷改副檔名,也沒有用 JavaScript 輪播,而是刻意利用漸進式 JPEG 原本「先顯示模糊預覽、再補上細節」的掃描結構,把後來的資料變成全新的畫面。
這篇名為「Regressive JPEGs」的實驗在 Hacker News 迅速累積 500 分與 47 則留言。它的實用性接近零,作者也坦言比較適合拿來做非常規 Rickroll 或技術惡作劇;但從影像格式、瀏覽器相容性與內容安全來看,這正是那種看似胡鬧、卻能把系統邊界照得很清楚的作品。
漸進式 JPEG 原本怎麼工作?
一般的 baseline JPEG 會從畫面上方一路解碼到下方,檔案只下載一部分時,常見結果是上半截清楚、下半截空白。Progressive JPEG 則把資料拆成多個 scan:第一輪先交出大致輪廓,後面幾輪再補充更高頻的細節與更精確的係數。
JPEG 壓縮的核心是離散餘弦轉換(DCT)。影像會被切成區塊,再把像素變成不同空間頻率的係數:低頻描述大面積的亮暗與輪廓,高頻描述邊緣、紋理與細節。Maurycy 拆解的一張代表性圖片共有 10 個 scan,第一個 scan 只帶三個色彩通道的最低 DC 頻率,之後才逐步補齊其餘資料。
- Y 通道:負責亮度,人眼對它最敏感,因此保留較多資訊。
- Cb、Cr 通道:負責藍色差與紅色差,可用較低解析度儲存。
- DC 係數:提供每個區塊最基本的平均色彩與亮度。
- AC 係數:補上區塊內更細的紋理與邊緣。
MDN 對 progressive image 的描述很直白:瀏覽器可以先渲染低品質版本,資料繼續抵達後再更新成完整品質。正常情況下,使用者只會看見同一張圖從模糊變清楚;Maurycy 的問題是,既然每個 scan 都能指定自己處理的頻率範圍,後來的 scan 能不能乾脆把已顯示的內容改掉?
Computerphile 的影片從 DCT 開始拆解 JPEG 壓縮,可搭配本文理解「低頻先到、高頻後補」為何能成立。
把「補細節」改造成「換畫面」
第一版做法非常直接:把多張同尺寸 JPEG 的壓縮資料串在一起,並移除重複的檔案開頭、frame 開頭與檔案結尾標記。當伺服器慢慢傳送這個檔案,瀏覽器就會在收到新 scan 時重算畫面,於是同一個 <img> 元素看起來像在切換不同照片。
問題也立刻出現。標準 progressive scan 不能在同一輪同時包含 DC 與 AC 資料;baseline decoder 又會讀完第一個 scan 就停。若每一格都保留完整低頻、高頻與多階精度,scan 數量很快超標,解碼器也可能為防止類似壓縮炸彈的資源消耗而停止處理。
作者最後採用更極端也更聰明的方案:每一格只留下 DC scan,不再傳送真正的高頻細節。因為 JPEG 的 DCT 區塊在這個設定下相當於 16×16 像素,每格只剩原圖約 1/16 線性解析度的粗糙輪廓,但對連續動作而言已足以讓人辨認。
生成單一影格不需要自製編碼器,jpegtran 就能用掃描腳本完成:
0,1,2:0-0,0,0;
這一行的意思是,只為 Y、Cb、Cr 三個通道產生 DC 頻率資料。接著再把大量影格的掃描資料合併,一個「靜態 JPEG 影片」便成形。作者實測 Chrome 約可處理 90 格才停止,Firefox 對更多 scan 顯得更有耐心。
數據解讀:90 格不是 JPEG 的官方上限
標題裡最吸睛的「90 格」需要加上重要限定:這是作者在特定 Chrome 版本、檔案結構與傳輸方式下觀察到的近似結果,不是 JPEG 標準承諾的動畫容量,也不是所有瀏覽器共同遵守的固定上限。
Hacker News 留言呈現出很不一致的實測:有人回報 Safari 會凍結到整張圖下載完,另有人在 WebKit 看到載入被取消,Firefox 則可能長時間維持未完成狀態。換句話說,這個效果同時取決於:
- 解碼器最多願意處理多少 scan。
- 瀏覽器是否把部分解碼結果立即畫到畫面上。
- 伺服器與網路如何分段送出資料。
- 快取、代理與圖片最佳化服務是否重寫原始 JPEG。
它也沒有真正影片格式必備的時間資訊。每一格何時出現,完全由下一段位元組何時抵達決定;網路快時可能一閃而過,網路抖動時則會忽快忽慢。留言者提出由伺服器固定間隔送 chunk、或以 Service Worker 模擬慢速連線,確實能近似控制速度,但那已把播放邏輯移到傳輸層,不是 JPEG 檔案本身學會了計時。
為什麼這件事重要?
這項實驗不會取代 GIF、WebP、AVIF 或 MP4,真正價值在於揭露「格式規格允許什麼」與「軟體通常假設什麼」之間的落差。開發者平常看到 .jpg,自然會假定它最後只對應一張穩定畫面;但在部分渲染期間,使用者實際看見的內容可能與最終解碼結果完全不同。
第一個影響是內容安全與審核。若上傳平台只擷取第一個預覽,惡意檔案可以把敏感內容藏在後續 scan;若掃描器只看最終畫面,早期短暫出現的內容又可能被忽略。Hacker News 也有人聯想到隱寫與校園內容過濾。這不代表現有系統一定能被直接繞過,但足以提醒服務端:驗證圖片不能只看副檔名、第一格或單一解碼器結果。
第二個影響是效能與資源上限。Progressive JPEG 不是免費功能,解碼器可能多次更新畫面,過多 scan 也會增加 CPU、記憶體與重繪成本。瀏覽器選擇在某個數量後停止,可能正是避免惡意或畸形檔案無限消耗資源。網站若依賴大量漸進圖,也應在真實裝置上測試,而不是只看檔案大小變小了多少。
第三個影響是跨瀏覽器相容性。同一份符合部分規則的資料流,在 Chrome、Firefox、Safari 與 WebKit 上可能分別動畫、等待、卡住或失敗。這類差異對日常照片無關緊要,但對數位鑑識、內容預覽、爬蟲、CDN 轉檔與自動化測試來說都很重要。格式邊界不只是一個好玩的 demo,也是一份免費的壓力測試案例。
社群反應:很酷,但別真的拿來當播放器
討論串最受歡迎的延伸點,是把伺服器變成節拍器:即時產生 JPEG,並以固定間隔逐段送出,甚至串接網路攝影機,讓「單張圖片」持續增加新畫面。這能把網路延遲造成的不穩定降到較低,卻也意味著連線不會自然結束,代理伺服器、CDN 與瀏覽器資源管理都可能介入。
另一派則提醒 progressive decoding 本身可能比 baseline 慢。對現代高速網路而言,使用者已很少明顯看見圖片從糊變清楚;若產品只沿用「漸進式永遠比較好」的舊建議,卻沒有量測解碼時間與重繪成本,可能反而拖慢體感。
比較精準的結論不是「JPEG 原來能播影片」,而是「JPEG 的 progressive scan 可以被安排成連續覆寫畫面,而某些瀏覽器願意把中間狀態畫出來」。它是巧妙的格式駭客實驗,不是可靠、可控制或可攜的媒體方案。
接下來值得追蹤什麼?
JPEG 委員會介紹的 JPEG XL 本來就支援 progressive coding,也正式支援 animation;新格式不需要靠網路延遲與解碼器副作用模擬影片。對照之下,Maurycy 的作品更像是對 1990 年代格式設計的考古:同一套低頻先行概念,在標準功能裡是提升感知載入速度,在邊界玩法裡卻能變成一部沒有時間軸的短片。
後續可以觀察三個方向:
- Chrome、Firefox 與 WebKit 是否會因資源管理或安全考量,調整 scan 數量與部分渲染策略。
- 圖片掃描器、社群平台縮圖服務與 CDN 轉檔,是否會把這種檔案正規化成單一最終畫面。
- 研究者能否把範例整理成跨瀏覽器測試集,釐清「合法但極端」與「損毀檔案」的界線。
這張會動的 JPEG 最有價值的地方,或許正是它沒有實用價值。當工程師可以放心把格式推到荒謬邊界,瀏覽器、解碼器與安全工具平常看不見的假設,才會一個個浮上來。
延伸閱讀:原始實驗與 merge.c|MDN:漸進式媒體渲染|Library of Congress:Progressive JPEG|libjpeg-turbo:MozJPEG|JPEG.org:JPEG XL
網友熱門留言 (5)