为什么 Chrome 中的微型 JPEG 看起来不同
摘要
本文解释了为什么与其他浏览器相比,Chrome 中的微型 JPEG 看起来会不同,原因在于一种 JPEG 解码优化,它会在重度缩小尺寸时跳过高频 DCT 系数。
暂无内容
查看缓存全文
缓存时间: 2026/08/12 14:19
# Guillaume Técher
来源:https://guillaumetech.github.io/posts/jpg-scaling-chrome/
一个看似渲染错误的问题,实际上是 Chrome 中一项巧妙的 JPEG 解码优化。
## 这个图标在我同事的电脑上看起来更好
不久前,我凑到同事的电脑前聊天时,发现一个 logo 显示的效果和我电脑上的不完全一样。在同事的电脑上,它看起来更细,更接近原始图像。它以 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**(离散余弦变换,Discrete Cosine Transform)。
在一个 8 × 8 块中,可能的最低频率是纯色。严格来说,它并不是真正的频率,因为没有任何变化;它是**常量分量**。而在另一端,最高频率看起来像棋盘格,数值变化达到最大。两者之间的所有内容代表了频域的其余部分。这些被称为**基函数**。
基函数*基函数:可以看到左上角的纯色,以及右下角的棋盘格。*
因此,将一个 8 × 8 块转换到频域,本质上是在问:这个块中包含每种模式各多少?这些量被称为**系数**。
之后,JPEG 压缩还有几个步骤来高效存储这些系数,有损压缩就发生在这里。但这一部分对于我们讨论的内容并不重要。
## 综合起来:以 1/8 比例渲染 JPEG
现在假设你想把图片缩小 8 倍。
我之前提到的那些 8 × 8 块,现在可以只用缩小后图像中的一个像素来表示。在这个尺寸下,图像主要需要低频信息,因为正如树的例子所示,高频细节在缩放过程中大多会消失。
因此,我们可以不解压整个 JPEG,而是跳过高频部分的系数,只使用粗略版本图像所需的系数。这样就能在不必先完整展开原始图像的情况下得到缩小后的结果。
解码后的图像占用更少空间,解压速度也更快,因为我们跳过了相当一部分系数。
这种方法可以扩展到其他比例,只要它们是以 8 为分母的分数。它的技术名称是**部分 IDCT 缩放\***。参见 jpegclub.org (https://jpegclub.org/djpeg/)(如果你稍微读一下相关内容,就会发现这项技术也可以用来放大图像!)。
\* 逆离散余弦变换:将频域带回图像域。
## Chrome 是如何做到的
Chrome 将图像解码和渲染委托给 Skia。对于 JPEG,Skia 使用 libjpeg-turbo,它实现了部分 IDCT 缩放。这样一来,当目标尺寸足够小时,Skia 可以只解码较低频率的数据。
换句话说,Chrome/Skia 并不总是先解压完整图像再缩放。它会计算以 8 为分母的最接近分数 (https://github.com/google/skia/blob/30ad01017a46a31859b580bc907457b0e43907a8/src/codec/SkJpegCodec.cpp#L383),然后以该比例解码图像。之后,再使用更传统的下采样算法进一步缩放图像,直到达到所需尺寸。
这就是为什么图像在我电脑上看起来更粗。因为它渲染得太小,所以使用部分 IDCT 缩放以八分之一比例解码。这样一来,频域表示中保留下来的数据只有常量分量;所有的边缘柔化和渐变都没有被用到。
说真的,这里的教训是:你不应该用 JPEG 来存放图标之类的图像。这种格式及其优化都是围绕我们对照片的感知来设计的。
毕竟,名字里就写着:联合**摄影**专家组(Joint **Photographic** Experts Group)。
相似文章
JPEG 是如何工作的:交互式探索 JPEG 的有损压缩方法
一篇交互式文章,解释 JPEG 有损压缩的工作原理,涵盖色彩空间转换、频域、量化和编码步骤。
JPEG XL 发展之路:开源实验塑造了图像编码的未来
谷歌工程师回顾 JPEG XL 背后长达十年的开源历程,重点介绍了 WebP Lossless、Butteraugli 和 Guetzli 等关键实验如何塑造了下一代图像标准。
浏览器对大网站区别对待
Safari 和 Firefox 等浏览器会针对特定大网站发布专用代码以修复兼容性问题,而 Chrome 则不会,这揭示了浏览器引擎处理网页异常的方式。
我的图片是如何进行抖动处理的
作者解释了使用 Imagemagick 进行图片抖动处理的方法,模拟调幅和调频打印模式,以减小文件大小并获得特定美学效果,灵感来源于 Low Tech Magazine 和粉色单色设计。
谷歌让 Pixel 相机变得更好,方法是让它们变得更差
谷歌 Pixel 11 引入了 Camera Looks,一套新的处理工具,让用户可以调低计算摄影的效果,以获得更真实或传统的图像外观,满足人们对少处理照片日益增长的需求。