Generating GDI+ Images for ASP.NET MVC Views

This post is about applying the same technique I presented for ASP.NET Web Forms, but this time for MVC.

We need to have an ActionResult that does the actual work of converting whatever we draw in the Graphics context into an image with an inline Data URI. Here’s a possible solution, where you only specify the dimensions of the image to be generated:

public sealed class InlineImageResult : ActionResult

{

    private readonly Image bmp;

 

    public InlineImageResult(Int32 width, Int32 height)

    {

        //the PixelFormat argument is required if we want to have transparency

        this.bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);

 

        this.Graphics = Graphics.FromImage(this.bmp);

        //for higher quality

        this.Graphics.CompositingQuality = CompositingQuality.HighQuality;

        this.Graphics.SmoothingMode = SmoothingMode.HighQuality;

        this.Graphics.InterpolationMode = InterpolationMode.High;

        //make the background transparent

        this.Graphics.Clear(Color.Transparent);

    }

 

    public Graphics Graphics { get; private set; }

 

    public override void ExecuteResult(ControllerContext context)

    {

        using (this.bmp)

        using (this.Graphics)

        using (var stream = new MemoryStream())

        {

            //PNG because of transparency

            var format = ImageFormat.Png;

 

            this.bmp.Save(stream, format);

 

            var img = String.Format("<img src=\"data:image/{0};base64,{1}\"/>", format.ToString().ToLower(), Convert.ToBase64String(stream.ToArray()));

 

            context.HttpContext.Response.Write(img);

        }

    }

}

Now, we need to create an instance of this result and draw in it, in a controller’s action method:

[ChildActionOnly]

public ActionResult Image()

{

    var result = new InlineImageResult(200, 50);

    result.Graphics.DrawString("Hello, World!", new Font("Verdana", 20, FontStyle.Regular, GraphicsUnit.Pixel), new SolidBrush(Color.Blue), 0, 0);

 

    return result;

}

Notice the ChildActionOnly attribute: it tells MVC that this action method is only meant to be used in child action (RenderAction) call, not as a stand-alone action, like this:

<p>

    @{ Html.RenderAction("Image"); }

</p>

As you can see, this provides a very convenient way to generate images on the fly. You just need to learn about the drawing methods in GDI+.

Generating GDI+ Images for the Web

.NET’s Graphics Device Interface (GDI+) is Microsoft’s .NET wrapper around the native Win32 graphics API. It is used in Windows desktop applications to generate and manipulate images and graphical contexts, like those of Windows controls. It works through a set of operations like DrawString, DrawRectangle, etc, exposed by a Graphics instance, representing a graphical context and it is well known by advanced component developers. Alas, it is rarely used in web applications, because these mainly consist of HTML, but it is possible to use them. Let’s see how.

