3、PNG压缩策略:量化、过滤、Deflate算法,工具推荐(pngquant, pngcrush)

PNG 这格式,说实话,又爱又恨。爱它无损、支持透明,恨它体积经常比 JPG 大好几倍。我在做移动端界面移植时,经常遇到设计师丢过来一张 2MB 的 PNG 启动图,嘴里还念叨着「这是无损的呀」。嗯,无损不代表没得优化。

今天我们就来拆解一下 PNG 的压缩三板斧:量化、过滤、Deflate。搞懂这三步,你就能明白为什么 pngquant 和 pngcrush 能帮你省下 60%~80% 的体积。

3.1 PNG 压缩的整体流程

先看一张图,把整个压缩链路串起来。你想想看,一张原始位图从内存到磁盘文件,中间经历了什么?

PNG 压缩流程 原始位图 RGBA 像素数据 量化 颜色数减少 过滤 行间差分 Deflate 压缩 LZ77 + Huffman PNG 文件 IDAT 数据块 量化是有损的,过滤和 Deflate 是无损的。顺序不能乱。

说白了,PNG 压缩分三步走:先砍颜色(量化),再让数据更规律(过滤),最后用通用压缩算法收尾(Deflate)。每一步都有讲究,我们一个一个说。

3.2 量化:砍掉你不需要的颜色

量化,就是把一张 24 位真彩色图(1600 万色)变成 8 位索引色(256 色)甚至更少。你可能会问:「颜色少了,画质不就崩了吗?」

嗯,这里有个关键点:界面 UI 图通常颜色数很少。一个按钮、一个图标、一个背景渐变,可能本身就只有几十种颜色。我做过一个统计,移动端界面 PNG 平均颜色数不到 200 种。你强行保留 1600 万色,纯粹是浪费空间。

3.2.1 量化算法怎么选?

算法 原理 质量 速度 适用场景
中位切分 递归切分颜色立方体 中等 快 通用,pngquant 默认
八叉树 八叉树颜色聚类 较好 中等 渐变丰富的图
K-Means 迭代聚类中心 最好 慢 离线批量处理
固定调色板 使用预定义颜色集 差 极快 Web 安全色

我个人习惯用 pngquant,它默认就是中位切分 + 抖动优化。我在项目中遇到过一张 512×512 的图标,原图 180KB,用 pngquant 压到 32KB,肉眼几乎看不出区别。你想想看,省了 80% 的体积,加载速度能不快吗?

小技巧:量化时保留透明通道。pngquant 的 --alpha 参数可以单独优化 alpha 通道,避免透明边缘出现锯齿。我一般用 pngquant --quality=65-80 --speed=1 input.png。

3.3 过滤:让数据更「好压」

量化之后,数据还是像素级的。Deflate 算法喜欢重复的字节序列,但原始像素数据往往不够「重复」。这时候就需要过滤(Filter)上场了。

过滤的本质是行间差分。PNG 定义了 5 种过滤模式:

  • None(0):不做任何处理,原始数据直接送 Deflate。
  • Sub(1):用当前像素减去左边像素。适合水平渐变。
  • Up(2):用当前像素减去上方像素。适合垂直渐变。
  • Average(3):用当前像素减去左边和上边的平均值。适合均匀区域。
  • Paeth(4):用当前像素减去左边、上边、左上角的线性预测值。最智能,但计算量最大。

你可能会问:「那我该选哪种?」

答案是:每行单独选。PNG 编码器会对每一行尝试 5 种过滤模式,选压缩率最高的那个。这就是 pngcrush 的核心工作——它暴力尝试所有过滤组合,找到最优解。

注意:过滤是无损的,不会改变像素值。但选错了过滤模式,Deflate 压缩率会差很多。我曾经遇到过一张纯色背景图,用 Sub 过滤压出来 12KB,用 Paeth 过滤压出来 38KB——差了 3 倍多。

3.4 Deflate:最后的通用压缩

过滤之后的数据,已经变成了「差值序列」。这些差值里有很多 0 和小数值,非常适合 Deflate 算法。

