May 21, 2012

ImageResizer Fluent Extensions

Today I created a Fluent API for the excellant ImageResizer module for .NET. You can download it from NuGet and get the source on Github.

If you haven’t heard of it, ImageResizer is a HTTP module that can be used to transform images on your site. The ImageResizer library includes both querystring and managed APIs and there are plugins available for almost any image processing task, from simple resizing to generating images using pages from a PDF document.

I first tested the module in ASP.NET MVC. Unfortunately there weren’t any HTML helpers available for constructing requests to the querystring api (contrary to the description on NuGet) so I ended up writing code like this:

<img src="@Url.Content("~/images/image.jpg")?maxwidth=400&show=both" alt="Some Image"/>

This isn’t actually that bad but it does require you to know what parameters to use and their constraints. Since I knew we would be doing some fairly complex image manipulation I thought it would be helpful to create a Fluent API that makes it easier to build the request.

The ImageBuilder class maintains a collection of parameters that are used to build the querystring. Each command type is exposed as an instance of ImageBuilderExpression. Expressions have access to the underlying builder, so they can add parameters where necessary. When we’re ready we call the Build method passing in our source image file path, and the querystring will be generated and appended automatically. You can check out the full source on Github.

Examples

Creating an ImageBuilder

var builder = new ImageBuilder();

Resizing

// resize using a fixed width and height
var builder = new ImageBuilder()
    .Resize(img => img.Dimensions(300, 225));

Resize and Crop

// resize and crop from the bottom left of the image
var builder = new ImageBuilder()
    .Resize(img => img.Dimensions(300, 225)
        .Crop().Anchor(AnchorPoint.BottomLeft));

Resize and Rotate

// set max width and rotate 180 degrees
var builder = new ImageBuilder()
    .Resize(img => img.MaxWidth(300))
    .Transform(img => img.Rotate(RotateType.Rotate180));

Resize and Flip

// set max width and flip on the x axis
var builder = new ImageBuilder()
    .Resize(img => img.MaxWidth(300))
    .Transform(img => img.FlipBefore(FlipType.X));

Styling

// set background color and add a 5px border
var builder = new ImageBuilder()
    .Resize(img => img.Dimensions(300, 300))
    .Style(img => img.BackgroundColor("000000")
        .BorderColor("FFFFFF").BorderWidth(5));

Output options

// setting output options
var builder = new ImageBuilder()
    .Resize(img => img.Dimensions(300, 300))
    .Output(img => img.Format(OutputFormat.Png).Quality(90));

Building the querystring

[Test]
public void Resize_mode_Crop()
{
    builder.Resize(img => img.Dimensions(100, 100).Crop()).Build("image.jpg")
        .ShouldEqual("image.jpg?width=100&height=100&mode=crop");
}

You’ll find many more examples in the test project on Github. So far I’ve only created expressions for the most common/free plugins, but hope to add more in the future.

ASP.NET MVC

There are two packages on Nuget, ImageResizer.FluentExtensions and ImageResizer.FluentExtensions.Mvc.

The latter includes some HTML helper methods for using the ImageBuilder direct from your view pages:

Configuring the builder inline:

@Html.BuildImage("~/images/image.jpg", x => x.Resize(img => img.MaxWidth(200)))

Using a preconfigured builder (useful for multiple images)

@{
    var images = new[] { "image1.jpg", "image2.jpg", "image3.jpg" };
    
    var builder = new ImageBuilder()
        .Resize(img => img.Dimensions(400, 300).Crop())
        .Transform(img => img.FlipAfter(FlipType.X))
        .Style(img => img.PaddingWidth(10)
                            .PaddingColor("FF0066")
                            .Margin(20)
                            .BackgroundColor("000000"))
        .Output(img => img.Quality(90)
                            .Format(OutputFormat.Png));   
}

@foreach (var image in images) {
	<li>@Html.BuildImage(image, builder)</li>
}

Extending the API

Extending the API is just a matter of creating extension methods for ImageBuilder or one of the existing builder expressions. The example below demonstrates how we might create an extension for the SimpleFilters plugin.

public class SimpleFiltersExpression : ImageBuilderExpression
{
    public SimpleFiltersExpression(ImageBuilder builder) : base(builder) { }

    public SimpleFiltersExpression Sepia()
    {
        builder.SetParameter(SimpleFiltersParameters.Sepia, "true");
        return this;
    }

    public SimpleFiltersExpression Brightness(decimal value)
    {
        if (value < -1 || value > 1)
            throw new ArgumentException("Brightness must be between -1 and 1");

        builder.SetParameter(SimpleFiltersParameters.Brightness, value.ToString());
        return this;
    }

    private static class SimpleFiltersParameters
    {
        internal const string Sepia = "sepia";
        internal const string Brightness = "brightness";
    }
}

public static class ImageBuilderExtensions
{
    public static ImageBuilder ApplyFilters(this ImageBuilder builder, Action<SimpleFiltersExpression> configure)
    {
        var expression = new SimpleFiltersExpression(builder);
        configure(expression);
        return builder;
    }
}

[TestFixture]
public class ImageBuilderExtensionTests
{
    [Test]
    public void Can_use_extension()
    {
        new ImageBuilder()
            .Resize(img => img.MaxWidth(200))
            .ApplyFilters(filters => filters.Sepia().Brightness(.75M))
            .Build("image.jpg")
            .ShouldEqual("image.jpg?maxwidth=200&sepia=true&brightness=0.75");
    }
}

© 2022 Ben Foster