Transform images to webp on the fly using Image Procesor in Episerver DXP

Transform images to webp on the fly using Image Procesor in Episerver DXP

Webp is a format which is now compatible with almost all recent browsers in the market. It is a lossless image format which allows to reduce the size of images considerably and therefore reduce the size of your sites while improving loading speeds and better user experience. You can find a more in detail explanation about the format in here

Checking the episerver foundation implementation I found that some of the images urls were using the query string parameter format=webp at the end and to be honest I had not idea what that was for. My first guess was that there was some code which was handling the query string parameter and transforming it but after some digging, I then assumed that it was a parameter from several ones that you can send to Cloudfare, the default CDN for Episerver DXP, which will return a webp image on the fly. You can still do this with Cloudfare, but the url format is different than the one used in this post (You can find more information here). However after a useful comment from Nate I realize that the plugin Image Processor for Episerver which uses behind the scenes the Image Processor module with the WebP plugin was responsible for this behavior.

So, in this blog post I am going to talk about how to do a proper implementation of it, and why to do it. So, without further due we will begin.

We must consider that even though webp format is supported by almost all browsers, other ones like Safari, before big sur update and internet explorer 11 do not support it, so it is critical to configure your images using the picture tag instead of just the image tag to be able to fall gracefully when a non-supported browser appears. The code I am going to write down is based upon the code found in the Episerver foundation cms site that you can find it here.

So first, we will add the image media data model code to handle image files, pay special attention to the extensions that the model support.

using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.DataAnnotations;
using EPiServer.Framework.Blobs;
using EPiServer.Framework.DataAnnotations;
using EPiServer.Labs.ContentManager.Cards;
using EPiServer.Labs.ContentManager.Dashboard;
using System.ComponentModel.DataAnnotations;

namespace Foundation.Features.Media
{
    [ContentType(DisplayName = "Image File",
        GUID = "20644be7-3ca1-4f84-b893-ee021b73ce6c",
        Description = "Used for image file types such as jpg, jpeg, jpe, ico, gif, bmp, png, webp")]
    [MediaDescriptor(ExtensionString = "jpg,jpeg,jpe,ico,gif,bmp,png,webp")]
    public class ImageMediaData : ImageData, IDashboardItem
    {
        [Editable(false)]
        [ImageDescriptor(Width = 256, Height = 256)]
        [Display(Name = "Large thumbnail", GroupName = SystemTabNames.Content, Order = 10)]
        public virtual Blob LargeThumbnail { get; set; }

        [Editable(false)]
        [Display(Name = "File size", GroupName = SystemTabNames.Content, Order = 20)]
        public virtual string FileSize { get; set; }

        [Display(GroupName = SystemTabNames.Content, Order = 40)]
        public virtual string Caption { get; set; }

        [CultureSpecific]
        [Display(GroupName = SystemTabNames.Content, Order = 150)]
        public virtual string Title { get; set; }

        [CultureSpecific]
        [Display(Description = "Description of the image", GroupName = SystemTabNames.Content, Order = 160)]
        public virtual string Description { get; set; }

        [CultureSpecific]
        [Display(Name = "Alternate text", GroupName = SystemTabNames.Content, Order = 170)]
        public virtual string AltText { get; set; }

        [CultureSpecific]
        [UIHint("allcontent")]
        [Display(Description = "Link to content", GroupName = SystemTabNames.Content, Order = 200)]
        public virtual ContentReference Link { get; set; }

        [CultureSpecific]
        [Display(GroupName = SystemTabNames.Content, Order = 210)]
        public virtual string Copyright { get; set; }

        public void SetItem(ItemModel itemModel)
        {
            itemModel.Description = Description;
            itemModel.Image = ContentLink;
        }
    }
}

Second, we will add the image media data view model used in the view.

namespace Foundation.Features.Media
{
    public class ImageMediaDataViewModel
    {
        public string Name { get; set; }
        public string ImageLink { get; set; }
        public string LinkToContent { get; set; }
        public string Title { get; set; }
        public string AltText { get; set; }
        public string Description { get; set; }
    }
}

Now, we will add an image display template to the project inside the default folder shared/views/displaytemplates with the following code:

@using Foundation.Features.Media
@model ContentReference

@{
    var currentContent = !ContentReference.IsNullOrEmpty(Model)
        ? Model.Get<MediaData>()
        : null;
}

