为什么小 JPEG 在 Chrome 中看起来不同
看起来像是渲染错误的其实是 Chrome 中一个巧妙的 JPEG 解码优化。几分钟前,我在和一位同事的电脑聊天时,注意到他们显示的一个徽标与我电脑上的并不完全一样。它在他们的电脑上看起来更细,更忠于原始图像。它以 15px 的大小渲染;这里有一个放大的版本。注意:这不是原始图像。这是一个较早的情况,所以我制作了一个新图像来演示这个问题。左边是 Firefox;右边是 Chrome。如果你眯起眼睛,或后退一步,Chrome 渲染的看起来是更粗的。有点奇怪,但用 SVG 替换图像后修复了。尽管如此,我还是很好奇:为什么一开始会以这样的方式渲染?我做了一些调查,发现 Chrome 在小尺度下渲染 JPEG 时使用了一个巧妙的优化。缩小图像可能是浪费的。直观的方式是从 JPEG 中完全解压缩图像,然后缩小。但这并不总是有效的。想象一个 2000 × 2000 的 JPEG,需要显示为 20 × 20。一旦解压缩,图像占用的内存远远超过最终结果。完整图像的位图大约使用 12 MB,而最终的 20 × 20 图像仅需要约 1.2 KB。在缩小过程中,大部分大版本中的信息被丢失。缩小时丢失了什么信息?一个有趣的见解是,丢失的信息并不是随机的。当图像大幅缩小时,消失的信息主要是高频细节。这一点很容易直观地看到。想象一棵有很多叶子和粗糙树皮的树:这些细微细节在像素间变化很快,因此它们算作高频信息。如果你把这棵树缩小到很小的东西,比如 20 × 10,你最后得到的只是顶部的绿色模糊和底部的棕色树干。缩小版本抛弃了细微细节,即高频信息。缩小树的插图这部分高频信息仍然在一定程度上保留,因为这些细节混合在一起。JPEG 如何存储图像数据我将保持这个解释简单明了,但我仍会提到几个专业术语,它们可以是你想深入研究的良好起点。我也将跳过 JPEG 变换的大部分,因为这在这里并不需要。在 JPEG 压缩过程中,图像被分割成 8 × 8 的块并转换为频域。这一操作称为 DCT(离散余弦变换)。在一个 8 × 8 的块中,最低可能的频率是均匀的颜色。严格来说,这不是真正的频率,因为没有变化;它是常数成分。在另一端,最高频率看起来像一个棋盘,值的变化尽可能大。介于两者之间的所有内容代表了频域的其余部分。这些被称为基函数。基函数:你可以在左上角看到均匀的颜色,在右下角看到棋盘。因此,将 8 × 8 的块转换为频域基本上是在问:这个块中每种模式的量是多少?这些量被称为系数。JPEG 压缩在存储这些系数以提高效率方面还有一些更多的步骤,这就是发生失真压缩的地方。但那部分与我们在这里讨论的内容无关。综合起来:以 1/8 的比例渲染 JPEG 现在假设你想要将图像缩小一个 8 的倍数。之前提到的 8 × 8 块现在可以用下缩图像中的单个像素表示。在那个大小下,图像主要需要低频信息,因为正如树的例子所示,在缩放过程中高频细节基本上消失。因此,我们可以跳过高频部分的系数,仅使用那部分系数来得到粗略版本的图像。这可以在不完全扩展原始图像的情况下取得缩小的结果。解码后的图像占用的空间更小,且解压缩速度更快,因为我们跳过了大部分的系数。这可以扩展到其他比例,只要它们是以 8 为分母的分数。这一技术的专业名称是部分IDCT缩放。请参见 jpegclub.org(如果你对此稍作了解,会发现此技术也可以用于放大图像!)反离散余弦变换:将频域带回图像域。Chrome 如何适应 Chrome 将图像解码和渲染委托给 Skia。对于 JPEG,Skia 使用 libjpeg-turbo,它实现了部分 IDCT 缩放。这使得在目标大小足够小时,它只解码低频数据。换句话说,Chrome/Skia 并不总是解压缩完整图像然后再缩放。它计算出最接近的以 8 为分母的分数,并在目标大小足够小时解码图像。
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