logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·31.10.26
← Back to news

Why Tiny JPEGs Look Different in Chrome

Original article
guillaumetech.github.io|157 points|28 comments|by gutechh|Aug 12, 2026

The Mystery of the "Thick" Tiny JPEGs in Chrome

By Guillaume Técher | August 3, 2026

What initially seemed like a rendering glitch in Google Chrome turned out to be a sophisticated optimization technique for decoding JPEGs.

An Unexpected Visual Discrepancy

While looking at a colleague's screen, I noticed a logo didn't quite match the version on my own machine. On their screen, the image appeared thinner and more accurate to the source file.

To investigate, I created a reproduction image. When you squint or view it from a distance, the Chrome-rendered version looks noticeably thicker.

I thought it was a bug. I wondered: Why is Chrome rendering this differently?

The answer lies in how Chrome handles JPEGs when they are displayed at very small scales.


The Inefficiency of Standard Scaling

The "obvious" way to render a small version of a JPEG is to fully decompress the image into memory and then shrink it. However, this is incredibly wasteful.

Consider a scenario where a large image is shrunk to a tiny icon:

MetricFull ResolutionTarget Scale
Dimensions2000×20002000 \times 20002000×2000 px20×2020 \times 2020×20 px
Memory Usage≈12 MB\approx 12 \text{ MB}≈12 MB≈1.2 KB\approx 1.2 \text{ KB}≈1.2 KB

Most of the data in the 12 MB12 \text{ MB}12 MB bitmap is discarded during the downscaling process. Crucially, the data being lost isn't random; it is primarily high-frequency information.

Understanding Frequency in Images

Imagine a detailed photo of a tree:

  • High-Frequency: The jagged edges of bark and individual leaves (rapid pixel changes).
  • Low-Frequency: The general shape of the canopy or the trunk (slow pixel changes).

When you scale that tree down to 20×1020 \times 1020×10 pixels, the leaves vanish, leaving only a green blob and a brown line. The high-frequency details are effectively erased.

Illustration of a tree being scaled down from high detail to a low-res blob


A Primer on JPEG Storage

To understand the optimization, we need a basic grasp of how JPEGs work. I'll avoid heavy math, but here are the core concepts:

  1. Block Splitting: The image is divided into 8×88 \times 88×8 pixel blocks.
  2. Frequency Conversion: Each block is converted into the frequency domain.
    • Low Frequency: A flat, solid color (the constant component).
    • High Frequency: A checkerboard pattern (maximum change).

The process of JPEG compression essentially asks: "How much of each of these patterns exists in this 8×88 \times 88×8 block?"

The JPEG Pipeline:

  • Divide image into 8×88 \times 88×8 blocks.
  • Convert to frequency domain.
  • Quantize coefficients (this is where the lossy part happens).
  • Store coefficients efficiently.

The "1/8 Scale" Shortcut

If you need to shrink an image by a factor of 8, an entire 8×88 \times 88×8 block can be represented by a single pixel.

Since high-frequency details disappear during heavy downscaling, Chrome can simply ignore the high-frequency coefficients and only decode the low-frequency data.

This approach is significantly faster and consumes far less memory because the browser skips a huge portion of the decompression work. This works for any scale that is a fraction with a denominator of 8 (for more, see jpegclub.org).

Technical Note: This process involves the Inverse Discrete Cosine Transform (IDCT), which moves data from the frequency domain back into the image domain.


How Chrome Implements This

Chrome doesn't handle this alone; it delegates the work to Skia, which in turn uses libjpeg-turbo.

The logic follows this pseudo-code flow:

if (target_size < threshold) {
    float scale = find_closest_fraction_of_8(target_size);
    decode_partial_idct(scale); // Skip high-frequency coefficients
    apply_final_downsample(target_size);
} else {
    decode_full_image();
    scale_image(target_size);
}

In the case of the "thick" logo, the image was so small that Chrome decoded it at 1/81/81/8 scale. Only the constant component (the lowest frequency) remained, while the subtle gradients and edge-softening (higher frequencies) were discarded.

The Final Takeaway

Moral of the story: Do not use JPEG for icons, logos, or simple graphics.

The JPEG format—and the optimizations Chrome uses—is specifically engineered for photographs. As the name implies (Joint Photographic Experts Group), it is designed for photos, not precise vector-like imagery.