Compressive Images Revisited

#

2012 was a dark time for responsive images. Standards work had begun, but there was no consensus and many angry people (myself included). Some remained unconvinced that responsive images were even a problem that needed to be solved.

But there was still a glimmer of hope in these dark days. In July of that year, Daan Jobsis discovered a technique that the Filament Group would later dub “compressive images”. The technique became pretty popular and worked quite well in lieu of an actual standard.

Now, fast forward to today. We have a set of standards for responsive images, browsers have improved image loading—does the compressive images technique still have a place in our workflow? I’ve been threatening to write this post for a long time, but just kept putting it off. But when Dave Shea offers you a like, you take it (there…happy Dave?).

What are compressive images?

The compressive images technique relies on you sizing a JPG image to be larger than the size it ultimately is displayed at and then compressing it to an incredibly low-quality setting. This cuts the image weight dramatically, but also makes the image look absolutely terrible. However, when the browser sizes the image down to be displayed, it looks fantastic again. In fact, it even looks fantastic on high-resolution displays. Magic! (Warning: Not actually magic.)

The benefit in weight can be substantial. In the Filament Group’s article, the example image was a whopping 53% lighter (from 95kb to 44kb).

The trade-off for compressive images is primarily the memory cost (there used to be scaling and decoding risks, but browsers have improved in that area).

Let’s consider the Filament Group’s example image again. The original image is a 400px by 300px image. The compressive image is 1024px by 768px.

When a browser stores a decoded image in memory, each pixel is represented by an RGBA value which means that each pixel costs us four bytes (a byte each for the red, green, blue and alpha values). The memory footprint of decoded images is only reported by Edge and Firefox at the moment, but it can be calculated easily enough.

  1. Take the height of the image and multiply it by the width of the image to get the total number of pixels.
  2. Multiply the total number of pixels by 4.

With that in mind, let’s calculate the memory impact for both the original image and compressive image in Filament Group’s post:

  • Resized image: 400 x 300 x 4 = 480,000 bytes
  • Compressive image: 1024 x 768 x 4 = 3,145,728 bytes

For the example provided, though the weight of the compressive image is less than 50% of the original weight, the memory allocated for the compressive image is roughly 6.5 times the size needed for the original image.

Another wrinkle: The GPU

Sounds terrible, right? Thankfully a few browsers have made some improvements in how images impact memory. Microsoft Edge (and IE 11+) and Blink-based browsers (Chrome + Opera) support GPU decoding of images in certain scenarios. There’s a lot of interesting fallout, but the memory impact is pretty straightforward. Since the final decoding happens on the GPU, JPG’s can now be stored in YUV values instead of RGBA values. Now each pixel only costs us three bytes:

  • Resized image: 400 x 300 x 3 = 360,000 bytes
  • Compressive image: 1024 x 768 x 3 = 2,359,296 bytes

That reduces the memory footprint a little, but it gets even better.

Since we’re storing the values before they’re finally converted to RGBA, browsers can also take advantage of subsampling to reduce the memory footprint even more. Subsampling isn’t the easiest thing to wrap your head around, but Colin Bendell wrote a good post going into detail and you can also read about it High Performance Images.

For the context of the article, it’s enough to say that subsampling reduces the file size of the image by not necessarily including information about each and every pixel in the image. The savings don’t just apply to the file size. If a browser is storing data in YUV format, then it gets to ignore those pixel values as well based on whatever subsampling level is applied.

Any image saved at a low-enough quality to take advantage of the compressive images technique will be using 4:2:0 subsampling. As a result, the browser only has to store a couple pixel values per section of the image. The math is slightly trickier, but the basic idea is that fewer pixel samples mean fewer pixels stored in memory.

  • Resized image: 400 x 300 x 3 = 360,000 bytes
  • Compressive image: 1024 x 768 x 3 - (1024 x 768 x .75 x 2) = 1,179,648 bytes

GPU decoding reduces the memory cost quite a bit. Without GPU decoding, the memory footprint for the compressive image was around 6.5 times the size of the original image. With GPU decoding in place, the memory footprint is closer to 3.3 times the original.

What should we do instead?

By now the trade-off is pretty clear. Compressive images give us a reduced file size, but it greatly increases the memory footprint. Thanks to the standards that have been developed around responsive images, it’s a trade-off we no longer need to make.

For selectively serving images to higher resolutions, the solution could be as simple as using the srcset attribute with a density descriptor.

<img src="/images/me.jpg" alt="Image of me" srcset="/images/me-1x.jpg 1x, /images/me-2x.jpg 2x" />

The snippet above:

  • Provides an image (me.jpg) for the default. Any browser not supporting the srcset attribute will load this.
  • Provides two alternative images (me-1x.jpg and me-2x.jpg) that browsers can choose from based on the screen resolution. The density descriptors (1x and 2x) help the browser determine which is the appropriate one to use.

This allows you to serve a lower quality image for older browsers (me.jpg), something appropriately sized but slightly better quality for 1x screen resolutions (me-1x.jpg), and a large high-resolution image for double-density displays (me-2x.jpg). No one pays a higher memory cost than necessary and the network costs can still be kept mostly in check.

That’s a straightforward use-case, but even if your situation is more complex you can likely navigate it with some combination of srcset, sizes or picture. Jason Grigsby’s ten-part (yes, 10) series of posts on responsive images is still my favorite go-to for anyone who would like to read a bit more. Just don’t tell Jason I said that—it would give him a big head.

All of this isn’t to say we should never use compressive images—never is a word that rarely applies in my experience. But it does mean that we should be cautious.

If you’re considering the approach for a single image on a page, and that image’s memory footprint will be reasonable (i.e. not a massive hero image)—you can likely use the compressive images technique without much of a problem (if you go that route, check out Kornel’s post from a few years back where he mentions modifying quantization tables to get better compressive images).

More often than not, though, your best approach is probably to make use of the suite of responsive images standards we now have available to us.