跳到主内容
@wquguru
精选70Hacker News Best(web_list)技巧与观点

Chrome 中微小 JPEG 显示差异的原因

原文
发到 X

Monday, August 3, 2026

2026年8月3日,星期一

Why Tiny JPEGs Look Different in Chrome

为什么Chrome中的微小JPEG看起来不同

What looked like a rendering bug turned out to be a clever JPEG decoding optimization in Chrome.

看似渲染错误,实则是Chrome中巧妙的JPEG解码优化。

This icon looks better on my colleague’s computer

这个图标在我同事的电脑上看起来更好

A while back, when chatting with a colleague over their computer, I noticed that a logo did not look exactly the same as it did on mine. It looked thinner on theirs and more faithful to the original image. It was rendered at 15px; here is an upscaled version.

不久前,当我和同事在他们的电脑上聊天时,我注意到一个标志看起来和我电脑上的不完全一样。他们的看起来更细,更接近原始图像。它以15像素渲染;这里有一个放大的版本。

Note: this was not the original image. It happened a while ago, so I made a new image to demonstrate the issue.

注意:这不是原始图像。这发生在一段时间前,所以我制作了一张新图像来演示这个问题。

On the left, Firefox; on the right, Chrome.

左边是Firefox,右边是Chrome。

If you squint, or take a step back, the one from Chrome looks thicker. A bit weird, but swapping the image for an SVG fixed it. Still, I was curious: why was it rendering like this in the first place?

如果你眯起眼睛,或者退后一步,Chrome中的那个看起来更粗。有点奇怪,但将图像换成SVG解决了问题。尽管如此,我还是很好奇:为什么它一开始会这样渲染?

I did some digging and found a nifty optimization that Chrome uses when rendering JPEGs at small scales.

我做了一些调查,发现Chrome在缩小JPEG渲染时使用了一个巧妙的优化。

Scaling down images can be wasteful

缩小图像可能很浪费

The intuitive way to render a small image from a JPEG is to fully decompress it in memory and then scale it down.

从JPEG渲染小图像的直观方法是在内存中完全解压它,然后缩小它。

But that is not always efficient.

但这并不总是高效的。

Imagine a 2000 × 2000 JPEG that needs to be displayed at 20 × 20. Once uncompressed, the image takes far more memory than the final result. A bitmap of the full image uses roughly 12 MB, while the final 20 × 20 image needs only about 1.2 KB. Most of the information in the large version is lost when scaling down.

想象一个2000×2000的JPEG需要以20×20显示。一旦解压,图像占用的内存远大于最终结果。完整图像的位图大约使用12 MB,而最终的20×20图像只需要大约1.2 KB。大版本中的大部分信息在缩小时丢失了。

What information is lost when scaling down?

缩小时丢失了什么信息?

An interesting insight is that the information lost is not random.

一个有趣的见解是,丢失的信息不是随机的。

When an image is scaled down heavily, the information that disappears is mostly the high-frequency detail. This is easy to see intuitively. Think of a tree with lots of leaves and rough bark: those fine details change quickly from pixel to pixel, so they count as high-frequency information.

当图像被大幅缩小时,消失的信息主要是高频细节。这很容易直观地看到。想象一棵有很多叶子和粗糙树皮的树:那些细微的细节在像素之间变化很快,所以它们算作高频信息。

If you scale that tree down to something tiny, like 20 × 10, you end up with just a green blob at the top for the foliage and a brown stick at the bottom for the trunk. The scaled-down version has thrown away the fine detail, the high-frequency information.

如果你把那棵树缩小到很小的尺寸,比如20×10,你最终只会得到顶部一个绿色的块状物代表树叶,底部一个棕色的棍子代表树干。缩小后的版本丢弃了细微的细节,即高频信息。

Illustration of a tree being scaled down

一棵树被缩小的插图

Some of that high-frequency information still survives to an extent, because the details get mixed together.

其中一些高频信息仍然在一定程度上幸存下来,因为细节混合在一起。

How JPEG stores image data

JPEG如何存储图像数据

I will keep this explanation light on jargon and math, but I will still mention a few technical terms that can be good starting points if you want to dig deeper. I will also skip a fair chunk of the full JPEG transformation, because it is not needed here.

我将尽量少用术语和数学,但仍会提到一些技术术语,如果你想深入了解,这些术语可以作为很好的起点。我还会跳过完整JPEG转换的相当一部分,因为这里不需要。

During JPEG compression, images are split into 8 × 8 blocks that are converted into the frequency domain. This operation is called a DCT (Discrete Cosine Transform).

在JPEG压缩过程中,图像被分割成8×8的块,并转换到频域。这个操作称为DCT(离散余弦变换)。

In an 8 × 8 block, the lowest possible frequency is a flat color. Strictly speaking, it is not really a frequency because nothing changes; it is the constant component. At the opposite end, the highest frequency looks like a checkerboard, where the value changes as much as possible. Everything in between represents the rest of the frequency domain. These are called basis functions.