Let’s start by implementing a custom server-side control inheriting from Image:

   1: public class ServerImage: Image

   2: {

   3:     private System.Drawing.Image image;

   4:

   5:     public ServerImage()

   6:     {

   7:         this.ImageFormat = ImageFormat.Png;

   8:         this.CompositingQuality = CompositingQuality.HighQuality;

   9:         this.InterpolationMode = InterpolationMode.HighQualityBicubic;

  10:         this.Quality = 100L;

  11:         this.SmoothingMode = SmoothingMode.HighQuality;

  12:     }

  13:

  14:     public Graphics Graphics { get; private set; }

  15:

  16:     [DefaultValue(typeof(ImageFormat), "Png")]

  17:     public ImageFormat ImageFormat { get; set; }

  18:

  19:     [DefaultValue(100L)]

  20:     public Int64 Quality { get; set; }

  21:

  22:     [DefaultValue(CompositingQuality.HighQuality)]

  23:     public CompositingQuality CompositingQuality { get; set; }

  24:

  25:     [DefaultValue(InterpolationMode.HighQualityBicubic)]

  26:     public InterpolationMode InterpolationMode { get; set; }

  27:

  28:     [DefaultValue(SmoothingMode.HighQuality)]

  29:     public SmoothingMode SmoothingMode { get; set; }

  30:

  31:     protected override void OnInit(EventArgs e)

  32:     {

  33:         if ((this.Width == Unit.Empty) || (this.Height == Unit.Empty) || (this.Width.Value == 0) || (this.Height.Value == 0))

  34:         {

  35:             throw (new InvalidOperationException("Width or height are invalid."));

  36:         }

  37:

  38:         this.image = new Bitmap((Int32)this.Width.Value, (Int32)this.Height.Value);

  39:         this.Graphics = System.Drawing.Graphics.FromImage(this.image);

  40:         this.Graphics.CompositingQuality = this.CompositingQuality;

  41:         this.Graphics.InterpolationMode = this.InterpolationMode;

  42:         this.Graphics.SmoothingMode = this.SmoothingMode;

  43:

  44:         base.OnInit(e);

  45:     }

  46:

  47:     protected override void Render(HtmlTextWriter writer)

  48:     {

  49:         var builder = new StringBuilder();

  50:

  51:         using (var stream = new MemoryStream())

  52:         {

  53:             var codec = ImageCodecInfo.GetImageEncoders().Single(x => x.FormatID == this.ImageFormat.Guid);

  54:

  55:             var parameters = new EncoderParameters(1);

  56:             parameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, this.Quality);

  57:

  58:             this.image.Save(stream, codec, parameters);

  59:

  60:             builder.AppendFormat("data:image/{0};base64,{1}", this.ImageFormat.ToString().ToLower(), Convert.ToBase64String(stream.ToArray()));

  61:         }

  62:

  63:         this.ImageUrl = builder.ToString();

  64:

  65:         base.Render(writer);

  66:     }

  67:

  68:     public override void Dispose()

  69:     {

  70:         this.Graphics.Dispose();

  71:         this.Graphics = null;

  72:

  73:         this.image.Dispose();

  74:         this.image = null;

  75:

  76:         base.Dispose();

  77:     }

  78: }

Basically, this control discards the ImageUrl property and replaces it with a Data URI value generated from a stored context. You need to define the image’s Width and Height and you can also optionally specify other settings such as the image’s quality percentage (Quality), compositing quality (CompositingQuality), interpolation (InterpolationMode) and smoothing modes (SmootingMode). These settings can be used to improve the outputted image quality.

Finally, you use it like this. First, declare a ServerImage control on your page:

   1: <web:ServerImage runat="server" ID="image" Width="200px" Height="100px"/>

And then draw on its Context like you would in a Windows application:

   1: protected override void OnLoad(EventArgs e)

   2: {

   3:     this.image.Graphics.DrawString("Hello, World!", new Font("Verdana", 20, FontStyle.Regular, GraphicsUnit.Pixel), new SolidBrush(Color.Blue), 0, 0);

   4:

   5:     base.OnLoad(e);

   6: }

Basically, this control discards the ImageUrl property and replaces it with a Data URI value generated from a stored context. You need to define the image’s Width and Height and you can also optionally specify other settings such as the image’s quality percentage (Quality), compositing quality (CompositingQuality), interpolation (InterpolationMode) and smoothing modes (SmootingMode). These settings can be used to improve the outputted image quality.

Finally, you use it like this. First, declare a ServerImage control on your page:

   1: <web:ServerImage runat="server" ID="image" Width="200px" Height="100px"/>

And then draw on its Context like you would in a Windows application:

   1: protected override void OnLoad(EventArgs e)

   2: {

   3:     this.image.Graphics.DrawString("Hello, World!", new Font("Verdana", 20, FontStyle.Regular, GraphicsUnit.Pixel), new SolidBrush(Color.Blue), 0, 0);

   4:

   5:     base.OnLoad(e);

   6: }

The result is this IMG tag with a Data URI content, that you can save or copy to the clipboard:

image

Pretty sleek, don’t you think? Winking smile