Progressive Image Loading - Blurry Placeholder Image like Unsplash

Progressive Image Loading - Blurry Placeholder Image like Unsplash

Unsplash is my favorite image site, not only for it’s huge amount of free images but also for it’s great user-experience. It has already been quite a while since Unsplash started using BlurHash to delivery progressive Image Loading experience.

BlurHash added to photo objects

The Concept

Progressive Image Loading

Progressive Image Loading makes better user experience. But what is Progressive Image Loading?

Instead of waiting for the final image to be rendered, use a low-quality image as placeholder (LQIP) first, swith to the original one when it’s loaded.

What is going on?

  1. Render an element (div for example) with specific aspect-ratio, usually the same as the original image
  2. Render a low-quality image first
  3. When this element is in viewport, triggers loading the original image
  4. After the original image is loaded, renders it upon the low-quality image and then hide/remove the low-quality image on the background

Here’s an example:

Placeholder vs. the Original

⬅️ This 8 × 6 px image can be scaled up many times (865 × 649 px on my screen for example) without increasing noticable noises.

What does Unsplash do?

  1. Fetch list data using XHR
  2. Decode BlurHash from blur_hash field of a list item
  3. Generate a 8 x 8 px image/bmp image from canvas drew with decoded BlurHash data, rendered as image placeholder
  4. Render the original image when it’s loaded

Why BMP?

See Size Comparison.

Also, render a small-sized image comsumes less CPU and Memory usage than draw a canvas do.

How to Generate Blurry Image?

Photoshop is an great tool to handle filter effects (such as Gaussian Blur) but that would be too complicated for most users.

Photoshop

So I created a web app (Image Blurrer) to generate blurry placeholder image. 4 types of output image supported:

  1. StackBlur
  2. Gaussian Blur
  3. BlurHash
  4. CSS Gradient

Comparison

Image Quality

With my Image Blurrer, blur-radius can be customized when generating blurry image using StackBlur. In my personal usage and test, StackBlur is my favorite, due to it’s flexible blur-radius.

Cavas Width Blur Radius
32 px 2 px
StackBlur
Gaussian Blur
BlurHash
CSS Gradient

BlurHash in most cases is a fine option (can be proved from the experience of Unsplash), however, in some cases it renders the dark area of the original image just too dark.

Example:

Original BlurHash
https://r2-assets.thelynan.com/uPic/P1010249.jpg UcF~mn?b00D$~q-:IUM{?c%3M{RjWBWBt7xu

Image Size

Test Image: https://r2-assets.thelynan.com/uPic/progress-image-loading-test-img.jpg

I’ve hilighted the smallest size in my tet result. Over all in my test, image/bmp is the smallest when canvas width is below 10px, and then between 12px - 22px, image/png takes over, after that image/jpeg is the smallest.

width(px) jpeg png bmp
4 816 B 132 B 90 B
6 828 B 168 B 135 B
8 825 B 222 B 198 B
10 846 B 279 B 279 B
12 867 B 366 B 378 B
14 885 B 432 B 495 B
16 882 B 546 B 630 B
18 936 B 618 B 783 B
20 954 B 768 B 954 B
22 942 B 855 B 1.1 KB
24 1.0 KB 1.0 KB 1.3 KB
26 1.0 KB 1.1 KB 1.5 KB
28 1.1 KB 1.3 KB 1.8 KB
30 1.1 KB 1.4 KB 2.0 KB
32 1.1 KB 1.6 KB 2.3 KB

Actually, most browsers do not support generating image/bmp directly by canvas.toDataURL, image/png format will be used if the specified type is not supported.

Solutions:

  1. canvs-to-bmp
  2. jimp My choice, for I have already use it to generate CSS gradient, also in my practice, jimp generate smaller-sized bmp image file.
jimp getBase64Async
1
2
3
4
const img = await Jimp.read("./path/to/image.jpg");
img.getBase64Async("image/bmp").then((res) => {
resolve(res);
});

Thanks to

  1. StackBlur
  2. Gaussian Blur
  3. BlurHash
  4. CSS Gradient
  5. jimp

Progressive Image Loading - Blurry Placeholder Image like Unsplash

https://thelynan.com/progressive-image-loading/

Author

Lynan

Posted on

2024-02-15

Licensed under

Comments