My blog

HOWTO: Use ImageProcessor.net to create dominant colour image placeholders

HOWTO: Use ImageProcessor.net to create dominant colour image placeholders

Loading images only after the page load, using JavaScript, is nothing new and it is a great way of getting the page loading faster. You can also request images that are sized correctly for the device you’re using. This article shows you a little trick to use a block colour data-url image as a placeholder, or CSS background, and for the colour to be the dominant colour of each image. This means the page looks good even before the images load.

I need to acknowledge that none of this is my invention. Pinterest and others are already doing this and my attention was drawn to it by @JamesMSouth when he tweeted an article about it. The article is worth a read; explaining a little about quantizing, creating small data uris and other tricks. In this post I’ll keep things really simple, and focus on what a basic implementation might look like.

NB: I’m spelling colour with a U throughout this article. I realise some coding standards state otherwise but they are wrong and I’m right.

Calculating the dominant colour

Let’s jump straight in and create our method for calculating a dominant colour, even though nothing yet calls it.

/// <summary>
/// Returns the dominant colour of an image based on quantizing
/// </summary>
/// <param name="mediaItem">The image that which wish to grab the colour for</param>
/// <returns>A hex representation (e.g. #f1b1c4) of the dominant colour</returns>
public static string GetDominantColour(IMedia mediaItem)
{
    //grab the value of umbracoFile
    var umbracoFile = mediaItem.GetValue<string>(Constants.Conventions.Media.File);

    //Deserialize the image cropper JSON to grab the original source file
    //NB: Assumes umbracoFile is an image cropper. The alternative otherwise would be:
    //var imageScr = umbracoFile
    var imageSrc = JsonConvert.DeserializeObject<ImageCropDataSet>(umbracoFile).Src;

    //Get the local file path to the image (e.g. C:\inetpub\wwwroot\media\1234.jpg)
    var imagePath = HostingEnvironment.MapPath(imageSrc);

    Image image;
    //resize the image to something smallish (250x250)
    //to avoid a delay in the quantization process
    using (var imageFactory = new ImageFactory())
    {
        var layer = new ResizeLayer(new Size(250, 250), ResizeMode.Max)
        {
            Upscale = false
        };

        using (var memoryStream = new MemoryStream())
        {
            //ImageFactory outputs on a stream
            imageFactory.Load(imagePath)
                        .Resize(layer)
                        .Save(memoryStream);

            memoryStream.Position = 0;

            //The stream is used to build an Image
            image = Image.FromStream(memoryStream);
        }
    }

    //Get a quanitized version of the image, which has 1 colour of 8 bits
    var quantizedImage = new OctreeQuantizer(1, 8).Quantize(image);
    //Grab the 1 (dominant) colour
    var dominantColour = quantizedImage.Palette.Entries[0];

    //Return the colour, represented as a hex format
    return "#" + 
            dominantColour.R.ToString("X2") + 
            dominantColour.G.ToString("X2") + 
            dominantColour.B.ToString("X2");
}

