Although JPEG is an image format that has almost 30 years now, it is still shining. One interesting feature about the format, which not known to many, is the possiblity to make the loading of images progressive. This means that the rendering (or decoding) of the image is done gradually by going through scans where each scan contains a certain amount of image data. As more bytes come in, the decoder defines if there are enough bytes to go to the next scan. Unlike traditional or baseline JPEG where the image is loaded chunk by chunk, a progressive JPEG is rendered as a full image with an incomplete resolution. From a viewer’s perspective, a blurry-like full image will appear first which would get clearer and clearer as more bytes come in. Illustration below:

Guess which one is the progressive ? Both images have been compressed with MozJPEG and both have roughly the same size. However, as you can see the browser deals with the images differently. The left one, which is the baseline, is loaded chunck by chunck where each chunk has the full image data or resolution. With the progressive version, the image gets displayed fully as soon as possible but with less pixels. This produces a kind of blurry effect on the image. As more data arrives, the resolution keeps on improving and the image keeps on getting clearer until the original resolution is reached.

With the nowadays high bandwidth connections, the difference could be sometimes barely noticable by the naked eye. However, if you are taking into consideration low latency networks and slow connections, progressive JPEGs can provide a better UX. Using progressive JPEGS can also make a developers’ life easier since it frees them from creating things like placeholders (e.g using BlurHash) that are meant to be displayed while the image is still loading.

How can I create progressive JPEGs?

a progressive JPEG can be created using many of the known image editors like GIMP or Photoshop.

If you want get low level or automate the process, you can use directly an image encoder/decoder. The most used ones are MozJPEG and libjpeg-turbo. Both librairies provide binaries that can be used directly from the CLI and APIs in case you need to call them programmatically (Both have a common root. In fact, MozJPEG is a fork of libjpeg).

Using MozJpeg:

cjpeg -progressive -outfile image_progressive.jpeg image.jpeg

the -progressive flag is active by default and shown here just for illustration purposes.

The command is similar using libturbo-jpeg except that the image needs to be decompressed first into another format and then compressed again:

djpeg -bmp image.jpeg | cjpeg -progressive -outfile image_progressive.jpeg

Finally, you can also use the imager200 compress endpoint if you don’t want to install any tool:

curl -H "X-Imager-Key: $API_KEY" --data-binary @image.jpeg -O progressive_compressed_image.jpeg "https://api.imager200.io/compress/sync?progressiveJpeg=true&jpegQuality=89"

Is it possible to customize the progressive loading?

The short answer is yes, and this is why JPEG is an amazing image format. It provides not only the possibility of progressive decoding, but also the possibilty to script how many scans can be and how much details a scan can have. This is considered as an advanced feature and require some knowledge about image theory and how images are encoded, but one can always try for fun. Besides MozJPEG and libjpeg, there are no tools, that we know of, that allow to customize directly the progessive loading scans. The script file can be provided through the -scans flag for both the tools. We will talk about the script file in more details in a future blog post so stay tuned! In the meanwhile, you can find more details in the libjpeg docs here. To conclude, let’s compare between the progressive loading for the default scan script used by both MozJPEG and libjpeg which is comprised of 10 scans and a custom scan script (source: cloudinary) comprised of 5 scans:

Custom Script

0 1 2: 0 0 0 0;

0: 1 9 0 0;

2: 1 63 0 0 ;

1: 1 63 0 0 ;

0: 10 63 0 0;

Default Script

0,1,2: 0-0, 0, 1 ;

0: 1-5, 0, 2 ;

2: 1-63, 0, 1 ;

1: 1-63, 0, 1 ;

0: 6-63, 0, 2 ;

0: 1-63, 2, 1 ;

0,1,2: 0-0, 1, 0 ;

2: 1-63, 1, 0 ;

1: 1-63, 1, 0 ;

0: 1-63, 1, 0 ;

Did you notice the difference ?