Thumbnails

Musium generates a cached thumbnail per album, extracted from the embedded cover art. The smaller thumbnails speed up the web-based library browser substantially.

Initially, Musium used Imagemagick both for downsizing cover art, and for encoding the thumnails as jpeg. However, better compressors exist, and for some types of album art, in particular graphics with solid red areas, the thumbnails generated by Imagemagick showed pretty bad artefacts. This document compares compressors.

Encoders considered (2019)

The following encoders were compared:

  • Imagemagick 7.0.9-10 Q16
  • Mozjpeg 3.3.1
  • Guetzli 1.0.1

These were used to encode 1216 downsized cover art images. They were downsized from their native size to 140 × 140 pixels with Imagemagick through

convert infile
    -colorspace LAB
    -filter Cosine
    -distort Resize 140x140!
    -colorspace sRGB
    -strip
    outfile.png

Size

The cumulative size of all 1216 compressed thumbnails for various encoders:

Size (bytes)EncoderQuality
10,771,743Guetzli95
11,926,551Guetzli96
12,016,780Mozjpeg92
12,889,658Imagemagick95
13,049,795Mozjpeg93
13,605,461Guetzli97
14,157,834Mozjpeg94
14,254,799Imagemagick96
14,988,104Mozjpeg95
19,531,105Mozjpeg97
20,254,930Guetzli99

The quality is the quality level passed to the encoder.

The goal of this investigation was to find the most appropriate replacement for Imagemagick at quality 95 (which was used initially), but it is not obvious how to make a fair comparison when the sizes differ so greatly.

Because the default of Imagemagick at quality 95 was a bit arbitrary, we can instead compare the 3 options that are closest together in terms of size, for a fairer comparison.

  • Guetzli 96, Mozjpeg 92, Imagemagick 95, with a gap of 963,107 bytes.
  • Imagemagick 95, Mozjpeg 93, Guetzli 97, with a gap of 715,803 bytes.
  • Guetzli 97, Mozjpeg 94, Imagemagick 96, with a gap of 649,338 bytes.

This means we will involve Imagemagick at quality 96 instead of 95, so the artefacts are not as bad, but they are still fairly apparent.

Comparison

Below is a comparison of the thumbnails where artefacts were most apparent in the images encoded by Imagemagick. This is not a general comparison of the encoders; the focus is specifically on images where Imagemagick performed badly. One thing that most of these images have in common, is that they contain solid areas of bright red with sharp edges.

Guetzli 97Mozjpeg 95Imagemagick 96
guetzli-q97mozjpeg-q95imagemagick-q96
guetzli-q97mozjpeg-q95imagemagick-q96
guetzli-q97mozjpeg-q95imagemagick-q96
guetzli-q97mozjpeg-q95imagemagick-q96
guetzli-q97mozjpeg-q95imagemagick-q96
guetzli-q97mozjpeg-q95imagemagick-q96
guetzli-q97mozjpeg-q95imagemagick-q96
guetzli-q97mozjpeg-q95imagemagick-q96
guetzli-q97mozjpeg-q95imagemagick-q96
guetzli-q97mozjpeg-q95imagemagick-q96
guetzli-q97mozjpeg-q95imagemagick-q96
guetzli-q97mozjpeg-q95imagemagick-q96
guetzli-q97mozjpeg-q95imagemagick-q96
guetzli-q97mozjpeg-q95imagemagick-q96
guetzli-q97mozjpeg-q95imagemagick-q96
guetzli-q97mozjpeg-q95imagemagick-q96

A few things stand out visually:

  • Both Guetzli and Mozjpeg perform much better than Imagemagick.
  • Where Imagemagick creates blurry edges, Mozjpeg creates sharp edges, but at the cost of ringing artefacts. Guetzli produces sharp edges without much ringing.

Throughput

This comparison does not include timing information, but while running the various compressors, it was clear that Guetzli is much slower than either Imagemagick or Mozjpeg. For Musium this is not a big problem, because generating thumbnails tends to be IO-bound when the files are one a a spinning disk. As long as Guetzli is faster than the disk, it is fast enough.

