Author news: A small taste of Programming Windows, Sixth Edition

I’m working on Chapter 13, “Touch, Etc.,” for the Release Preview ebook of Charles Petzold’s Programming Windows, Sixth Edition, which we’ll release in the next two weeks. (Consider buying now before the prices jumps $10.) I thought you might like a small taste from the chapter. Let me know if you’d like more samples like this.

Pressure Sensitivity

The lines drawn by the various FingerPaint programs are of a uniform stroke thickness—24 pixels to be precise—but I probably would have done it differently if the tablet I’m using was sensitive to touch pressure. But it is not.

There are two properties that might influence line thickness in a finger-painting program, and both are defined by the PointerPointProperties object returned from the Properties property of the PointerPoint class (which in turn is obtained by a call to the GetCurrentPoint method of the PointerRoutedEventArgs event arguments).

The first property is ContactRect, a Rect value that is intended to report the rectangular bounding box of the contact area of a finger (or pen point) on the screen. On my tablet, this Rect always has a Width and Height of zero regardless of the pointer device.

The second is Pressure, which is afloat value that can take on values between 0 and 1. On my tablet, this Pressure value is the default value of 0.5 for fingers and the mouse, but it is variable for the pen, and so we have the opportunity to try it out.

For purposes of simplicity, the FingerPaint4 program does not include Esc key processing or the editing feature, but it does implement pointer capturing. The big difference is that the Polyline approach to drawing must be abandoned because a Polyline has only a single StrokeThicknessproperty. In this new program each stroke must instead be composed of very short individual lines, each a unique StrokeThickness that is calculated from the Pressure value, but all in the same color. This implies that the dictionary needs to contain values of type Color (or better yet, a Brush) and the previous Point. This is now two items, so let’s define a custom structure for that purpose that I called PointerInfo:

 

 

Project: FingerPaint4 | File: MainPage.xaml.cs (excerpt)

 

publicsealedpartialclassMainPage : Page
{
structPointerInfo
{
publicBrush Brush;
publicPointPreviousPoint;
}

Dictionary<uint, PointerInfo> pointerDictionary = newDictionary<uint, PointerInfo>();
Random rand = newRandom();
byte[] rgb = newbyte[3];

publicMainPage()
{
this.InitializeComponent();
}

protectedoverridevoidOnPointerPressed(PointerRoutedEventArgsargs)
{
// Get information from event arguments
uint id = args.Pointer.PointerId;
Point point = args.GetCurrentPoint(this).Position;

// Create random color
rand.NextBytes(rgb);
Color color = Color.FromArgb(255, rgb[0], rgb[1], rgb[2]);

// Create PointerInfo
PointerInfopointerInfo = newPointerInfo
{
PreviousPoint = point,
Brush = newSolidColorBrush(color)
};

// Add to dictionary
pointerDictionary.Add(id, pointerInfo);

// Capture the Pointer
CapturePointer(args.Pointer);

base.OnPointerPressed(args);
}

protectedoverridevoidOnPointerMoved(PointerRoutedEventArgsargs)
{
// Get information from event arguments
uint id = args.Pointer.PointerId;
PointerPointpointerPoint = args.GetCurrentPoint(this);
Point point = pointerPoint.Position;
float pressure = pointerPoint.Properties.Pressure;

// If ID is in dictionary, create a new Line element and add to Grid
if (pointerDictionary.ContainsKey(id))
{
PointerInfopointerInfo = pointerDictionary[id];

Line line = newLine
{
X1 = pointerInfo.PreviousPoint.X,
Y1 = pointerInfo.PreviousPoint.Y,
X2 = point.X,
Y2 = point.Y,
Stroke = pointerInfo.Brush,
StrokeThickness = pressure * 24,
StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round
};
contentGrid.Children.Add(line);

// Update PointerInfo and store back in dictionary
pointerInfo.PreviousPoint = point;
pointerDictionary[id] = pointerInfo;
}

base.OnPointerMoved(args);
}

protectedoverridevoidOnPointerReleased(PointerRoutedEventArgsargs)
{
// Get information from event arguments
uint id = args.Pointer.PointerId;

// If ID is in dictionary, remove it
if (pointerDictionary.ContainsKey(id))
pointerDictionary.Remove(id);

base.OnPointerReleased(args);
}

protectedoverridevoidOnPointerCaptureLost(PointerRoutedEventArgsargs)
{
// Get information from event arguments
uint id = args.Pointer.PointerId;

// If ID is still in dictionary, remove it
if (pointerDictionary.ContainsKey(id))
pointerDictionary.Remove(id);

base.OnPointerCaptureLost(args);
}
}

 

The previous PointerPressed handling created a Polyline, gave it an initial point, and added it to the Grid and Dictionary. In this program, only a PointerInfo value is created and added to the dictionary. Much more work occurs in the PointerMoved handler. Using the new point and the previous point from the dictionary, a Line element is constructed and added to the Grid. The new point then replaces the previous point in the PointerInfo value.

Notice that the StrokeThickness is set to 24 times the Pressure value. This results in a maximum stroke thickness of 24 and a stroke thickness of 12 for non-pressure-sensitive devices. Notice also that the StrokeStartLineCap and StrokeEndLineCap properties are set to Round. Try commenting out these property settings and see what happens when a stroke has sharp turns: little gaps appear because two short lines are at an angle to each other. The line caps cover those gaps.

Here’s a little, umm, artwork I did entirely with the pen:

image

 

Notice the graceful subtlety of the strokes when rendered with a pressure-sensitive input device.

 

If you draw very quickly, you might notice that the resultant strokes are not as curvy as you like and wonder if the GetIntermediatePoints method of PointerPoint might provide some more detail. It is my experience that the collection of points returned from this call always contains only one item, but I’m glad the feature is built in to obtain a more detailed flow of points.

 

It is also my experience that PointerMoved events are fired over 100 times per second, which is faster than the frame rate of the video display but not quite fast enough for extremely energetic fingers.