深度解析

PNG 的誕生:從 GIF 專利危機到網路上的無失真格式

koboshiCo-founder
·6 分鐘閱讀
PNG 的誕生:從 GIF 專利危機到網路上的無失真格式
概述

PNG 誕生於 1995 年的 GIF 專利危機。志願者在大約四個月內搭出規格草案,結合濾波器預處理與 DEFLATE 壓縮,後來成為靜態影像的首選。這篇文章從 Unisys 的訴訟講起,一直到你可以在十六進位編輯器裡讀到的檔案簽章。

用十六進位編輯器開啟一個 PNG 檔案。前八個位元組:

89 50 4E 47 0D 0A 1A 0A

這就是 PNG 檔案簽章。每個 PNG 解碼器都會檢查它。第一個位元組 0x89 被刻意設得很高,為的是防止簡單的文字編輯器把它當成 ASCII 處理。50 4E 47 是 ASCII 裡的 "PNG"。0D 0A 是 DOS 換行字元,1A 是 DOS 的檔案結束標記,0A 是 Unix 換行字元。設計者把這些換行字元塞進簽章,是為了讓以文字模式進行 FTP 傳輸時檔案立即損毀,迫使用戶改用二進位模式。這聽起來像是過度反應,但他們才剛親眼看到一種受專利保護的格式變成法律武器。

今天,只要是需要反覆存檔後像素仍完全一致的影像,PNG 幾乎都是預設選擇。截圖、UI 素材、圖表、Logo 都常用它。它比 Google 和 iPod 更早出現,至今仍是無失真壓縮的標準選項。最初它根本不是為了成為標準,只是權宜之計。

催生新格式的訴訟

1995 年 1 月,Unisys 宣布將對使用 GIF 格式的開發者強制執行 LZW 壓縮專利,也就是美國專利 4,558,302。該專利涵蓋了 Lempel-Ziv-Welch 演算法,這是每個 GIF 編碼器和解碼器的核心壓縮方法。

1995 年的網際網路幾乎靠 GIF 運轉。動態橫幅、透明 Logo、平鋪背景,GIF 都能處理。然後 Unisys 開始收授權費。商業軟體廠商面臨授權費用,開源專案面臨生存威脅。GNU Image Manipulation Program(GIMP)直接受到影響。Web 開發社群震怒。

需要一種東西來替代 GIF 處理靜態影像。需求很明確:沒有專利、壓縮率優於 GIF、支援全彩、並提供真正的 Alpha 通道,而不是 GIF 那種粗糙的單色透明。它還得夠簡單,讓開發者能在一個週末內寫出解碼器。

1995 年 4 月 4 日,Thomas Boutellcomp.graphics Usenet 新聞群組發了一份提案。他稱之為 PBF——Portable Bitmap Format。這個名字沒叫開。接下來的幾個月裡,一個郵件清單逐漸形成。貢獻者包括 Tom Lane(Independent JPEG Group 負責人)、Lee Daniel CrockerAlexander Lehmann,以及幾十位其他人。到 1996 年 10 月 1 日,PNG 規範以 RFC 2083 的形式凍結。從想法到標準,整個過程大約花了十八個月。作為對比,JPEG 花了六年。

PNG 出現之前的選擇

1995 年,你的影像格式選項很有限,而且每個都有包袱:

格式壓縮色深透明專利風險典型用途
GIFLZW最多 256 色1 位,1 種顏色有(LZW)Web 圖形、動畫
JPEGDCT + Huffman24 位全彩基礎專利已過期照片
BMP無或 RLE最高 24 位Windows 桌布
TIFFLZW、PackBits 等最高 48 位LZW 可選印刷、掃描
PCXRLE最高 24 位DOS 遊戲、早期剪貼畫

GIF 主宰了 Web,但在法律上是有害的。它的 256 色調色盤對圖示和卡通來說夠用,對照片則完全不行。它的透明只有兩種狀態:一個像素要麼完全不透明,要麼完全透明。沒有柔和邊緣,也沒有陰影。

JPEG 處理照片極為出色,但會不可逆地銷毀資料。開啟一張 JPEG,編輯,再儲存一次,影像就開始劣化。JPEG 也完全不支持透明。對於需要把 Logo 放在紋理背景上的 Web 設計師來說,JPEG 毫無用處。

BMPPCX 要麼不壓縮,要麼只做了簡單壓縮。1995 年一張 640 × 480 的 BMP 要吃掉 900 KB。在 28.8 kbps 的數據機上,下載需要四分多鐘。

