Doodling on the Web

 

Julia Lerman
The Data Farm

May 2005

Applies to:
   Microsoft Windows XP Tablet PC Edition
   Windows XP Tablet PC Edition Development Kit 1.7

Summary: This article demonstrates an additional way to redisplay ink images drawn on ASP.NET pages as regular GIF images, without having to waste unnecessary resources. It also addresses the need to do so in a way that is consistent with a professionally designed Web site. (6 printed pages)

Contents

Introduction
My Big Ink Problem
Getting the GIF from the Ink Object
Getting the GIF to another page
Biography

Introduction

With the 2004 release of the 1.7 version of the Tablet PC SDK, Microsoft gave developers the ability to incorporate ink-enabled functionality onto their Web sites. In Mobile Ink Jots 3: Ink On the Web, Shawn Van Ness gives a great overview of getting ink onto the Web.

Some key points from the article to remember about are:

  • Web applications are ink-enabled by embedding an ink-enabled windows forms user control into the html.
  • You must create your own user control rather than using something directly from the API as you can't depend on users having the API.
  • It is very import to explicitly dispose any ink-enabled controls used in the .NET Framework.
  • When attaching the InkOverlay object to a form or control, in order to allow it to safely be used on a Web site within the Code Access Security rules, reference the control, not its handle. For example inkO=new InkOverlay(myControl.handle) works in a Windows application but does not work in a Web application. The new SDK allows you to use the syntax: inkO=new InkOverlay(myControl) which permits the control to be used in Internet Explorer.

For more information about getting ink-enabled controls onto your Web applications, see Mobile Ink Jots 3: Ink On the Web.

This article examines one particular scenario. We are going to take an image that is created in ink on a Web page and display it on another Web page as a true image (GIF). Although this doesn't sound so tricky, there are, in fact, a few bridges that need to be crossed on the way. The advantage of this particular method is that we won't be forced to save every single ink drawing on our server merely for the purpose of redisplaying it. With this approach, users will have the ability to view and interact with the drawing as they world any other regular image. For example, they can right click on the image to copy it or save it onto their own computer.

For an example of an application that leverages this technique, we will be looking at the DataFarm Doodling application which is live at www.thedatafarm.com/doodle.aspx.

My Big Ink Problem

The objective of the doodling application is to let users draw on one page in an ink-enabled control and then display the image as a GIF in another Web page.

The first page of this application is simply an ASP.NET page with an ink-enabled Windows Forms user control embedded onto it. The control is ink-enabled by using the InkOverlay component and exposes a number of public functions to allow changing pen properties (such as color, width and transparency). As with any other Windows Forms control on a Web page, you need to interface with the control by using JavaScript in the HTML. Functionality built directly into the control—such as buttons or dropdowns—is handled directly by the control.

Of the many roadblocks, the toughest problem I encountered while writing the doodling application is that the normal process used to push the persisted image dynamically to a Web page is Response.BinaryWrite, which renders the image to a blank page. In other words, as you render a page with BinaryWrite, whatever data you are rendering becomes the entire page. In this case, that means a blank page with nothing but the image on it. I need to render the image within an existing Web page so that it can be consistent with the look and feel of the rest of the Web site.

The entire solution requires two Web pages. The first, named Doodle.aspx, has the embedded control. The second, ViewDoodle.aspx, along with an HttpHandler named ReturnBinaryImagefromInk.ashx, enables us to display the image by using BinaryWrite onto a page with additional content.

Getting the GIF from the Ink Object

The first part of this challenge is to get an image from what has been drawn in the ink enabled control. This is actually done by using the Ink object's Save method. Although there are a number of formats we can save to, we need to choose a method that can be used to create our GIF image. Therefore, we are going to save to a GIF format and then convert it to Base-64 Encoded String, the format that is required to transfer the data from one page to another. The conversion method is a function of the user control, but we need to expose this method (make it public) so that the ASP.NET page that contains the control has access to the conversion method. Remember that if you try to save an empty Ink object, you get an error, so test for the existence of Stroke objects first. The GetInkasBase64 method returns the ink data as a string.

public string GetInkasBase64()
   {
      //avoid throwing an exception
if (inkO.Ink.Strokes.Count==0)   
  {
    return "empty";
     }
     byte[] inkBytes=inkO.Ink.Save(PersistenceFormat.gif);
     return Convert.ToBase64String(inkBytes);
   }

Another thing you learned in the Mobile Ink Jots article is that because the embedded control is not a server-side component, you cannot interact with it in the code-behind of your ASP.NET page. Instead, you must do all of your work in client-side code. I use JavaScript to do this.

On the main drawing page of the Doodle application, there is a button labeled GIF in a new Window. In the HTML, this button has an onclick parameter that fires a JavaScript function called DrawGIF.

<input onclick="DrawGIF()" type="button" value="GIF in new Window">

Because we need to pass the string to another Web page, we store it into a hidden field on the page called InkData. Note that the page's form is named inkForm and its action, which fires when we activate the form's submit method, opens the page ViewDoodle.aspx. Also, notice that the form tag does not have the parameter "runat=server", while the hidden field does have the "runat=server" parameter. This is what enables us to share the ink data with the next page. It also means that we will not be able to use most of the ASP.NET server controls, including buttons and text boxes. Every control on the page is an HTML control.

<FORM id="inkForm" name="inkForm" action="ViewDoodle.aspx" method="post">
<INPUT id="inkData" type="hidden" name="inkData" runat="server">

The DrawGIF function—which is called when a user clicks the GIF in new Window button—calls the GetInkasBase64 function from within the control.

Note   Because the control is not a server-side control, there is no IntelliSense when working with its properties and methods.