Deflate 是 LZ77 + Huffman 编码的组合拳:

  1. LZ77:找重复的字节序列,用「距离+长度」对替换。比如连续 100 个 0,会被编码成「距离=1,长度=100」。
  2. Huffman 编码:对出现频率高的符号用短码,频率低的用长码。进一步压缩。

这里有个坑:Deflate 的压缩级别。级别从 0(不压缩)到 9(最大压缩)。级别越高,LZ77 的搜索窗口越大,压缩率越好,但编码时间也越长。

级别 压缩率 编码速度 解码速度 推荐场景
0 0% 极快 极快 调试用
1-3 低 快 快 开发环境
4-6 中等 中等 快 生产环境(推荐)
7-9 高 慢 快 发布包、安装包

我个人习惯用级别 6。级别 9 虽然能多压 2%~5%,但编码时间可能多 10 倍。对于界面移植这种需要频繁迭代的场景,性价比不高。

3.5 工具推荐:pngquant vs pngcrush

这两个工具是 PNG 压缩界的「倚天剑」和「屠龙刀」。它们分工不同,我一般配合使用。

3.5.1 pngquant:量化神器

pngquant 专注于量化阶段。它把真彩色 PNG 转成 8 位索引色 PNG,同时用 Floyd-Steinberg 抖动算法减少色带。

常用命令:

# 基本用法:压缩到 256 色
pngquant input.png

# 指定质量范围(0-100),自动选择颜色数
pngquant --quality=65-80 input.png

# 保留透明通道,输出到指定目录
pngquant --ext .png --force --quality=65-80 input.png

# 速度优先(speed=1 最慢但质量最好,speed=10 最快但质量差)
pngquant --speed=1 --quality=65-80 input.png
实战经验:我在移植一个电商 App 的首页时,所有 PNG 图标加起来 1.2MB。用 pngquant 压一遍,降到 280KB,页面首屏加载时间从 3.2s 降到 1.1s。设计师看了对比图,说「我眼睛瞎了才看得出区别」。

3.5.2 pngcrush:过滤优化专家

pngcrush 不改变颜色数,它专注于过滤模式选择和IDAT 块重组。它会暴力尝试所有过滤组合,找到压缩率最高的方案。

常用命令:

# 基本用法:暴力搜索最优过滤
pngcrush -brute input.png output.png

# 指定过滤模式(0-4),减少搜索时间
pngcrush -l 9 -m 0 input.png output.png

# 只优化 IDAT 块,不重新压缩
pngcrush -rem alla -reduce input.png output.png

# 移除元数据(如 EXIF、ICC 等)
pngcrush -rem alla -brute input.png output.png
避坑指南:我曾经用 pngcrush -brute 压一张 4K 分辨率的大图,跑了 40 分钟还没结束。后来发现 -brute 会尝试所有过滤组合,对于大图来说计算量爆炸。建议大图用 -l 9 -m 0 指定过滤模式,或者先用 pngquant 量化缩小后再 crush。

3.5.3 组合拳:先量化,后 crush

我推荐的工作流是这样的:

  1. 第一步:用 pngquant 量化,把颜色数降到 256 色以内。
  2. 第二步:用 pngcrush 优化过滤和 IDAT 块。
  3. 第三步:对比压缩前后,确认质量可接受。

一个完整的脚本示例:

#!/bin/bash
# 批量压缩 PNG 脚本
for f in *.png; do
  echo "处理: $f"
  # 量化
  pngquant --quality=65-80 --ext .quant.png --force "$f"
  # crush
  pngcrush -l 9 -m 0 "${f%.png}.quant.png" "${f%.png}.opt.png"
  # 清理中间文件
  rm "${f%.png}.quant.png"
done
echo "完成!"

3.6 总结:记住这三句话

  • 量化砍颜色:界面图颜色少,用 pngquant 砍到 256 色以内,省 60%~80% 体积。
  • 过滤找规律:pngcrush 暴力搜索最优过滤模式,让数据更「好压」。
  • Deflate 收尾:级别 6 性价比最高,别盲目上 9。

嗯,这套组合拳我在多个项目里验证过,效果稳定。下次设计师再给你丢大 PNG,你就知道怎么「优雅地」把它变小了。