TIFF 強大且靈活,但靈活性正是它的詛咒。TIFF 檔案可以使用十幾種不同的壓縮方案、色彩空間和位元深度。寫一個通用 TIFF 解碼器是論文等級的專案,不是週末就能 hack 出來的。

PNG 的設計目標相當明確:取代 GIF 處理靜態影像、壓縮率超過 GIF、加入全彩與真正的透明,並且永遠免費。

PNG 如何壓縮

PNG 壓縮分為兩個階段。單獨看,每一步都不特別;組合起來,效果卻很好。

階段 1:濾波

在任何壓縮發生之前,PNG 先把原始像素資料送進一個濾波器。濾波器本身不壓縮任何內容。它只是重新排列資料,讓下一階段能壓得更好。

影像是一個位元組網格。在一張晴朗天空的照片裡,相鄰像素幾乎相同。但原始位元組流仍然為每個通道儲存 120, 121, 120, 122, 119。差值很小:+1, -1, +2, -3。如果你儲存差值而不是絕對值,結果數字會聚集在零附近。這正是壓縮演算法最拿手的。

PNG 為每條掃描線定義了五種濾波類型:

濾波類型名稱作用
0None儲存原始位元組
1Sub儲存與前一個像素的差值
2Up儲存與上方像素的差值
3Average儲存與 Sub 和 Up 平均值的差值
4Paeth儲存與最佳預測值(Sub/Up/對角線)的差值

編碼器會對每條掃描線嘗試全部五種濾波,然後選擇在階段 2 後輸出最小的那種。這就是 PNG 編碼器可能很慢的原因:它在窮舉濾波組合。但解碼很快,因為濾波類型已經寫在檔案裡,解碼器只要做逆運算。

階段 2:DEFLATE

濾波之後,資料用 DEFLATE 壓縮,gzip 和 ZIP 檔案用的也是同一種演算法。DEFLATE 是 LZ77(滑動視窗重複字串消除)和 Huffman 編碼(為頻繁出現的符號分配變長前綴碼)的組合。

結果:一張典型的截圖或 UI 圖形,可以壓到未壓縮 BMP 的 3–5 分之一。照片用 PNG 壓縮通常比 JPEG 大 5–10 倍,但每個像素都可恢復。壓縮從設計上就是無失真的:沒有資料被丟棄,只去除冗餘。

作為參考,一張 1920 × 1080 的原始 RGB 截圖是 6.2 MB。同一張截圖存成 PNG 通常會降到 800 KB – 1.5 MB。同一張影像用 JPEG 品質 90 是 300–500 KB,但重複儲存十次就會引入可見瑕疵。

主要功能

PNG 不只是沒有專利的 GIF 替代品,它還補上了 Web 設計師從 1993 年起就想要的功能。

全彩:PNG 支援 24 位元 RGB(1670 萬色)和 48 位元深彩色,沒有調色盤限制。一張 PNG 照片可以顯示人眼能分辨的每一種顏色。

Alpha 通道:PNG 支援 8 位元 Alpha,也就是每個像素有 256 級透明。陰影可以從完全不透明平滑過渡到完全透明,圓角按鈕在任何背景上都能抗鋸齒。GIF 只提供 1 位元透明:一種顏色要麼全開,要麼全關,兩者差距很大。

Adam7 交錯:PNG 可以按 7 輪交錯順序儲存像素。瀏覽器收到檔案的 1/64 後就能渲染出粗略預覽,然後逐步細化。與 GIF 的逐行交錯不同,Adam7 從第一輪就把細節分佈到整幅影像上,到第三輪已可辨認,第七輪則完全精確。

伽瑪校正:PNG 在詮釋資料裡儲存伽瑪值。在 Mac(伽瑪 1.8)上建立的影像,在 Windows PC(伽瑪 2.2)上也能正確顯示,無需手動校色。在 1990 年代跨平台一致性還很罕見時,這解決了實際問題。

CRC 檢查碼:每個 PNG 資料塊都帶有 CRC-32 檢查碼。損毀的下載能被立即檢測出來,而不是渲染成半張影像。

透過讀取檔案簽章識別 PNG

別信 .png 副檔名。讀前八個位元組,檢查簽章。

精確的位元組布局:

