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;
       
publicPoint
PreviousPoint;
    }

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

   
public
MainPage()
    {
       
this
.InitializeComponent();
    }

   
protectedoverridevoidOnPointerPressed(PointerRoutedEventArgs
args)
    {
       
// 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(PointerRoutedEventArgs
args)
    {
       
// 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))
        {
           
PointerInfo
pointerInfo = 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(PointerRoutedEventArgs
args)
    {
       
// 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(PointerRoutedEventArgs
args)
    {
       
// 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.