You can see that GetDominantColour doesn’t actually do a great deal. Firstly it uses ImageProcessor to get a small version of the image - this is done to reduce the time taken by the following processes. The next step is to call the quantizer which does all the calculations to return you an image with just the dominant colour in it. This colour we then return as a hex value (e.g. #112AFF). So we now have a colour that we can use instead of our image.

Storing the dominant colour

I took the approach that the dominant colour needs to be calculated on the save event of a Media item, and that this only need to apply to the in-built Image media type. So that we have somewhere to store our colour I created a new property on the media type with the alias dominantColour using the pre-packaged data type Label (No Edit).

Now we need to call out GetDominantColour method, when the Image is saved, and update the dominantColour property. This can be achieved simply as follows:

public class UmbracoApplicationEventHandler : ApplicationEventHandler
{
    protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
    {
        MediaService.Saved += MediaServiceSaved;
    }

    private static void MediaServiceSaved(IMediaService sender, SaveEventArgs<IMedia> e)
    {
        foreach (var mediaItem in e.SavedEntities
                .Where(mediaItem => mediaItem.ContentType.Alias.Equals("Image", StringComparison.InvariantCultureIgnoreCase)))
        {
            try
            {
                //Get the dominant colour of the image as a hex value
                var hexColour = ImageHelper.GetDominantColour(mediaItem);
                //set the hex colour on our new property
                mediaItem.SetValue("dominantColour", hexColour);

                //Re-save the media item, not raising events to avoid a reciprical call
                sender.Save(mediaItem, raiseEvents: false);
            }
            catch (Exception exception)
            {
                LogHelper.Error<UmbracoApplicationEventHandler>(
                        "Unable to save dominant colour for media item " + mediaItem.Id, exception);
            }
        }
    }
}

If you’re not familiar with ApplicationEventHandler then check it out in the documentation. So now you’ll see in the backoffice that whenever you create a new image, or overwrite the file, your dominantColour property gets recalcaluated. I think this is pretty cool, but it’s quite useless without some implementation.

Using the dominant colour

I don’t wish to cover the lazy image loading in any detail partly because I’m no authority on it and also because it is not the focus of this post. However we need to see the fruits of our labour, so I’m going to create a Content Type (with a Template) called MyPage, with two properties called image1 and image2 which are both media pickers. I’ll then create a new piece of content at the root of my site, and pick an image for each of my properties. To make this easier for you can download the source code of this demo (the back office username is admin, and the password is password, and make sure you restore nuget packaged). On our template I’ll render our image both as a data-url image. We obviously don’t want to supply the url of the image as the src or as inline CSS, but we will add them as data-* attributes.

@{
    var image1Id = Model.Content.GetPropertyValue<int>("image1");
    var image1 = Umbraco.TypedMedia(image1Id);
    var dominantColour1 = image1.GetPropertyValue<string>("dominantColour");
}
<img src="@ImageHelper.HexToDataUri(dominantColour1)"
    width="@(image1.GetPropertyValue<int>("umbracoWidth"))"
    height="@(image1.GetPropertyValue<int>("umbracoHeight"))"
    data-load-image="@image1.Url" alt="@image1.Name" />

You will have noted the new ImageHelper method HexToDataUri. The code for this is:

/// <summary>
/// Return a colour as a data uri
/// </summary>
/// <param name="colour">The colour that we wish to a data uri for, in hex format</param>
/// <returns>The colour as a data uri</returns>
public static string HexToDataUri(string hex)
{
    //Convert from the hex string in to a Color object
    var converter = new ColorConverter();
    var colour = (Color)converter.ConvertFromString(hex);

    //As per https://manu.ninja/dominant-colors-for-lazy-loading-images
    //We can lose a lot of the standard data uri values to create a shorter string
    byte[] gif = {
        0x47, 0x49, 0x46, 0x38, 0x39, 0x61, // Header
        0x01, 0x00, 0x01, 0x00, 0x80, 0x01, 0x00, // Logical Screen Descriptor
        colour.R, colour.G, colour.B, 0x00, 0x00, 0x00, // Global Color Table
        0x2C, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, // Image Descriptor
        0x02, 0x02, 0x44, 0x01, 0x00 // Image Data
        };

    return string.Format("data:image/gif;base64,{0}", Convert.ToBase64String(gif));
}

If we load our page now we will see:

<img src="data:image/gif;base64,R0lGODlhAQABAIABAPagbAAAACwAAAAAAQABAAACAkQBAA==" width="400" height="400" data-load-image="/media/1002/logo_400x400.gif" alt="logo_400x400.gif">

of course you can also use the hex to set the background via CSS for divs. So your users will so something hopefully prettier than this:

Before (on page load)

Colours instead of images

Finally we need some JavaScript that will fire after the page has loaded to replace the placeholders with our images. A basic JQuery implementation is as simple as:

(function ($) {
    var loadImages = function () {
        alert("Let's now go and replace the placeholder");

        //Go through each image with our data-* attribute
        $("img[data-load-image]").each(function () {
            var img = $(this);
            //Set the src equal to this data-* attribute
            img.attr("src", img.data("loadImage"));
        });
    }

    $(function () {
        loadImages();
    });
})(window.jQuery);

I’ve added a delay to the image loading purely for demonstration as otherwise with such a small page the images may load before you’re able to your dominant colour.

After (images loaded)

Images replace colours

Conclusion

To call this client-side implementation basic doesn’t go far enough. My recent implementation loads images at the required size (responsive images), loads them one at a time (chaining) so that you get images appearing sooner, and only loads images shortly before they fall in to the viewable area of the page (lazy loading). Checkout Slimsy for starters, although I prefer to roll my own using vanilla JavaScript, URI.js and JQuery Deferred. Let me know if you’d like more information about this in the comments.

Finally a hats off to all the aforementioned people and the Umbraco team. I think it is remarkable that we can achieve all of this with so little code.

Share this post
comments powered by Disqus