Bytes 0–7:   簽章 89 50 4E 47 0D 0A 1A 0A
Bytes 8–11:  資料塊長度(大端 uint32)
Bytes 12–15: 資料塊類型:"IHDR"(影像標頭)
Bytes 16–19: 影像寬度(大端 uint32)
Bytes 20–23: 影像高度(大端 uint32)
Byte 24:     位元深度
Byte 25:     色彩類型
Byte 26:     壓縮方法(始終為 0)
Byte 27:     濾波方法(始終為 0)
Byte 28:     交錯方法(0 或 1)

瀏覽器裡的 TypeScript:

async function isPng(file: File): Promise<boolean> {
  const buffer = await file.slice(0, 8).arrayBuffer()
  const bytes = new Uint8Array(buffer)

  const signature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]
  return bytes.length === 8 && bytes.every((b, i) => b === signature[i])
}

Python

def is_png(path: str) -> bool:
    with open(path, "rb") as f:
        header = f.read(8)

    return header == b"\x89PNG\r\n\x1a\n"

用標準函式庫的更高階做法:

import imghdr

if imghdr.what("image.png") == "png":
    pass

或者使用 Pillow:

from PIL import Image

try:
    with Image.open("image.png") as img:
        is_png = img.format == "PNG"
except Exception:
    is_png = False

Go

func isPng(path string) bool {
	f, err := os.Open(path)
	if err != nil {
		return false
	}
	defer f.Close()

	buf := make([]byte, 8)
	if _, err := f.Read(buf); err != nil {
		return false
	}

	return bytes.Equal(buf, []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a})
}

PHP

function isPng(string $path): bool {
    $header = file_get_contents($path, false, null, 0, 8);
    return $header === "\x89PNG\r\n\x1a\n";
}

使用 fileinfo

$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file('image.png');
// image/png

ImageMagick CLI

magick identify -verbose image.png | grep "Format:"
# Format: PNG (Portable Network Graphics)

或者簡單一點:

file image.png
# image.png: PNG image data, 1200 x 675, 8-bit/color RGBA, non-interlaced

PNG 的局限

PNG 並不完美。它的設計是權衡,有些選擇至今仍帶來代價。

缺少內建動畫:PNG 工作群組明確拒絕了動畫。他們想先把靜態影像問題解決好,再考慮增加複雜度。結果是動畫 GIF 又存活了二十年。APNG(Animated PNG)最終在 2004 年標準化,但瀏覽器支援直到 2010 年代末才趨於完善。即使今天,動畫 GIF 的數量仍以數量級優勢超過 APNG。

照片體積過大:一張 1200 萬像素的照片存成 PNG 通常是 15–25 MB。用 JPEG 品質 90 則只有 3–5 MB。PNG 的無失真壓縮無法與基於 DCT 的心理視覺丟棄競爭。對於照片,PNG 是錯誤的工具。

不支援 CMYK:PNG 只支援 RGB。需要 CMYK 分色的印刷工作流程必須把 PNG 轉成 TIFF 或 JPEG。這是刻意為之,設計者聚焦於螢幕顯示而非印刷,但這也限制了 PNG 在專業出版中的實用性。

解碼比 JPEG 慢:PNG 解碼需要對每條掃描線進行逆濾波和 DEFLATE 解壓。JPEG 解碼則可以高度平行化,並且已被硬體深度最佳化。在行動裝置上,一張大 PNG 的渲染時間可能是同等解析度 JPEG 的 2–3 倍,拖慢 Largest Contentful Paint。

沒有漸進品質:與 JPEG 2000 或 JPEG XL 不同,PNG 無法從截斷的檔案生成低品質預覽。你要麼有完整檔案,要麼什麼都沒有。Adam7 交錯有助於生成粗略預覽,但它不會減少總檔案大小。

為什麼 PNG 從未取代 JPEG

這是關於影像格式最常見的誤解。PNG 和 JPEG 本來就不是做同一份工作。

JPEG 是一種失真心理視覺壓縮器。它丟棄的是你的眼睛大概率不會注意到的資料。它為連續色調照片設計,也就是具有平滑漸層、精細紋理和自然光照的影像。在這個領域,JPEG 在尺寸與品質的權衡上至今仍然難以取代。

PNG 是一種無失真資料壓縮器。它保留每一位元。它為離散色調影像設計,例如截圖、UI 元素、圖表、Logo、文字疊加,其中銳利邊緣和精確顏色至關重要。在這個領域,PNG 是標準。

兩種格式各自劃分了地盤:

