Rendering Graphics in ASP.NET with GDI+

As I write this, Vista is out and promises new and cool things, and Windows Presentation Foundation (WPF) is closing the gap between rich Windows clients and rich web clients. So, a huge number of developers and businesses out there are interested in rich web clients. For them, I wrote this article.

This article uses a trivial example to demonstrate how to dynamically create GDI+ graphics in web pages. The example, a ticking clock, has plumbing that would support any kind of continuously updating, dynamically rendered graphic.

Rendering Graphics with GDI+

Although web forms do not have a canvas (or device context, also called DC) and you therefore cannot ask a Web form for its Graphics object, you can simulate this behavior by rendering graphics in ASP.NET. In summary, to render images with GDI+ you need:

  • One user control that will act as your device context or drawing canvas
  • One web page to contain the user control
  • One web page to contain an image control (The first web page will actually play the role of the image for this web page.)

Figure 1 depicts the relationship between the user control and two web pages.

Figure 1: The Relationship Between the Controls that Play the Role of Dynamic Canvas

One page is actually the viewable page, and the second page and user control play the role of dynamic canvas. Take a look at how this works by building it from the inside out.

Defining the UserControl

The UserControl is as close to the Graphics object (canvas or device context, if you prefer) as you are going to get. The basic idea of rendering the image is to create an image, get a Graphics object from that image, draw something on it, and then save that image on the HttpResponse.OutputStream (this code is show in Listing 1).

Listing 1: The Code That Orchestrates Rendering the Clock

Private Sub DrawClock()
   Dim b As Bitmap   = New Bitmap(FClockWidth, FClockHeight)
   Dim g As Graphics = Graphics.FromImage(b)
   Dim p As Pen      = New Pen(Brushes.Black, 1)

   g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias

   DrawClockFace(g, p)
   DrawClockTicks(g, p)
   DrawClockHands(g, p)

   Response.ContentType = "image/jpeg"
   b.Save(Response.OutputStream, ImageFormat.Jpeg)
   Response.End()
End Sub

The reason I used an inner UserControl is that writing to the output stream is destructive to things already on the stream. So, other HTML would be obliterated by writing the image. When you save the image to the output response stream, that is all that will be there. The code that renders the clock is not that important technically, but Listing 2 shows it for fun.

Listing 2: The ClockControl (The ClockControl.ascx Page Is an Empty UserControl.)

Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices

