This topic has not yet been rated - Rate this topic

Custom Rendering Ink

The DrawingAttributes property of a stroke allows you to specify the appearance of a stroke, such as its size, color, and shape, but there may be times that you want to customize the appearance beyond what DrawingAttributes allow. You may want to customize the appearance of ink by rendering in the appearance of an air brush, oil paint, and many other effects. The Windows Presentation Foundation (WPF) allows you to custom render ink by implementing a custom DynamicRenderer and Stroke object.

This topic contains the following subsections:

Ink rendering occurs two times; when a user writes ink to an inking surface, and again after the stroke is added to the ink-enabled surface. The DynamicRenderer renders the ink when the user moves the tablet pen on the digitizer, and the Stroke renders itself once it is added to an element.

There are three classes to implement when dynamically rendering ink.

  1. DynamicRenderer: Implement a class that derives from DynamicRenderer. This class is a specialized StylusPlugIn that renders the stroke as it is drawn. The DynamicRenderer does the rendering on a separate thread, so the inking surface appears to collect ink even when the application user interface (UI) thread is blocked. For more information about the threading model, see The Ink Threading Model. To customize dynamically rendering a stroke, override the OnDraw method.

  2. Stroke: Implement a class that derives from Stroke. This class is responsible for static rendering of the StylusPoint data after it has been converted into a Stroke object. Override the DrawCore method to ensure that static rendering of the stroke is consistent with dynamic rendering.

  3. InkCanvas: Implement a class that derives from InkCanvas. Assign the customized DynamicRenderer to the DynamicRenderer property. Override the OnStrokeCollected method and add a custom stroke to the Strokes property. This ensures that the appearance of the ink is consistent.

Although the DynamicRenderer class is a standard part of WPF, to perform more specialized rendering, you must create a customized dynamic renderer that derives from the DynamicRenderer and override the OnDraw method.

The following example demonstrates a customized DynamicRenderer that draws ink with a linear gradient brush effect.


using System;
using System.Windows.Media;
using System.Windows;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Input;
using System.Windows.Ink;


...


// A StylusPlugin that renders ink with a linear gradient brush effect.
class CustomDynamicRenderer : DynamicRenderer
{
    [ThreadStatic]
    static private Brush brush = null;

    [ThreadStatic]
    static private Pen pen = null;

    private Point prevPoint;

    protected override void OnStylusDown(RawStylusInput rawStylusInput)
    {
        // Allocate memory to store the previous point to draw from.
        prevPoint = new Point(double.NegativeInfinity, double.NegativeInfinity);
        base.OnStylusDown(rawStylusInput);
    }

    protected override void OnDraw(DrawingContext drawingContext,
                                   StylusPointCollection stylusPoints,
                                   Geometry geometry, Brush fillBrush)
    {
        // Create a new Brush, if necessary.
        if (brush == null)
        {
            brush = new LinearGradientBrush(Colors.Red, Colors.Blue, 20d);
        }

        // Create a new Pen, if necessary.
        if (pen == null)
        {
            pen = new Pen(brush, 2d);
        }

        // Draw linear gradient ellipses between 
        // all the StylusPoints that have come in.
        for (int i = 0; i < stylusPoints.Count; i++)
        {
            Point pt = (Point)stylusPoints[i];
            Vector v = Point.Subtract(prevPoint, pt);

            // Only draw if we are at least 4 units away 
            // from the end of the last ellipse. Otherwise, 
            // we're just redrawing and wasting cycles.
            if (v.Length > 4)
            {
                // Set the thickness of the stroke based 
                // on how hard the user pressed.
                double radius = stylusPoints[i].PressureFactor * 10d;
                drawingContext.DrawEllipse(brush, pen, pt, radius, radius);
                prevPoint = pt;
            }
        }
    }
}


Implement a class that derives from Stroke. This class is responsible for rendering StylusPoint data after it has been converted into a Stroke object. Override the DrawCore class to do the actual drawing.

Your Stroke class can also store custom data by using the AddPropertyData method. This data is stored with the stroke data when persisted.

The Stroke class can also perform hit testing. You can also implement your own hit testing algorithm by overriding the HitTest method in the current class.

The following C# code demonstrates a custom Stroke class that renders StylusPoint data as a 3-D stroke.


using System;
using System.Windows.Media;
using System.Windows;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Input;
using System.Windows.Ink;


...


// A class for rendering custom strokes
class CustomStroke : Stroke
{
    Brush brush;
    Pen pen;

    public CustomStroke(StylusPointCollection stylusPoints)
        : base(stylusPoints)
    {
        // Create the Brush and Pen used for drawing.
        brush = new LinearGradientBrush(Colors.Red, Colors.Blue, 20d);
        pen = new Pen(brush, 2d);
    }

    protected override void DrawCore(DrawingContext drawingContext, 
                                     DrawingAttributes drawingAttributes)
    {
        // Allocate memory to store the previous point to draw from.
        Point prevPoint = new Point(double.NegativeInfinity, 
                                    double.NegativeInfinity);

        // Draw linear gradient ellipses between 
        // all the StylusPoints in the Stroke.
        for (int i = 0; i < this.StylusPoints.Count; i++)
        {
            Point pt = (Point)this.StylusPoints[i];
            Vector v = Point.Subtract(prevPoint, pt);

            // Only draw if we are at least 4 units away 
            // from the end of the last ellipse. Otherwise, 
            // we're just redrawing and wasting cycles.
            if (v.Length > 4)
            {
                // Set the thickness of the stroke 
                // based on how hard the user pressed.
                double radius = this.StylusPoints[i].PressureFactor * 10d;
                drawingContext.DrawEllipse(brush, pen, pt, radius, radius);
                prevPoint = pt;
            }
        }
    }
}


The easiest way to use your customized DynamicRenderer and stroke is to implement a class that derives from InkCanvas and uses these classes. The InkCanvas has a DynamicRenderer property that specifies how the stroke is rendered when the user is drawing it.

To custom render strokes on an InkCanvas do the following:

The following C# code demonstrates a custom InkCanvas class that uses a customized DynamicRenderer and collects custom strokes.


public class CustomRenderingInkCanvas : InkCanvas
{
    CustomDynamicRenderer customRenderer = new CustomDynamicRenderer();

    public CustomRenderingInkCanvas() : base()
    {
        // Use the custom dynamic renderer on the
        // custom InkCanvas.
        this.DynamicRenderer = customRenderer;
    }

    protected override void OnStrokeCollected(InkCanvasStrokeCollectedEventArgs e)
    {
        // Remove the original stroke and add a custom stroke.
        this.Strokes.Remove(e.Stroke);
        CustomStroke customStroke = new CustomStroke(e.Stroke.StylusPoints);
        this.Strokes.Add(customStroke);

        // Pass the custom stroke to base class' OnStrokeCollected method.
        InkCanvasStrokeCollectedEventArgs args = 
            new InkCanvasStrokeCollectedEventArgs(customStroke);
        base.OnStrokeCollected(args);

    }

}


An InkCanvas can have more than one DynamicRenderer. You can add multiple DynamicRenderer objects to the InkCanvas by adding them to the StylusPlugIns property.

You can customize the appearance of ink by deriving your own DynamicRenderer, Stroke, and InkCanvas classes. Together, these classes ensure that the appearance of the stroke is consistent when the user draws the stroke and after it is collected.

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.