使用情境正確格式原因
照片JPEG/AVIF失真壓縮可以小 5–10 倍
截圖PNG/WebP無失真保留文字銳度
帶透明的 LogoPNG/WebPAlpha 通道 + 無失真邊緣
UI 圖示PNG/SVG體積小,顏色精確匹配
科學資料視覺化PNG漸層圖例不產生瑕疵
印刷就緒影像TIFF/JPEG XL支援 CMYK、高位元深度

PNG 從來沒打算取代 JPEG。它只是找到了不同的用武之地。

PNG 今天的位置

2025 年 Web Almanac 的資料顯示,PNG 約占 Web 上所有服務影像的 22%。這比峰值有所下降,WebP 和 AVIF 正在蠶食份額,但 PNG 仍然是所有瀏覽器、影像編輯器、作業系統都能直接開啟的備用格式。

WebP(Google,2010)同時支援失真和無失真模式,外加動畫和透明。無失真 WebP 通常比同等 PNG 小 20–30%。瀏覽器支援自 2020 年起已全面普及。對於新專案,WebP 無失真在大多數情況下是 PNG 的務實替代。

AVIF(AOM,2019)實現了更優的壓縮率,但其無失真模式的編碼和解碼速度都比 PNG 慢。AVIF 也缺少對某些進階 PNG 功能的完整瀏覽器支援,比如 16 位元通道和嵌入式伽瑪校正。

SVG 在簡單的向量圖形領域統治了圖示和 Logo,但點陣化的複雜圖形仍然需要 PNG。

PNG 不會消失。它是安全預設值,是匯出檔案時用來確保收件人能開啟的格式。它像影像格式的 QWERTY 鍵盤,不是最優的,但人人都會用。

PNG 的未來

PNG 是一個已完成的標準。規範自 2003 年以來沒有實質性變化。這種穩定性是特性,不是缺陷。你在 1997 年建立的 PNG,在任何現代瀏覽器裡開啟,渲染結果完全一致。

PNG 周圍的生態系統仍在演進。APNG 正逐步普及:Safari 從 2014 年起就支援它,Chrome 和 Firefox 隨後跟上,Discord、Slack 和 Twitter 都原生渲染 APNG。對於短的 UI 動畫(載入轉圈、反應表情、狀態指示器),APNG 正以更小的檔案和更好的色彩保真度取代動畫 GIF。

PNG 最佳化工具也持續進步。oxipngpngcrushzopfli 可以透過暴力搜尋更好的濾波組合和 DEFLATE 參數,把 PNG 檔案再削減 10–30%。對於高流量網站,把每張 PNG 過一遍 oxipng 已是常見做法。

把 PNG 當成容器:一些現代工作流程把 ICC 色彩設定檔、EXIF 詮釋資料甚至 XMP 資料嵌入 PNG 資料塊。PNG 已經成為一種輕量級封存格式,不如 TIFF 豐富,但可攜性強得多。

長期來看,PNG 將與 WebP、AVIF 和 JPEG XL 共存多年。它占據了一個沒有其他格式能完全覆蓋的領域:無失真、免專利、到處都支援,又簡單到足以讓開發者在一週內根據規範寫出解碼器。這種組合很難被取代。

回到起點

PNG 誕生,是因為開發者們受夠了。一場專利訴訟威脅到開放網路,一群開發者在業餘時間做出了替代品。他們當初並沒有打算建立一個能存續三十年的標準,只是要解決一個問題:如何在不付授權費的情況下,把一張透明 Logo 放到網頁上。

結果超出所有人的預期。PNG 帶給 Web 全彩圖形、平滑透明和可偵測損毀的檔案完整性。它證明了志願者建立的開放標準,能夠抗衡有大企業撐腰的專有格式。

它不是最小的格式,也不是解碼最快的;它不適合動畫,對照片也束手無策。但當你需要確保存進去的每一個像素,日後取出來還是原樣,你依然會選擇 PNG。

不是所有圖片生來就是 PNG。如果你手邊的 JPG 需要透明通道或無失真編輯,直接在瀏覽器裡就能轉換,不用裝任何軟體,資料也不會離開你的裝置。JPG 轉 PNG 在本地完成這件事。做 Web 專案時如果檔案大小讓人頭痛,JPG 轉 WebP 能在不犧牲畫質的情況下把檔案變小。而當你需要網站圖示(favicon)時,JPG 轉 ICO 能把照片變成多種尺寸的 ICO 檔案。

更多推薦閱讀