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: }

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

                             

5 Comments

  • That's really sweet! I wasn't even thinking about using data: uri there but it totally makes sense for a simple dynamically generated image.

  • Glad you liked! Keep dropping by! ;-)

  • MSDN Library says "Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions. For a supported alternative, see Windows Imaging Components.".

    https://msdn.microsoft.com/en-us/library/system.drawing(v=vs.110).aspx

    I saw some asp.net applications that uses the System.Drawing classes throws exceptions unexceptedly.

    WHY it is not supported and altenative samples is below

    http://blogs.msdn.com/b/jaskis/archive/2012/03/23/why-you-should-not-use-system-drawing-from-asp-net-applications.aspx

    http://www.asprangers.com/post/2012/03/23/Why-you-should-not-use-SystemDrawing-from-ASPNET-applications.aspx



  • Hi, Hiroki!
    Yes, that is well known since ASP.NET 1... Did you read the whole post, including "Best option is to explicitly call Dispose/Close on EACH Drawing object as soon as done with it"? You will notice that that's exactly what I'm doing! ;-)
    Keep dropping by!

  • Hi, Ricardo.
    You are right!
    I am sorry for missing your Dispose method implementation.
    Thanks.

Add a Comment

As it will appear on the website

Not displayed

Your website