在8×8的块中,最低可能的频率是纯色。严格来说,它并不是真正的频率,因为没有任何变化;它是常数分量。在另一端,最高频率看起来像棋盘格,值变化尽可能大。介于两者之间的代表频域的其余部分。这些被称为基函数。

The basis functions: you can see the flat color in the top-left, and the checkerboard in the bottom-right.

基函数:你可以在左上角看到纯色,在右下角看到棋盘格。

So converting an 8 × 8 block into the frequency domain is basically asking: how much of each pattern is present in this block? Those amounts are called coefficients.

因此,将8×8的块转换到频域基本上是在问:这个块中存在多少每种模式?这些数量称为系数。

JPEG compression has a few more steps after that to store those coefficients efficiently, and that is where the lossy compression happens. But that part is not important for what we are discussing here.

JPEG压缩在之后还有几个步骤来高效存储这些系数,这就是有损压缩发生的地方。但那部分对我们这里讨论的内容并不重要。

Putting it together: rendering a JPEG at 1/8 scale

综合起来:以1/8比例渲染JPEG

Now let’s say you want to shrink an image by a factor of 8.

现在假设你想将图像缩小8倍。

Those 8 × 8 blocks I mentioned earlier can now be represented by a single pixel in the downscaled image. At that size, the image mostly needs low-frequency information because, as in the tree example, the high-frequency details mostly disappear during scaling.

我之前提到的那些8×8块现在可以用缩小图像中的单个像素来表示。在那个尺寸下,图像主要需要低频信息,因为正如树示例中那样,高频细节在缩放过程中大多消失。

So instead of decompressing the whole JPEG, we can skip the coefficients for the high-frequency parts and use only the ones needed for the coarse version of the image. That gives a scaled-down result without fully expanding the original image first.

因此,与其解压整个JPEG,我们可以跳过高频部分的系数,只使用粗略版本图像所需的系数。这样可以在不先完全展开原始图像的情况下得到缩小后的结果。

The decoded image takes less space and is faster to uncompress, since we are skipping a good chunk of the coefficients.

解码后的图像占用更少的空间,解压速度也更快,因为我们跳过了相当一部分系数。

This can be extended to other ratios, as long as they are fractions with a denominator of 8. The technical name for this is partial IDCT scaling*. See jpegclub.org (if you read a bit on this, you will see that this technique can also be used to upscale images!).

这可以扩展到其他比例,只要它们是分母为8的分数。这项技术的专业名称是部分IDCT缩放*。参见jpegclub.org(如果你读一些相关内容,你会看到这项技术也可以用于放大图像!)。

* Inverse discrete cosine transform: taking the frequency domain back to the image domain.

* 逆离散余弦变换:将频域带回图像域。

How Chrome fits in

Chrome如何融入其中

Chrome delegates image decoding and rendering to Skia. For JPEGs, Skia uses libjpeg-turbo, which implements partial IDCT scaling. That lets it decode only the lower-frequency data when the target size is small enough.

Chrome将图像解码和渲染委托给Skia。对于JPEG,Skia使用libjpeg-turbo,它实现了部分IDCT缩放。这使得当目标尺寸足够小时,它只需解码低频数据。

In other words, Chrome/Skia does not always decompress the full image and scale it afterward. It computes the closest fraction with a denominator of 8 and decodes the image at that scale. It then scales the image further using a more traditional downsampling algorithm until it reaches the desired size.

换句话说,Chrome/Skia并不总是先解压完整图像再缩放。它会计算最接近的以8为分母的分数,并按该比例解码图像。然后,它使用更传统的下采样算法进一步缩放图像,直到达到所需尺寸。

That is why the image looked thicker on my machine. Because it was rendered so small, it was decoded at one-eighth scale using partial IDCT scaling. So the only data from the frequency representation that remained was the constant component; all the edge softening and gradients were not used.

这就是为什么在我的机器上图像看起来更粗。因为它被渲染得很小,所以使用部分IDCT缩放以八分之一的比例解码。因此,频率表示中保留的数据只有常量分量;所有边缘柔化和渐变都没有被使用。

Correction 12-08-2026 : Someone on Hacker news pointed out that the scaling algorithm used can play a significant role in how the image looks. So the resulting degradation is actually a mix of IDCT and scaling algorithm.

更正(2026年8月12日):Hacker News上有人指出,所使用的缩放算法对图像外观有显著影响。因此,最终的质量下降实际上是IDCT和缩放算法的混合结果。

Really, the moral here is that you should not use JPEG for icons and the like. The format and its optimizations are designed around our perception of photos.

实际上,这里的教训是你不应该将JPEG用于图标等。该格式及其优化是围绕我们对照片的感知而设计的。

After all, it is in the name: Joint Photographic Experts Group.

毕竟,它的名字就说明了这一点:联合图像专家组。

更进一步:量化金融体系

看懂新闻只是起点——沿量化金融路径,把它变成能交付的工程能力

进入量化体系 →

相似阅读

另一事件,读法相近