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.

Rendering Graphics in ASP.NET with GDI+

Showing the Dynamic Image

To render the image, you can create a page—the example calls ClockPage.aspx—and drag and drop the ClockControl onto the ClockPage (see Figure 2).

[GDI0102.jpg]

Figure 2: The Dynamically Drawn Image of a Simple Analog Clock

When the ClockPage.aspx page is requested, the ClockControls Page_Load calls DrawClock. To ensure the ClockPage is requested, you assign it instead of an actual image to the source property of an <img> control (keeping in mind that an Image web control is rendered as an <img> HTML tag). Listing 3 shows the markup for the ClockPage.

Listing 3: The Markup for the clockpage.sspx Page

<%@ Page Language="VB" AutoEventWireup="false"
         CodeFile="ClockPage.aspx.vb" Inherits="ClockPage" %>
<%@ Register Src="ClockControl.ascx"
             TagName="ClockControl" TagPrefix="uc1" %>
<%@ OutputCache NoStore="true" Duration="1" VaryByParam="none" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
   <title>Untitled Page</>title>
</head>
<body>
   <form id="form1" runat="server">
   <div>
      <uc1:ClockControl ID="ClockControl1" runat="server"
                        EnableViewState="false" />
   </div>
   </form>
</body>
</html>

I'll leave the final challenge of animating the clock as real-time feedback for another article. As is, the user would have to refresh the page every time an update clock was desired.

The Clock Is Drawn

This example demonstrated how you can treat a dynamic image on a UserControl as a drawing surface much as you'd treat a Windows form as one. The user control is then embedded on a page, and that page becomes the source value of an image attribute. Each time the source attribute—I used the <img src="clockpage.aspx" /> tag—requested and the image is to be rendered, the clock will be redrawn.

About the Author

Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object-oriented programming and .NET. Check out his new book UML DeMystified from McGraw-Hill/Osborne. Paul is a software architect for Tri-State Hospital Supply Corporation. You may contact him for technology questions at pkimmel@softconcepts.com.

If you are interested in joining or sponsoring a .NET Users Group, check out www.glugnet.org.

Copyright © 2007 by Paul T. Kimmel. All Rights Reserved.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • VMware vCloud® Government Service provided by Carpathia® is an enterprise-class hybrid cloud service that delivers the tried and tested VMware capabilities widely used by government organizations today, with the added security and compliance assurance of FedRAMP authorization. The hybrid cloud is becoming more and more prevalent – in fact, nearly three-fourths of large enterprises expect to have hybrid deployments by 2015, according to a recent Gartner analyst report. Learn about the benefits of …

Most Popular Programming Stories

More for Developers

RSS Feeds