Partial Class ClockControl
  Inherits System.Web.UI.UserControl

  Private FClockWidth As Integer = 100
  Private FClockHeight As Integer = 100

  Private Sub DrawClock()
    Dim b As Bitmap   = New Bitmap(FClockWidth, FClockHeight)
    Dim g As Graphics = Graphics.FromImage(b)
    Dim p As Pen      = New Pen(Brushes.Black, 1)

    g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias

    DrawClockFace(g, p)
    DrawClockTicks(g, p)
    DrawClockHands(g, p)

    Response.ContentType = "image/jpeg"
    b.Save(Response.OutputStream, ImageFormat.Jpeg)
    Response.End()
  End Sub

  Private Sub DrawClockFace(ByVal g As Graphics, ByVal p As Pen)
    g.FillRectangle(Brushes.White, 0, 0, FClockWidth, FClockHeight)
    g.DrawEllipse(p, New Rectangle(1, 1, 98, 98))
  End Sub

  Private Sub DrawClockTicks(ByVal g As Graphics, ByVal p As Pen)
    ' draw the clock ticks
    ' Borrowed from Mike Gold's article:
    ' http://www.vbdotnetheaven.com/UploadFile
    '/mgold/VirtualClockinVBdotNET04212005032826AM/
    'VirtualClockinVBdotNET.aspx
    Dim count As Integer = 1
    Dim hour As Integer = 1

    Dim angle As Double

    For count = 0 To 330 Step 30

      angle = (count - 1) * Math.PI / 180

      'For angle = 0 To (2 * Math.PI) - (2.0 * Math.PI / 14)
      'Step 2.0 * Math.PI / 12
      Dim x As Double = (FClockWidth - 20) / 2 *_
         Math.Cos((angle - Math.PI / 3)) + _
        (FClockWidth - 20) / 2 + 5

      Dim y As Double = (FClockWidth - 20) / 2 * _
         Math.Sin((angle - Math.PI / 3)) + _
        (FClockWidth - 20) / 2 + 4

      Dim font As Font = New Font("Times New Roman", 8)
      g.DrawString(Convert.ToString(hour), font, Brushes.Black, _
         CSng(x), CSng(y), _
        New StringFormat)

      'count += 1
      hour += 1

    Next count ' angle
  End Sub

  Private Sub DrawClockHands(ByVal g As Graphics, ByVal p As Pen)
    Dim d As DateTime = DateTime.Now
    ' draw hour hand
    Dim h As Integer = d.Hour
    DrawHour(g, h)

    ' draw minute hand
    Dim m As Integer = d.Minute
    DrawMinute(g, m)

    ' draw second hand
    Dim s As Integer = d.Second
    DrawSecond(g, s)

  End Sub

  Private Sub DrawHour(ByVal g As Graphics, ByVal hour As Integer)
    Const OFFSET As Integer = 30

    Dim p As Pen = New Pen(Color.Black, 3)

    ' Figure out the Angle in radians
    Dim angle As Double = ((hour - 1) Mod 12) * 30 * Math.PI / 180

    Dim x, y As Double

    x = (FClockWidth - OFFSET) / 2 * _
        Math.Cos((angle - Math.PI / 3)) + _
      (FClockWidth - OFFSET) / 2 + 10

    y = (FClockWidth - OFFSET) / 2 * _
       Math.Sin((angle - Math.PI / 3)) + _
      (FClockWidth - OFFSET) / 2 + 8


    g.DrawLine(p, CSng(FClockWidth / 2), CSng(FClockHeight / 2), _
      CSng(x), CSng(y))

  End Sub

  Private Sub DrawMinute(ByVal g As Graphics, _
                         ByVal Minute As Integer)
    Dim p As Pen = New Pen(Color.Black, 2)

    ' Figure out the Angle in radians
    Dim angle As Double = (Minute - 6) * 6 * Math.PI / 180
    Dim x, y As Double

    x = (FClockWidth - 20) / 2 * Math.Cos((angle - Math.PI / 3)) + _
      (FClockWidth - 20) / 2 + 10

    y = (FClockWidth - 20) / 2 * Math.Sin((angle - Math.PI / 3)) + _
      (FClockWidth - 20) / 2 + 8

    g.DrawLine(p, CSng(FClockWidth / 2), CSng(FClockHeight / 2), _
       CSng(x), CSng(y))

  End Sub

  Private Sub DrawSecond(ByVal g As Graphics, _
                         ByVal Second As Integer)
    Dim p As Pen = New Pen(Color.Red, 1)

    ' Figure out the Angle in radians
    Dim angle As Double = (Second - 6) * 6 * Math.PI / 180
    Dim x, y As Double

    x = (FClockWidth - 20) / 2 * Math.Cos((angle - Math.PI / 3)) + _
      (FClockWidth - 20) / 2 + 10

    y = (FClockWidth - 20) / 2 * Math.Sin((angle - Math.PI / 3)) + _
      (FClockWidth - 20) / 2 + 8

    g.DrawLine(p, CSng(FClockWidth / 2), CSng(FClockHeight / 2), _
       CSng(x), CSng(y))

  End Sub


  Protected Sub Page_Load(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles Me.Load

    DrawClock()

  End Sub
End Class

The last three lines of code in DrawClock move the dynamic image to the output stream. You will need to use System.Drawing and System.Drawing.Image to support the ClockControl behavior.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read