Skip to content

UPNG 压缩输出体积波动说明

1. 现象描述

在使用 UPNG.js(或其 TypeScript 封装版本)进行图片压缩时,我们观察到以下现象:

  • 输入:同一张源图片。
  • 参数:完全相同的配置(例如 cnum = 256)。
  • 结果:每次运行压缩生成的输出文件,其二进制哈希值 (Hash) 不同,且文件体积 (File Size) 会有轻微波动(通常在 1% - 5% 之间)。

2. 核心原因:算法的随机性

这不是 Bug,而是 UPNG 内部使用的 颜色量化 (Color Quantization) 算法的固有特性。

2.1 颜色量化过程

当我们将图片从真彩色(数百万种颜色)压缩到索引色(256种颜色)时,算法需要从原图中计算出“最能代表整张图片”的 256 种颜色(即调色板 Palette)。

2.2 K-Means 与随机初始化

UPNG 在构建颜色树或进行聚类分析(类似于 K-Means 算法)时,需要选取初始的中心点或参考向量。为了避免算法陷入局部最优解,源码中使用了随机数进行初始化

UPNG.js 源码的 estats 函数中,存在如下逻辑:

javascript
// 源码片段 (estats 函数)
// 使用 Math.random() 生成随机向量用于计算统计信息
var b = [Math.random(), Math.random(), Math.random(), Math.random()];

由于 Math.random() 每次执行的结果都不同,导致后续的计算路径发生微小偏移。

3. 为什么这会影响文件大小?

虽然最终生成的颜色数量都是 256 种,但随机性引发了连锁反应:

  1. 调色板差异:每次计算出的 256 种“最佳颜色”的具体 RGB 值会有细微差别。
  2. 像素索引变化:由于调色板变了,图片上每个像素点引用的“颜色索引号”也会随之改变。
  3. 压缩率波动 (Deflate)
    • PNG 的最后一步是 Deflate 无损压缩(类似于 Zip)。
    • Deflate 算法对数据的重复模式极其敏感。
    • 如果某次随机生成的像素索引排列更有规律,压缩率就高,体积就小;反之则体积稍大。

4. 影响评估

维度评估结果说明
视觉质量无区别虽然数学上颜色值有细微偏差,但肉眼无法分辨这些差异。
文件有效性有效生成的始终是符合标准的 PNG 文件。
体积波动轻微通常波动范围极小,不影响实际业务传输效率。
一致性无法通过哈希校验来判断文件是否被修改(因为每次生成都不同)。

5. 结论与建议

结论

这是预期内的正常行为 (Feature, not a bug)。 这种随机性有助于在某些边缘情况下获得更好的平均画质,避免算法陷入局部最优解。

建议

  1. 在生产环境中:忽略此差异。它不会影响用户体验或图片加载速度。
  2. 在自动化测试中:如果您编写了对比压缩前后文件的单元测试,不要比较文件的 MD5/Hash 值,也不要通过严格相等的体积来断言。建议设置一个允许的体积波动阈值(例如 ±5%)。

极端情况解决方案

如果业务场景严格要求“幂等性”(即输入不变,输出必须由二进制级别的完全一致),唯一的解决方案是修改源码:

Math.random() 替换为带种子的伪随机函数(Pseudo-Random Number Generator, PRNG),确保每次初始化的随机向量一致。但对于常规 Web 应用,不推荐这样做,因为维护成本高于收益。

6. 补充说明:关于 cnum 参数的物理上限 (256)

在使用 UPNG 进行压缩时,cnum (Color Number) 参数决定了输出图片的颜色数量。虽然代码允许设置大于 256 的数值,但在以减小体积为目标的场景下,256 是绝对的物理上限

6.1 PNG 格式的限制原理

这由 PNG 文件标准 (ISO/IEC 15948) 决定:

  • Type 3 (索引模式):使用调色板存储颜色。标准规定索引深度最大为 8-bit,即最多存储 256 种颜色。此时每个像素仅占 1 byte。这是 UPNG 实现高压缩率的核心。
  • Type 6 (真彩色模式):不限制颜色数量。但每个像素必须存储完整的 RGBA 值,占用 4 bytes

6.2 设置 cnum > 256 的后果

如果您设置 cnum = 500,会触发“无效压缩”:

  1. 算法将颜色减少到 500 种(画质受损)。
  2. 由于 500 > 256,无法使用索引模式,编码器被迫切换回真彩色模式
  3. 结果:文件体积瞬间膨胀 4 倍(回到原图大小),且画质不如原图。

6.3 最佳配置表

cnum 设置值模式体积表现适用场景
0无损 (TrueColor)大 (100%)医疗影像、设计原稿存档
256 (推荐)有损 (Indexed)极小 (~30%)Web 展示、App、H5、小程序
> 256无意义大 (100%)请勿使用 (体积大且画质受损)

Last updated: