Click here to Skip to main content
6,631,404 members and growing! (16,079 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » Mobile Development » Ink     Intermediate

Understanding Strokes and Digital Ink

By frankgo

Learn how Ink is created and strokes are managed
C# 2.0.NET 1.1, .NET 2.0, WinXP, Vista, TabletPC, WinForms, VS.NET2003, VS2005, Dev
Posted:7 Mar 2007
Updated:20 Mar 2007
Views:18,307
Bookmarked:21 times
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
7 votes for this article.
Popularity: 3.64 Rating: 4.30 out of 5

1

2
1 vote, 14.3%
3
2 votes, 28.6%
4
4 votes, 57.1%
5

Introduction

This article is an introductory look at the object model used for ink collection and recognition. We examine the mechanism for collecting ink, as well as the logical location and identification of ink strokes.

Let's begin with some discussion of the most important classes in our object model, the Ink Class. In most cases you'll be using the InkOverlay class to work with Ink in your applications, but the InkOverlay, as well as the InkCollector, both handle instantiation of the Ink object for you.

Screenshot - StrokeViewerFigure1.jpg
Figure 1. Object Model for ink

  • InkCollector: This is the fundamental object that is used to collect and render ink as it is being entered by the user. The InkCollector fires events to your application as they happen, and also packages up Cursor movements into ink strokes for you. You can tell the InkCollector what events and what data you are interested in receiving, as well as whether you want it to paint ongoing ink strokes as they are collected. InkCollector was intentionally designed without all the support that the InkOverlay brings to the picture, as it's a baseline for building component technology in which you want behavior different than the InkOverlay.
  • InkOverlay: The InkOverlay object is a superset of the InkCollector and adds selection and erasing of the ink that's been captured. For most all applications the InkOverlay is the object of choice because its support for selection-related features is a real time-saver and typically a requirement of any application. To keep things simple, this is the object I use in Figure 1.

The InkCollector and InkOverlay objects fire numerous events. Wiring these events enables you to respond to all of the important stages of ink input or pen movement.

The CursorInRange/CursorOutOfRange events specify that the user has moved the cursor in range of an ink-enabled area. Note that on some devices the cursor does not have to be touching the screen for these events to fire.

Following the CursorInRange event, if the cursor is not touching the digitizer, the NewInAirPackets event is raised.

When the cursor touches the digitizer, the CursorDown event fires.

The CursorDown event is followed by the NewPackets event which indicates that ink is being collected.

When the CursorOutOfRange event fires, you receive a Stroke or Gesture event, depending on the value of the InkCollector object's CollectionMode property. The same is true for the InkOverlay object.

Your application will often receive various SystemGesture and Mouse events. By default the Tablet PC Platform supports numerous system gestures, many of which mimic traditional mouse events. For example, tap events map to mouse clicks, and drag, hold, and hover events map to the same type of Mouse events. For more information about system gestures, see System Gestures in the Tablet PC SDK.

Stroke Data

A Stroke object consists of packet information (x, y,...) that represents the Ink at a certain point on the coordinate system, which we now know starts 0,0 at the upper-left corner. Each Stroke object is assigned a unique ID (Stroke.ID) relative to the Ink object in which that it resides. It allows the developer to define extended properties for custom data and exposes DrawingAttributes that affect the rendering of the data.

The Strokes collection is a collection of references to Stroke objects. A Strokes collection provides helpful operations common to the collection of Stroke objects it contains, such as Rotate() and Scale(), which are used for filtering, moving, or resizing the Stroke data.

Let's examine some Stroke information. I am using the StrokeIDViewer application that ships as part of the Building Tablet PC Applications book; I've rewritten part of that application for inclusion in this article and the download that accompanies it. In my sample source it's called StrokeViewer.

Screenshot - StrokeViewerFigure2.jpg
Figure 2. Stroke count for my name in cursive and print.

Notice the difference in the number of strokes when I write my first name in script versus printed format. I press down, write, and pull up four times in script and nine when I print. Wow, that's a lot of extra work. So a stroke contains the packet data that represents the ink from when I touch the digitizer to when I pull away. Strokes are basically a Bezier curve that represents the ink collected between a CursorDown and CursorUp event.

There are several things going on here.

  1. A Stroke object is created with each pen down/pen moves/pen up action.
  2. A Stroke contains all of the packet data (including things like pressure).
  3. A Stroke's visual representation is its Bezier by default (but this can be changed in the drawing attributes).

Let's work with the StrokeViewer sample (which is included in the download that accompanies this article) some more to see what's happening when we add, select and delete strokes. We'll add three strokes to the window and will display their stroke ID's.

Screenshot - StrokeViewerFigure3.jpg
Figure 3. I create three strokes.

I created three strokes and you can see they are labeled 1,2,3, which in this case are the Stroke ID's. Now I'll change the mode of the application to "delete" and I'll remove some of the strokes.

Screenshot - StrokeViewerFigure4.jpg
Figure 4. I erase one stroke.

When I add another stoke you would expect to see ID 4, correct? Well, you won't.

Screenshot - StrokeViewerFigure5.jpg
Figure 5. What happened to Stroke ID 4?

As you can see in the previous image, the new Stroke has an ID of 5. So, what happened to 4? Each pen action I performed in delete mode causes a new Stroke to be created; hence the IDs incremented. This is interesting because no ink was actually drawn. Let's try this out with select mode and you'll see a similar progression of the stroke IDs.

Screenshot - StrokeViewerFigure6.jpg
Figure 6. Experimenting with the selection's affect on a stroke.

Switch to select mode, select a Stroke, and then go back to ink mode and add a new Stroke.

Screenshot - StrokeViewerFigure7.jpg
Figure 7. Sequence of stroke ID jumps.

Now I am back in ink mode and I draw another stroke, which is labeled with an ID of 7. ID 6 was used for the selection process.

Finally, when we go into point-erase mode and run the eraser across all the strokes, splitting each one into two, you can see the same phenomena. The action of deleting the strokes actually wound up creating one to represent what was removed.

Screenshot - StrokeViewerFigure8.jpg
Figure 8. The effect of erasing on Stroke ID.

Let's take a look at the code I wrote to build the StrokeViewer; it's pretty straightforward

First, in my C# Windows Call (remember to add a reference to Microsoft.TabletPC.API):

public Form1() 
{ 
    InitializeComponent();
    // Instantiate the InkOverlay. 

    m_InkOverlay = new Microsoft.Ink.InkOverlay(this.Handle); 
    m_InkOverlay.Enabled = true;
    // Create the event handler to respond to a StrokeAdded event.

    m_InkOverlay.Stroke += new InkCollectorStrokeEventHandler( InkStrokeAdded );
    // Hook up to the InkOverlay's event handlers. 

    m_InkOverlay.Painted += new InkOverlayPaintedEventHandler(InkPainted); 
}

The InkStrokeAdded event handler is called when a Stroke is added; what I do is invalidate the form in order to force a repaint.

// Delegate called when a Stroke is added.

private void InkStrokeAdded( object sender, InkCollectorStrokeEventArgs e) 
{
    // Invalidate the form so we can force a repaint.

    this.Invalidate();
}

The InkPainted handler is called when the WM_Paint message is sent to my form; because I want ownership of this process, I intercept the call and make a subsequent call into a helper function that does the real work.

// Delegate to respond to Paint request.

private void InkPainted( object sender, PaintEventArgs e)
{
    // Call the helper function that redraws the form. 

    RendererEx.DrawStrokeIds(e.Graphics, Font, m_InkOverlay.Ink);
}

As mentioned, the real work is done in the following method, which is called to render the Ink and the Stroke.ID.

// Draw the Stroke IDs for a Strokes collection.

public static void DrawStrokeIds( 
    Renderer renderer, Graphics g, Font font, Strokes strokes) 
{ 
    // Iterate through every Stroke referenced by the collection.

    foreach (Stroke s in strokes) 
    { 
        // Make sure each Stroke has not been deleted. 

        if (!s.Deleted) 
        { 
            // Draw the Stroke's ID at its starting point. 

            string str = s.Id.ToString(); Point pt = s.GetPoint(0); 
            renderer.InkSpaceToPixel(g, ref pt); 
            g.DrawString( str, font, Brushes.White, pt.X-10, pt.Y-10);
            g.DrawString( str, font, Brushes.White, pt.X+1, pt.Y+1);
            g.DrawString( str, font, Brushes.Black, pt.X, pt.Y); 
        } 
    }
}

There are a lot of great white papers on the MSDN Mobile PC Development Center that dig into stoke handling a bit more for you to read. Check out:

Rendering Ink

The Renderer object controls the actual drawing of Ink data to a hardware device context (HDC), be it a window, printer, or other HDC. When rendering Ink, you must keep in mind the two separate coordinate systems supported by the Tablet PC device: "device coordinates" and "ink coordinates". The ink coordinate system is the default. The Renderer object exposes methods to convert ink coordinates to device coordinates, and vice versa. These include the InkSpaceToPixel and PixelToInkSpace methods. The Renderer object is also responsible for the actual rendering of the Ink to the HDC. The Renderer object uses the Draw and DrawStroke methods to accomplish this task. Finally, the Renderer object provides support for the manipulation of Ink data, including the transforming, scaling, repositioning, and resizing of Strokes.

Conclusion

I encourage you to try things out and give strokes a whirl, there is a lot you can do with them.
Frank Gocinski

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

frankgo


Member
As a Business Development Manager for the Mobile and Tailored PC group at Microsoft, my team is responsible for bringing highly tuned Mobile PC applications to market. We work with all developers through our forums and development center (http://msdn.microsoft.com/mobilepc) to drive awareness of the platform and directly with partners to ensure success of their solutions. A long time developer, started with FORTRAN for those of you that remember so far back. I don’t write any real production code anymore but I’ll always be a closet developer.
Occupation: Web Developer
Location: United States United States

Other popular Mobile Development articles:

  • Writing Your Own GPS Applications: Part 2
    In part two of the series, the author of "GPS.NET" teaches developers how to write GPS applications suitable for the real world by mastering GPS precision concepts. Source code includes a working NMEA interpreter and sample high-precision application in C# and VB.NET.
  • Writing Your Own GPS Applications: Part I
    What is it that GPS applications need to be good enough to use for in-car navigation? Also, how does the process of interpreting GPS data actually work? In this three-part series, I will cover both topics and give you the skills you need to write a commercial-grade GPS application.
  • Learn How to Find GPS Location on Any SmartPhone, and Then Make it Relevant
    A step by step tutorial for getting GPS from any SmartPhone, even without GPS built in, and then making location useful.
  • iPhone UI in Windows Mobile
    It's an interface that works with transparency effects. As a sample I used an interface just like the iPhone one. In this tutorial I am explaining how simple is working with transparency on Windows Mobile.
  • Pocket 1945 - A C# .NET CF Shooter
    An article on Pocket PC game development
Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 4 of 4 (Total in Forum: 4) (Refresh)FirstPrevNext
QuestionSending mouse event to an inkoverlay object PinmemberFadik22:34 12 Jun '08  
GeneralWhat do I need to have installed to make this work? Pinmemberjafwin6:58 30 Nov '07  
GeneralRe: What do I need to have installed to make this work? PinmemberWitch Doctor17:29 12 Jan '08  
GeneralRe: What do I need to have installed to make this work? PinmemberMember 35437140:27 27 Oct '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 20 Mar 2007
Editor: Sean Ewington
Copyright 2007 by frankgo
Everything else Copyright © CodeProject, 1999-2009
Web21 | Advertise on the Code Project