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!