@if (currentContent == null) { return; }

@if (currentContent is ImageMediaData image)
{
    var viewModel = new ImageMediaDataViewModel
    {
        Name = image.Name,
        Title = image.Title,
        AltText = image.AltText,
        Description = image.Description,
        ImageLink = Url.ContentUrl(image.ContentLink),
        LinkToContent = ContentReference.IsNullOrEmpty(image.Link) ? string.Empty : Url.ContentUrl(image.Link)
    };

    @Html.Partial("~/Features/Media/ImageMedia.cshtml", viewModel)
}

This will allow any image of type ImageMediaData to be handled by the view ImageMedia.cshtml without requiring casting the content reference to ImageMediaData and avoids using the current MediaController (current way episerver foundation cms code works for now).

Next, we have the ImageMedia.cshtml which will check if the model has a content link and if that is the case render the image wrapped inside an anchor.

@using EPiServer.Web.Mvc.Html
@using Foundation.Features.Media

@model ImageMediaDataViewModel

@if (!string.IsNullOrEmpty(Model.LinkToContent))
{
    <a href="@(string.IsNullOrEmpty(Model.LinkToContent) ? "#" : Url.ContentUrl(Model.LinkToContent))">
        @Html.Partial("~/Features/Media/_ImageRenderer.cshtml", Model)
    </a>
}
else
{
    @Html.Partial("~/Features/Media/_ImageRenderer.cshtml", Model)
}

Finally, we create a new partial view _ImageRenderer.cshtml to render the image using the picture tag, pay special attention to the loading lazy attribute added to the image tag which will allow further gains in performance by loading only images which are part of the user can see at that point in the browser and not all the images of the page.

@using EPiServer.Web.Mvc.Html
@using Foundation.Features.Media

@model ImageMediaDataViewModel

@{
    var imgLink = Url.ContentUrl(Model.ImageLink);
}

<picture>
    <source srcset="@imgLink?format=webp" type="image/webp" alt="@Model.AltText" longdesc="@Model.Description" title="@(Model.Title ?? Model.Name)">
    <img src="@imgLink" alt="@Model.AltText" longdesc="@Model.Description" title="@(Model.Title ?? Model.Name)" loading="lazy" />
</picture>

With all these in place, we can just call the property image content reference with the allowed type ImageMediaData.

        [UIHint(UIHint.Image)]
        public ContentReference ThumbnailImage { get; set; }

with the following code:

  @Html.PropertyFor(x => x.ThumbnailImage)

To see the benefits in action I added a png image with size of 10 MBs to a site.

Then I loaded the page where the image is used to see the results.

Checking the image you can see that the request is not a png anymore but a webp and the size of the image is 1 MB instead of the original size, this is a huge reduction of size which will allow your site to load faster. And that is it, if you have questions please use the comment section. I hope it helps someone and as always keep learning !!!

Written by:

Jorge Cardenas

Developer with several years of experience who is passionate about technology and how to solve problems through it.

View All Posts

6 COMMENTS

comments user
Johan

Cloudflare has a feature called Polish that automatically changes an image to webp without the need for a query parameter. For us this was enabled by default on our Episerver DXP Cloudflare.

    comments user
    Jorge Cardenas

    Thanks for your comment Johan. Yes in fact, we can use Polish to automatically transform images to webp. You can find more information here. However, I prefer to have more control about the image transformations.

    In any case, I made a mistake about this being part of cloudfare so I will modify the blog post accordingly.

comments user
Nate

This does not actually use cloudflare, but instead the ImageProcessor package added to foundation. As you can see in the documentation you linked, the parameters to transform images in cloudflare need to be embedded in the url, not in the query string. ImageProcessor provides the ‘format’ query parameter, along with others for width, height, crop, etc.

    comments user
    Jorge Cardenas

    Thank Nate for letting me know about my mistake. I will fix the post accordingly.

comments user
Erik Henningson

Hi Jorge. I don’t know If you have seen it, but there is a Picture html helper included in the ImageProcessor.Episerver package. The result is similar to your solution.
https://github.com/vnbaaij/ImageProcessor.Web.Episerver#picture-helper

    comments user
    Jorge Cardenas

    Thanks for letting me know. I was not aware of that. I will take a look.

Leave a Reply