VB and Tablet PC: Inking on Irregular Surfaces

So I was playing around with my tablet PC application this evening because someone in a Visual Basic list had posted a question about how to create an irregular drawing surface for a medical application they were working on. They wanted users to be able to draw on a picture of a body, but did not want to collect the ink anywhere outside of the body shape. It actually turned out to be relatively easy. But some background first…


The Tablet PC SDK comes with a couple designable controls called InkPicture and InkEdit. You can drop these controls onto your form and they will immediately start capturing ink. The InkEdit will also convert your ink to text.



These controls make it extremely easy to work with ink. For instance, here’s how we can save just the ink we draw on the InkPicture into a file:


Private Sub SaveInk(ByVal filename As String, ByVal format As Microsoft.Ink.PersistenceFormat)


    Try


        Dim inkData As Byte() = Me.InkPicture1.Ink.Save(format, Microsoft.Ink.CompressionMode.Default)


        


        With My.Computer.FileSystem


            If .FileExists(filename) Then


                .DeleteFile(filename)


            End If


            .WriteAllBytes(filename, inkData, False)


        End With


 


        MsgBox(String.Format(“Saved ink data to {0}”, filename), MsgBoxStyle.Information)


 


       


    Catch ex As Exception


        MsgBox(String.Format(“Unable to save ink data to {0}”, filename))


    End Try


 


End Sub


The persistence format can even be specified as a Gif format so you can create a completely new picture of just your annotations. To load the annotations, we just read in the bytes and then load them into a new Ink.Ink object and set it to the Ink property of our InkPicture:


Private Sub LoadInk(ByVal filename As String)


    Dim inkData As Byte() = Nothing


 


    Try


        With My.Computer.FileSystem


            If .FileExists(filename) Then


                inkData = .ReadAllBytes(filename)


            End If


        End With


    Catch ex As Exception


        MsgBox(String.Format(“Unable to load ink data from {0}”, filename))


    End Try


 


    If inkData IsNot Nothing Then


        Dim newink As New Microsoft.Ink.Ink


        newink.Load(inkData)


        Me.InkPicture1.InkEnabled = False


        Me.InkPicture1.Ink = newink


        Me.InkPicture1.InkEnabled = True


    End If


End Sub


When we want to save both the picture and the ink we just have to create a new picture and then render the ink on the picture using the Ink.Renderer object:


Private Sub SaveInkOnPicture(ByVal filename As String)


    Try


        ‘Create graphics object


        Dim bm As New Bitmap(Me.InkPicture1.Image)


        Dim g As Graphics = Graphics.FromImage(bm)


 


        ‘Create a renderer to draw ink on the graphics surface


        Dim r As New Microsoft.Ink.Renderer()


        r.Draw(g, Me.InkPicture1.Ink.Strokes)


 


        ‘Save the new image


        bm.Save(filename, System.Drawing.Imaging.ImageFormat.Bmp)


 


        MsgBox(String.Format(“Saved picture to {0}”, filename), MsgBoxStyle.Information)


 


    Catch ex As Exception


        MsgBox(String.Format(“Unable to save picture to {0}”, filename))


    End Try


 


End Sub


These designable controls are really easy to use and will get you up and running quickly, however what if you want to draw on something other than a picture? That’s where the InkOverlay object comes in. You can use this object to wrap any windows Control to enable inking. For instance, to create a blank “drawing surface” you can use a Panel with the InkOverlay:


Private WithEvents overlay As InkOverlay


 


Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load


    overlay = New InkOverlay(Me.Panel1)


    overlay.Enabled = True


End Sub


 


So now back to the original question about inking on an irregular surface. Well since the InkOverlay can be used to wrap any control, and we can draw any control any way we want, it’s actually pretty easy to achieve this. Since this was a medical application I first found I picture of a human body. I then threw a panel on a form and set its background image to the picture of the body. Next, I added an event handler to the Paint event in order to draw the panel how I wanted to. The key is setting up a GraphicsPath and then creating a new Region along that path for the control.


Public Class Form2


 


    Private WithEvents overlay As Microsoft.Ink.InkOverlay


 


    Private Sub Form2_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load


        overlay = New Microsoft.Ink.InkOverlay(Me.Panel1)


        overlay.DefaultDrawingAttributes.Color = Color.Red


        overlay.Enabled = True


    End Sub


 


    Private Sub Panel1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Panel1.Paint


        Dim Person As New System.Drawing.Drawing2D.GraphicsPath()


        Person.AddEllipse(82, 1, 74, 74)


        Person.AddLine(65, 75, 175, 75)


        Person.AddLine(175, 75, 250, 240)


        Person.AddLine(250, 240, 210, 260)


        Person.AddLine(210, 260, 170, 180)


        Person.AddLine(170, 180, 175, 440)


        Person.AddLine(175, 440, 145, 440)


        Person.AddLine(145, 440, 127, 245)


        Person.AddLine(127, 245, 105, 440)


        Person.AddLine(105, 440, 70, 440)


        Person.AddLine(70, 440, 80, 180)


        Person.AddLine(80, 180, 35, 260)


        Person.AddLine(35, 260, 1, 240)


        Person.AddLine(1, 240, 65, 75)


 


        Me.Panel1.Region = New Region(Person)


    End Sub


End Class


The graphics path I created here is pretty basic but it illustrates the idea. Now when users draw on the panel, it will only allow ink on the graphics path!