All Your Images Are Belong to Umbraco

Umbraco - Zero Wing Edition.
Umbraco - Zero Wing Edition.

Today I am going to talk to you about a tool that has been shipped within the Umbraco core since v7.1. It powers the Image Cropper property editor but can do much much more to help you build high quality, performant websites. That tool is called ImageProcessor.

ImageProcessor is actually two libraries: ImageProcessor - A library for desktop and web that provides a fluent API allowing you to easily chain methods to deliver the desired output, and ImageProcessor.Web - A web extension to ImageProcessor that allows the developer to perform image manipulation using a Url API of querystring parameters as instructions.

The Ever Increasing Web

Web pages are getting bigger. According to the HTTP Archive in the last year the average web page has gone up by 252kb to a whopping 1953kb with 213kb of that due to images. I don't know about you but I find that to be a worrying trend, especially in this age of responsive websites and increased mobile browser usage.

December 15th 2013 December 1st 2014
At 1030kb Image weight is already high.
At 1030kb Image weight is already high.
1243kb Eeek!
1243kb Eeek!

Thankfully with Umbraco we have all the tools available to us to keep our webpages snappy. 

Image Cropper

If you are using Umbraco 7 and you don't use the Image Cropper property editor then you are sorely missing out; it's pretty sweet. The Image Cropper utilizes ImageProcessor.Web's Url API to generate crops of your images that are then cached in a super awesome auto cleaning diskcache mechanism that will serve the cropped image on subsequent requests.

It's possible to alter the generated url further to improve performance like this. Any of the ImageProcessor commands can be appended to the URL.

<img src='@CurrentPage.GetCropUrl("image", "banner")&quality=70' />

Dropping the quality slightly can dramatically decrease filesize without affecting the appearance.

Slimsy

This is a rather neat little package written by Jeavon Leopold that when used in conjunction with ImageProcessor.Web and the built-in Umbraco Image Cropper will make your websites images respond to the viewport width and also the pixel density if supported by the client browser. It does this by providing extension methods and performing calculations on-the-fly via JavaScript to provide the correct instructions to the Url API. 

It's very flexible and I highly recommend you have a look at it.

@foreach (var feature in homePage.umbTextPages.Where("featuredPage"))
{
    <div class="3u">
        <!-- Feature -->
        <section class="is-feature">
            @if (feature.HasValue("Image"))
            {
                var featureImage = Umbraco.Media(feature.Image);
                <a href="@feature.Url" class="image image-full">
                <img src="@featureImage.GetResponsiveImageUrl(270, 161)" alt="" />
                </a>
            }
            <h3><a href="@feature.Url">@feature.Name</a></h3>
            @Umbraco.Truncate(feature.BodyText, 100)
        </section>
        <!-- /Feature -->
    </div>
}

An initial image size of 270 x 161. This example is looping pages, each page has a media picker with property alias "Image"

Presets

ImageProcessor.Web has the ability to translate preset instructions passed to the Url API. (I find this very useful when generating images for specific items in widget based setups where you don't care so much about art direction). Presets allow you to configure the image output in a single location making it easy to tweak output for improved performance.

To set up presets you need the correct configuration files installed in your website. There's a nuget package available - ImageProcessor.Web.Config which allows you to install the files but there is also extensive documentation online so you can set it up yourself.

Here's an example of a signpost.

<div class="signpost">
    <!-- Signpost-->
    @if (signpost.HasValue("Image"))
    {
    var signpostImage = Umbraco.Media(signpost.Image);
    <a href="@signpost.Url">
        <img src="@signpostImage.Url?preset=signpost" alt="@signpostImage.Url" />
    </a>
    }
    <h3><a href="@signpost.Url">@signpost.Name</a></h3>
    <!-- /Signpost -->
</div>

A single signpost item with preset applied.

With the relevant configuration.

<?xml version="1.0" encoding="utf-8" ?>
  <processing preserveExifMetaData="false">
    <!-- Presets that allow you to reduce code in your markup. 
         Note the use of &#038; to escape ampersands. -->
    <presets>
      <preset name="signpost" value="width=768&#038;height=480&#038;mode=crop"/>
    </presets>
    <!-- List of plugins. -->
    <plugins autoloadplugins="true">

Sample configuration trimmed for brevity.

Pre Processing

All the previous shown methods involve postprocessing the uploaded image on first instance of the page loading. This works perfectly 99% of the time but chances are there will be a client somewhere that will upload a huge image that will cause a performance hit as ImageProcessor processes and saves it for subsequent viewing.

Fortunately there is a way to deal with this.

Umbraco provides various events that you can tap into when saving media and you can use ImageProcessor's fluent API within these events to resize any images when they are saved to the media section. Here's the relevant code to demonstrate how to do this.

public class ApplicationEvents : ApplicationEventHandler
{
    protected override void ApplicationStarting(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
    {
        // Tap into the Saving event
        MediaService.Saving += (sender, args) =>
        {
            MediaFileSystem mediaFileSystem = FileSystemProviderManager.Current.GetFileSystemProvider<MediaFileSystem>();
            IContentSection contentSection = UmbracoConfig.For.UmbracoSettings().Content;
            IEnumerable<string> supportedTypes = contentSection.ImageFileTypes.ToList();

            foreach (IMedia media in args.SavedEntities)
            {
                if (media.HasProperty("umbracoFile"))
                {
                    // Make sure it's an image.
                    string path = media.GetValue<string>("umbracoFile");
                    string extension = Path.GetExtension(path).Substring(1);
                    if (supportedTypes.InvariantContains(extension))
                    {
                        // Resize the image to 1920px wide, height is driven by the
                        // aspect ratio of the image.
                        string fullPath = mediaFileSystem.GetFullPath(path);
                        using (ImageFactory imageFactory = new ImageFactory(true))
                        {
                            ResizeLayer layer = new ResizeLayer(new Size(1920, 0), ResizeMode.Max)
                            {
                                Upscale = false
                            };

                            imageFactory.Load(fullPath)
                                        .Resize(layer)
                                        .Save(fullPath);
                        }
                    }
                }
            }
        };
    }
}

Using Umbraco events to preprocess images.

Onwards and Downwards

Hopefully this is enough to get you all started making the most of ImageProcessor within Umbraco. If you need any more information feel free to read the documentation at ImageProcessor.org or hit me up on the forums.

Together we can all do our bit to ensure next year the charts show a swing in the other direction.

James Jackson-South

James is on Twitter as