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

Conditional Content in SharePoint Markup

In SharePoint, there are several web parts that allow us to have different contents depending on some conditions:

  • LoginView (ASP.NET): allows the definition of templates per authenticated or anonymous user:
<asp:LoginView runat="server">

    <AnonymousTemplate>

        <!-- anonymous content -->

    </AnonymousTemplate>

    <LoggedInTemplate>

        <!-- authenticated content -->

    </LoggedInTemplate>

    <RoleGroups>

        <asp:RoleGroup Roles="Admin">

            <ContentTemplate>

                <!-- admin content -->

            </ContentTemplate>

        </asp:RoleGroup>

    </RoleGroups>

</asp:LoginView>

<SharePoint:SPSecurityTrimmedControl runat="server" PermissionMode="All" PermissionContext="CurrentSite" Permissions="ManageWeb">

    <!-- secure content -->

</SharePoint:SPSecurityTrimmedControl>

  • EditModePanel: for displaying contents in a web part page depending on its edit mode:
<PublishingWebControls:EditModePanel runat="server" PageDisplayMode="Edit">

    <!-- edit content -->

</PublishingWebControls:EditModePanel>

<SharePoint:UIVersionedContent runat="server" UIVersion="4">

    <ContentTemplate>

        <!-- content for SharePoint 2010 -->

        <!-- no code is run if does not match UIVersion -->

    </ContentTemplate>

</SharePoint:UIVersionedContent>

 

 

<SharePoint:VersionedPlaceholder runat="server" UIVersion="4">

    <!-- content for SharePoint 2010 -->

    <!-- code is always run but not rendered if does not match UIVersion -->

</SharePoint:VersionedPlaceholder>

  • AuthoringContainer: displays content depending on whether the current user has write or read rights on the current page or if it has an associated list item:
<PublishingWebControls:AuthoringContainer runat="server" DisplayAudience="ReadersOnly">

    <!-- content for readers -->

</PublishingWebControls:AuthoringContainer>

<PublishingWebControls:DeviceChannelPanel runat="server" IncludedChannels="iPhone">

    <!-- content for iPhones -->

</PublishingWebControls:DeviceChannelPanel>

  • DataViewWebPart: allows the passing of parameters and the usage of XSL for rendering logic;

I imagine you are now rolling your eyes: DataViewWebPart? how come? Well, because it doesn’t need to point to a specific list or view (unlike XsltListViewWebPart), it is very useful for markup-based customizations that will only depend on parameters:

   1: <WebPartPages:DataFormWebPart runat="server" Title="Conditional Content">

   2:     <ParameterBindings>

   3:         <ParameterBinding Name="MyParameter" Location="QueryString(MyParameter)"/>

   4:     </ParameterBindings>

   5:     <XSL>

   6:         <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl asp" xmlns:asp="System.Web.UI.WebControls">                                                  

   7:             <xsl:param name="MyParameter"/>                                                                                                                                        

   8:             <xsl:template match="/">

   9:                 <asp:Label runat="server" Text="Some content, just to show ASP.NET controls inside a SharePoint DataFormWebPart"/>

  10:                 <xsl:choose>

  11:                     <xsl:when test="$MyParameter=''">

  12:                         No parameter...

  13:                     </xsl:when>

  14:                     <xsl:otherwise>

  15:                         Allright! <xsl:value-of select="$MyParameter"/>

  16:                     </xsl:otherwise>

  17:                 </xsl:choose>                                                                                                

  18:             </xsl:template>

  19:         </xsl:stylesheet>

  20:     </XSL>                                        

  21: </WebPartPages:DataFormWebPart>

You can use this technique for:

  • Including scripts and stylesheets;
  • Including server-side controls.

It’s just a matter of rolling out some XSL to the rescue!

You may be already familiar with the available parameters, but you can find the full list here: http://msdn.microsoft.com/en-us/library/office/ff630170(v=office.15).aspx.

Another SharePoint Designer-only solution that may come in handy! 😉