用十六进制编辑器打开一个 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、支持真彩色、拥有真正的阿尔法通道而不是 GIF 那种粗糙的单色透明。它还必须足够简单,让一个开发者用一个周末就能写出解码器。
1995 年 4 月 4 日,Thomas Boutell 在 comp.graphics Usenet 新闻组发了一份提案。他称之为 PBF——Portable Bitmap Format。这个名字没叫开。接下来的几个月里,一个邮件列表逐渐形成。贡献者包括 Tom Lane(Independent JPEG Group 负责人)、Lee Daniel Crocker、Alexander Lehmann,以及几十位其他人。到 1996 年 10 月 1 日,PNG 规范以 RFC 2083 的形式冻结。从想法到标准,整个过程大约花了十八个月。作为对比,JPEG 花了六年。
PNG 出现之前的选择
1995 年,你的图像格式选项很有限,而且每个都有包袱:
| 格式 | 压缩 | 色深 | 透明 | 专利风险 | 典型用途 |
|---|---|---|---|---|---|
| GIF | LZW | 最多 256 色 | 1 位,1 种颜色 | 有(LZW) | Web 图形、动画 |
| JPEG | DCT + Huffman | 24 位真彩色 | 无 | 基础专利已过期 | 照片 |
| BMP | 无或 RLE | 最高 24 位 | 无 | 无 | Windows 壁纸 |
| TIFF | LZW、PackBits 等 | 最高 48 位 | 有 | LZW 可选 | 印刷、扫描 |
| PCX | RLE | 最高 24 位 | 无 | 无 | DOS 游戏、早期剪贴画 |
GIF 统治了 Web,但在法律上是有毒的。它的 256 色调色板对图标和卡通来说够用,对照片则完全不行。它的透明是二元的:一个像素要么完全不透明,要么完全透明。没有柔和边缘,没有投影。
JPEG 处理照片极为出色,但会不可逆地销毁数据。打开一张 JPEG,编辑,再保存一次,图像就开始劣化。JPEG 也完全不支持透明。对于需要把 Logo 放在纹理背景上的 Web 设计师来说,JPEG 毫无用处。
BMP 和 PCX 要么不压缩,要么几乎不压缩。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 为每条扫描行定义了五种滤波类型:
| 滤波类型 | 名称 | 作用 |
|---|---|---|
| 0 | None | 存储原始字节 |
| 1 | Sub | 存储与前一个像素的差值 |
| 2 | Up | 存储与上方像素的差值 |
| 3 | Average | 存储与 Sub 和 Up 平均值的差值 |
| 4 | Paeth | 存储与最佳预测值(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 照片可以显示人眼能分辨的每一种颜色。
阿尔法通道:PNG 支持 8 位阿尔法——每个像素 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 | 无损保留文字锐度 |
| 带透明的 Logo | PNG/WebP | 阿尔法通道 + 无损边缘 |
| 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 优化工具 持续进步。oxipng、pngcrush 和 zopfli 可以通过暴力搜索更好的滤波组合和 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 文件。