We only submit the form if the conversion does not return "empty".

function DrawGIF() 
{
 inkForm.inkData.value = inkForm.inkWebControl1.GetInkasBase64(); 
 if (inkForm.inkData.value!="empty")
 inkForm.submit();
 }

When we call the submit function, the ViewDoodle.aspx page is activated, and we do some work in the page's Load method to get the Base64 ASCII data out of the first page.

For a complex drawing, the string can get VERY long. Here is what the beginning of one of these string looks like:

R0lGODlhYAGNAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/w
D//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzA
Az/wBmAABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM
/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/z
NmADNmMzNmZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/
ADP/MzP/ZjP/mTP/zDP// . . .

Getting the GIF to another page

When ASP.NET was released, it offered Web developers many new ways to share information between pages. We were no longer dependent on hidden controls to do the trick. So why are we using a hidden control on the Doodle page to get the image over to the ViewDoodle page? Because the ink control is a client-side control, not a server side control, we don't have access to all of the .NET Framework's functionality for persisting, caching, or sharing information when we are on that first page. We have to rely on client-side methods. By storing the ink's data into a hidden control on the Doodle.aspx page, we can access it when we are on the ViewDoodle.aspx page by calling the GetValues() method on the ASP.NET property, HttpRequest.Form. This is a shortcut to getting the NameValueCollection returned by Request.QueryString and then using GetValues to return the particular input element that we are looking for. The data is then stored in a String object, inkString.

inkString=Request.Form.GetValues("inkData");

Now, we have moved the Base64 encoded data representing the image to our ViewDoodle page. At this point, if we called Response.BinaryWrite, passing in the data, we would lose everything else that we have placed on the ViewDoodle page and have nothing but the image. This does not make designers—who spend a lot of time creating Web sites—very happy. So here is where a little known bit of HTML trickery comes into play.

We know that we want to display an image in our ViewDoodle page. We also know that we need an empty page on which to render our image. The trick is the fact in addition to using an image file as the source of an image, you can also set the source to another Web page!

What we are going to do is add one more Web element into the mix, an HttpHandler. The HttpHandler is an efficient way of getting server side processing when you do not need a user interface, almost like having a Web page with code behind but nothing on the design surface. The way we do it is to create a special type of page called an ashx page. This page has a directive that points to a class that does the work of rendering the image. We then embed the ashx page into the <image> control that we place on the ViewDoodle page. We have almost reached our goal.

Create a new code file and rename it to ReturnBinaryImagefromInk.ashx.

Add to this file only one line of code, which is the WebHandler directive.

<%@ webHandler Language="C#" Class="ReturnBinaryImagefromInk" %>

In order to make this work, let's go back to the Load method of the ViewDoodle.aspx page. Here we add two more lines of code. The first is to save the image data into an ASP.NET session variable, which will be available to the ReturnBinaryImagefromInk class. The second is to set the source of the image that is on the ViewDoodle page to ReturnBinaryImagefromInk.ashx. We cannot set this property during design time because we do not want the image to render until after it has acquired the image data. Here is the complete Load method which runs only the first time the page is loaded, not on post back. Also note that if we run into problems, we are going to send the browser to a different page, gallery.aspx. In this particular example, there is no exception handling, merely the redirection to the gallery page. You should consider adding some additional support to end users for potential problems and even collect exception data for your own analysis.

private void Page_Load(object sender, System.EventArgs e)
      {
         if (!IsPostBack)
         {
           try
             {
            inkString=Request.Form.GetValues("inkData");
            Session["inkString"]=inkString[0];
            Image1.ImageUrl="ReturnBinaryImagefromInk.ashx";
            }   
            catch
            {
               Response.Redirect("gallery.aspx");
            }
         }
      }

Once this happens, the ReturnBinaryImagefromInk handler loads and runs the ReturnBinaryImagefromInk class.

The class will need to implement the iHttpHandler interface. Additionally, in order to get the ink data out of Session, it will also need to implement the IRequiresSessionState interface. You must override the iHttpHandler.ProcessRequest method and iHttpHandler.IsReusable property. The image data stored in the session variable is converted back to a byte array then returned with BinaryWrite. Be sure to complete the rendering with Response.End.

Here is what the entire code module looks like.

using System;
using System.Web;
using System.Web.SessionState;

public class ReturnBinaryImagefromInk : System.Web.IHttpHandler,IRequiresSessionState
   {
      public void ProcessRequest (HttpContext context)
      {         
         string inkString=context.Session["inkString"].ToString();
         byte[] inkBytes=Convert.FromBase64String(inkString);
         context.Response.BinaryWrite(inkBytes);
         context.Response.End();
      }
      public bool IsReusable
      {
         get {return true;}
      }

   }

This renders inside of the placeholder for the image on the ViewDoodle page, which will resize itself based on the size of the image.

At this point, the user has all of the normal options that they have with any other image, and we—the owner of the Web site—do not have to deal with saving and deleting every image that is drawn on our Web site.

Not only does the Doodle application enable the user access to their image to copy or save onto their computer (by using the built-in browser functionality), but there is additional functionality on the ViewDoodle page that enables users to save their drawings to the Doodle Gallery. At this point—because the drawing is merely an image on the Web page—it is very simple to save it as a file in the particular directory that has been set aside for this purpose.

Biography

Julia Lerman is an independent consultant who has been designing and writing software applications for 20 years. She lives in Vermont where she runs the Vermont.NET User Group. Julia is well known in the .NET community as an INETA Board member, .NET MVP, ASPInsider, conference speaker, and prolific blogger. You can read Julia's blog at thedatafarm.com/blog.