Conclusion

We should compress thumbnails with Guetzli to minimize visible artefacts.

Cjpegli (2024)

JPEG XL exists nowadays, and it includes an improved encoder for traditional jpeg, cjpegli. It’s a lot faster than guetzli, should we use it?

First, we need to find the right quality level. For this I sampled 369 album covers at random from my library. I then performed binary search (with linear interpolation) to find an adequate Butteraugli distance.

CompressorQualityTotal bytesAvg. bytesSize ratioTotal time (secs)
guetzliq=974,267,77111,565100.0%166.88
cjpeglid=1.0002,782,8427,54165.2%102.78
cjpeglid=0.5004,059,08211,00095.1%103.70
cjpeglid=0.4704,195,24111,36998.3%102.51
cjpeglid=0.4594,228,26211,45899.1%102.60
cjpeglid=0.2505,601,18715,179131.2%105.60

Here we see the total size of the 369 thumbnails as well as the average size per thumbnail, and the ratio to the Guetzli target. The total time is only a single measurement with no attempt to reduce variance, and it includes both resizing with ImageMagick as well as encoding. In the case of cjpegli, the time is dominated by resizing. Even without a proper benchmark it is clear that cjpegli is faster. Cjpegli is configured with --progressive=1, one fewer scan than the default level.

After picking --distance=0.459 as the quality level, I re-encoded 1839 album covers with cjpegli. The average thumbnail size went from 11,583 bytes to 11,438 bytes, so quite close to the target.

Unfortunately, quality of the cjpegli thumbnails was noticeably worse for some thumbnails, especially in the reds again:

Guetzli 97Cjpeg 0.459
guetzli-q97cjpegli-d0.459
guetzli-q97cjpegli-d0.459

For the first thumbnail, cjpegli creates visible blocking artifacts in the background gradient, as well as ringing artifacts aroung the lower left edge of the ellipse. Guetzli creates smoother gradients and tighter edges. This is no surprise in this case, as the Guetzli image is 7,161 bytes whereas the cjpegli one is only 5,762 bytes. For the second thumbnail, Guetzli preserves more detail in the dark top-left corner. Again, the better quality is not entirely a surprise, as Guetzli’s output is 22% larger in this case.

To confirm, we can look at the size differences for a given thumbnail. We can order the thumbnails by the size ratio of cjpegli to guetzli. Below are the top 8 and bottom 8 thumbnails, by size ratio. The quality settings are the same as before (97 for Guetzli, distance 0.459 for cjpegli).

Guetzli (smaller)Cjpegli (larger)Guetzli (larger)Cjpegli (smaller)
guetzlicjpegliguetzlicjpegli
guetzlicjpegliguetzlicjpegli
guetzlicjpegliguetzlicjpegli
guetzlicjpegliguetzlicjpegli
guetzlicjpegliguetzlicjpegli
guetzlicjpegliguetzlicjpegli
guetzlicjpegliguetzlicjpegli
guetzlicjpegliguetzlicjpegli

The differences are subtle, but more visible to the human eye for the cases where Guetzli produces the larger image.

Perhaps the --progressive_level=1 has a large impact? We don’t need progressive images for the thumbnails, they get loaded before they scroll into view anyway. So let’s recalibrate with progressive scan disabled, maybe we can squeeze out some more quality.

CompressorQualityTotal bytesAvg. bytesSize ratio
guetzliq=974,267,77111,565100.0%
cjpeglid=0.4604,254,46211,52999.7%
cjpeglid=0.4574,269,84611,571100.0%
cjpeglid=0.4304,376,41911,860102.5%

It turns out that at this level of detail, distance does not relate monotonically to output size. Either way, progressive or not does not seem to affect size meaningfully.

Okay, with the difference being so difficult to observe, let’s go with the faster compressor and use cjpegli after all.