Episerver and ImageProcessor: more choice for developers and designers
When working with images in Episerver from a developer/designer perspective, the go-to solution seems to be to install ImageResizer and the accompanying ImageResizer plugin package (package, source) from Valdis (which are both excellent). This does however come with some drawbacks:
- If you want to use a disk based cache for your resized images (and you REALLY should on a production environment), you need to buy a commercial license for ImageResizer.
If you don't have a license and still use the DiskCache plugin, you'll see a red dot in the lower right corner of every processed image.
- You might need to buy commercial licenses for extra modules/plugins.
With this blogpost I would like to introduce an alternative solution. You may have heard of the (brilliant) ImageProcessor package by the all round nice guy James Jackson-South. It is used in many, many projects, websites and software packages, is completely open source, is very configurable and does not require paid-for licenses. To make it work seamlessly with Episerver requires some code though. The package I have built and introduce here contains those changes and additions. It is called ImageProcessor.Web.Episerver (because it is technically an ImageProcessor extension) and will be is available from the Episerver NuGet feed shortly (after approval).
Let me first sum up what is in the package and then explain in more detail:
- It has code so ImageProcessor can find and process images from blob storage
- It has a disk cache which stores the processed images alongside the originals in the blob storage
- It comes with a strongly typed Fluent API for all ImageProcessor.Web image processing methods
Let ImageProcessor read from blob storage
Episerver uses blob storage to store media items like images and documents. ImageProcessor needs some help to understand where to get the files from. Specifically, for that goal ImageProcessor comes with an IImageService. From the docs: “The IImageService defines methods and properties which allow developers to extend ImageProcessor to retrieve images from alternate locations to process.” An implementation of this interface for Episerver is quite straightforward and has already been written (see for example here and here). I added a combination of these in this package. I also added code to make the processing work from within the editing experience. Just like with the solution from Valdis, this might not always play well with unpublished images.
Caching
ImageProcessor uses a disk based caching mechanism that creates a file structure in /App_Data/cache by default (the location it stores the files is configurable). Using it this way is certainly possible, but then you end up with two separate structures of files. I thought it would be nicer (not necessarily better) to store the processed and cached files alongside the original blobs. This package contains code which does just that. This is possible because ImageProcessor exposes an IImageCache interface. From the docs: “The IImageCache defines methods and properties which allow developers to extend ImageProcessor to persist cached images in alternate locations.” It retains all the good stuff that the default ImageProcessor implementation already has, like automatic cleanup and store cache outside of www root. Yes, it can also be an UNC path. To change the path, follow the regular steps to do this in Episerver (in <episerver.framework> section change <appData basePath>).
Fluent API
ImageProcessor, like the name implies, does a lot more than just resizing images. See the ImageProcessingModule documentation for all available out of the box methods that you can use. It works as an HttpModule that processes commands added to the query string of an image in your views. Adding to the query string is however not very developer friendly. You don't get IntelliSense and there is no compile time checking of the syntax. Inspired by the fluent api in Valdis’s package, I decided to add this to my package as well. I added a complete, strongly typed, fluent implementation for all default ImageProcessingModule methods and provided a couple of extra ones as shortcuts. This means that you can now do things like this in your views:
@Html.ProcessImage(Model.Image).Resize(375,null).ReplaceColor("fff", "f00",99).Watermark("Episerver",new Point(100,100),"fff")
Note that the Resize function has parameters that are only used in specific combinations. I created a couple of overloads for this. Using the mentioned shortcuts, instead of .Resize you could also use .Width.
Don't forget to add the using stament to your view:
@using ImageProcessor.Web.Episerver
I tried to retain the defaults for parameters as much as possible. I documented the methods and parameters based on the original documentation. IntelliSense is your friend and will show you all the information when needed.
Note that the commands will be executed in the order supplied.
To get an example after installing the package in the Alloy starter kit, change TeaserBlok.cshtml in Views/Shared/Blocks to the code below:
@using EPiServer.Core
@using ImageProcessorEpiserverTest.Controllers
@using ImageProcessor.Web.Episerver
@using System.Drawing
@model TeaserBlock
<div class="border">
@*Link the teaser block only if a link has been set and not displayed in preview*@
@using (Html.BeginConditionalLink(
!ContentReference.IsNullOrEmpty(Model.Link) && !(ViewContext.Controller is PreviewController),
Url.PageLinkUrl(Model.Link),
Model.Heading))
{
<h2 @Html.EditAttributes(x => x.Heading)>@Model.Heading</h2>
<p @Html.EditAttributes(x => x.Text)>@Model.Text</p>
<div @Html.EditAttributes(x => x.Image)>
<img src="@Html.ProcessImage(Model.Image).Resize(375,null).ReplaceColor("fff", "f00",99).Watermark("Episerver",new Point(100,100),"fff").RoundedCorners(20)" />
</div>
}
</div>
Remenber to change the @using in line 2 AND to enable all the necessary processors in config/imageprocessor/processing.config
This should give you the following:
Installation
The package is available on the Episerver NuGet feed with the name ImageProcessor.Web.Episerver. It obviously has a dependency on both the ImageProcessor core and the ImageProcessor.Web packages and will therefore add these to your solution.
ImageProcessor uses sensible defaults for it's configuration options. If you would like to override these defaults, you can install the ImageProcessor.Web.Config package. We need to do this here, so this package is also installed as a dependency. The needed configuration changes to both the ImageProcessor.Web configuration and the site web.config are then made by my NuGet package.
If you want to enable or disable processors, you can do that in the standard ImageProcessor way. See http://imageprocessor.org/imageprocessor-web/configuration/#processingconfig how to do this. If you want to tryout the processors, you can copy the contents from this file in the sample site in the repository to enable them all.
To minimize possible attack areas, only enable what you need in your production environment!
Uninstalling the package will revert everything back to the default ImageProcessor configuration.
Next steps
ImageProcessor has plugins to store the cache in Azure or Amazon S3 blob storage. I hope to release Episerver adapted versions of these plugins in the near future. The goal is that cache will be stored alongside the source blobs just as with the disk based solution without requiring additional configuration besides the regular Episerver blob storage cloud settings.
James is working on a completely rebuilt .NET Core version of ImageProcessor named ImageSharp (in beta at the time of writing). When this gets released, ImageProcessor will probably go into maintenance mode. He mentioned on Twitter that he wants to write a HttpModule which, because of .NET Standard support, makes it possible to use ImageSharp from .NET Framework and Episerver. I hope to be able to offer a comparable package then. Lets first see how this goes…
All the code for this package is on my GitHub account. I would really appreciate feedback in the comments below and help in the form of issues and, even better, pull requests in the repository.
Hope this is helpful.
More recent posts about this add-on:
- https://world.episerver.com/blogs/vincent-baaij/dates/2017/11/episerver-and-imageprocessor-now-also-on-azure-and-cms-11/
- https://world.episerver.com/blogs/vincent-baaij/dates/2018/5/episever-and-imageprocessor-new-versions/
- https://world.episerver.com/blogs/vincent-baaij/dates/2018/7/imageprocessor-web-episerver-new-versions/
- https://world.episerver.com/blogs/vincent-baaij/dates/2018/7/fggnrh/
- https://world.episerver.com/blogs/vincent-baaij/dates/2019/1/episerver-and-imageprocessor-new-crop-addition/
Looks great! Are the processed images optimized according to Google PageSpeed? https://developers.google.com/speed/docs/insights/OptimizeImages
Thanks!
ImageProcessor uses System.Drawing internally. The default encoder used by System.Drawing doesn't compress well when encoding images. ImageProcessor wraps around System.Drawing so suffers from the same limitations.
However James has released a plugin that uses various tools to post process images. The tool can be installed via Nuget.
https://www.nuget.org/packages/ImageProcessor.Web.PostProcessor/
Note that this will add overhead to the processing pipeline
I haven't tested this package with the post processor yet, but will do that soon and report back my findings.
this is an awesome alternative! thx for getting this together!
Great! I've been wanting this for years! Even gave it a try to do it myself once, but lack of time and (mainly) knowledge...
Great work, Vincent!
Great work, thank you :)
Hey Vincent,
I see you have been busy with lots of updates over the last year, thanks! (ref: https://world.episerver.com/blogs/vincent-baaij/dates/2018/7/fggnrh/ https://github.com/vnbaaij/ImageProcessor.Web.Episerver#change-log)
At the end of this article you mentioned adding Azure and Amazon S3 support. In the release notes, I see Azure added, but has Amazon support been added?
@david: No, Amazon has not been added. I decided to postpone that (indefinetely) as I have no experience whatsoever with Amazon and nobody asked for it until now.
@david: No, Amazon has not been added. I decided to postpone that (indefinetely) as I have no experience whatsoever with Amazon and nobody asked for it until now.
Look really interesting, just wondering about the security settings. Can you limit the total number of images that get cached on disk for each original image?
Otherwise couldn't you generate an almost infinite number of variations?
/path-to-image/my-image.jpg?height=500&quality=100
/path-to-image/my-image.jpg?height=499&quality=100
/path-to-image/my-image.jpg?height=498&quality=90.5
/path-to-image/my-image.jpg?height=498&quality=90.000005
Hi Vincent,
I am curious to know if this works in DXP? I did not see it explicitly said anywhere so I wanted to confirm that this is possible to use in DXP.
Hey Vincent
I am using this plugin in DXP to improve image loading performance to resize image at rendering time using ProcessImage helper as per small thumbnail dimension to avoid loading of big size image.
I noticed original image 900x506 size 55 kb and taking 500 ms to load, while using ProcessImage helper with ?width=380 its size reduced to 22 kb but load time increased to 700 ms.
As image size reduced I was expecting to reduce load time even thought azure blob cache is enable, in following request still load time remain same 700 ms.
Can you help what was wrong?