Calligraphy In Ink! (Part 2)

Ok, see this is what happens when I have some time to 'play' with Blend.

My previous post showed how to modify the stylus tip to get something akin to a calligraphy pen. But, the pen that I got most recently got is called a scroll point. The tip itself looks like a fairly broad pen, but there's a 'gap' in the middle, so it's effectively drawing two parallel lines. You use the same gestures you'd use with a normal pen, it just looks somewhat different. (Sorry, I can't seem to find a bloody link on the web to show you what I'm talking about).

So, I want to duplicate THIS effect. It's a bit trickier then it was with the widening the tip, but it's still doable.

First, I need to implement my own handler for when I'm working with the Stylus, so I can intercept the event and add the second (and for fun) third line. I only want to draw when the stylus is down, so easily enough, add a property on the paper for when we're drawing. (Now, here I was a bit lazy and did it on the Window itself, but moving it the paper should be easy enough).

public partial class Window1

{

      bool drawing = false;

      public Window1()

      {

            this.InitializeComponent();

      }

 

      private void OnStylusDown(object sender, System.Windows.Input.StylusDownEventArgs e)

      {

            this.drawing = true;

      }

      private void OnStylusUp(object sender, System.Windows.Input.StylusEventArgs e)

      {

            this.drawing = false;

      }

}

 

(Of course, I do need to hook the StylusDown and StylusUp events up correctly, but that's trivial in Blend). Ok, now I can just check this.drawing, and if it's true, then the Stylus is actually on the surface. All I need to do, is when I'm drawing, add another stroke. So, add a StylusMove event:

 

private void OnStylusMove(object sender, System.Windows.Input.StylusEventArgs e)

{

if (this.drawing)

{

StylusPointCollection originalPoints = e.GetStylusPoints(Paper);

StylusPointCollection newPoints = new StylusPointCollection();

StylusPointCollection moreNewPoints = new StylusPointCollection();

foreach (StylusPoint thisPoint in originalPoints)

{

newPoints.Add(new StylusPoint(thisPoint.X + 3, thisPoint.Y + 3, thisPoint.PressureFactor));

moreNewPoints.Add(new StylusPoint(thisPoint.X + 6, thisPoint.Y + 6, thisPoint.PressureFactor));

}

Paper.Strokes.Add(new Stroke(newPoints));

Paper.Strokes.Add(new Stroke(moreNewPoints));

}

}

 

Add this and run it, and it's almost there. You'll see the additional lines get added, but there's one 'stupid' issue. if you look, the second and third strokes, depending on your speed, will look somewhat dotted. Here's what's happening. Let's say, for the sake of argument, that based on your drawing speed, the StylusMove is getting called each time with 3 points. Consider when you draw a line of length 9. The original line will correctly connect every point, 1 to 2, 2 to 3, 3 to 4 and so on.

 

However, in our instance, the StylusMove gets called with points 1, 2 and 3, so it creates a stroke connecting those three points. It gets called the second time with points 4, 5 and 6, and it creates that stroke. Here is the problem... nothing ever connects points 3 and 4! So where the original stroke is 9 connected points, our added strokes are three seperate strokes, each three points long, and disconnected.

 

Well, that's no good. So, we need to remember what point 3 was, so that when we start the next stroke segment, we use that as our initial point and connect there. So, in effect, instead of 1-2-3 4-5-6 7-8-9, we end up with 1-2-3 3-4-5-6 6-7-8-9, and since 3 and 3 match, it works.

 

However, once the Stylus comes off the page, we want to forget that final point, otherwise, we'd be always attaching the end point to the begin point of the new stroke, and that's obnoxious.

 

So, start by adding two new properties, one for the last detected point, and one for whether or not we're 'remembering' a point.

 

StylusPoint lastDetectedPoint = new StylusPoint();

bool drawingContinuousLine = false;

 

So, whenever drawingContinuousLine is set to true, we need to initialize the newPoints and moreNewPoints collections to that lastDetectedPoint (adjusted, of course, by 3 and 6 respectively). And, every time we add a new stroke in the StylusMove, we need to remember that lastDetectedPoint and state that we are drawing.

private void OnStylusMove(object sender, System.Windows.Input.StylusEventArgs e)

{

if (this.drawing)

            {

                  StylusPointCollection originalPoints = e.GetStylusPoints(Paper);

                  StylusPointCollection newPoints = new StylusPointCollection();

                  StylusPointCollection moreNewPoints = new StylusPointCollection();

                  if (this.drawingContinuousLine)

                  {

                        newPoints.Add(new StylusPoint(this.lastDetectedPoint.X + 3, this.lastDetectedPoint.Y + 3, this.lastDetectedPoint.PressureFactor));

                        moreNewPoints.Add(new StylusPoint(this.lastDetectedPoint.X + 6, this.lastDetectedPoint.Y + 6, this.lastDetectedPoint.PressureFactor));

                  }

                  foreach (StylusPoint thisPoint in originalPoints)

                  {

                        newPoints.Add(new StylusPoint(thisPoint.X + 3, thisPoint.Y + 3, thisPoint.PressureFactor));

                        moreNewPoints.Add(new StylusPoint(thisPoint.X + 6, thisPoint.Y + 6, thisPoint.PressureFactor));

                  }

                  Paper.Strokes.Add(new Stroke(newPoints));

                  Paper.Strokes.Add(new Stroke(moreNewPoints));

                  this.drawingContinuousLine = true;

                  this.lastDetectedPoint = originalPoints[originalPoints.Count - 1];

            }

      }

And, last but not least, we want to make sure that on StylusUp, add state that we're no longer drawing a ContinuousLine, so our strokes correctly stop getting drawn.

      private void OnStylusUp(object sender, System.Windows.Input.StylusEventArgs e)

      {

            this.drawing = false;

            this.drawingContinuousLine = false;

      }

And there you have it. Here's the final code (if you don't want to go hunting and pasting throughout my code). In order to make this work:

  • Create an InkCanvas, and name it Paper
  • Hook StylusUp, StylusDown and StylusMove to OnStylusUp, OnStylusDown and OnStylusMove respectively
  • Add the code
  • Enjoy

Note that this will only work with an actual Stylus, so you can't use a Mouse to mock it up. However, it wouldn't be too hard to add the code to work with a Mouse as well.

public partial class Window1

{

      bool drawing = false;

StylusPoint lastDetectedPoint = new StylusPoint();

bool drawingContinuousLine = false;

      public Window1()

      {

            this.InitializeComponent();

      }

private void OnStylusMove(object sender, System.Windows.Input.StylusEventArgs e)

{

if (this.drawing)

            {

                  StylusPointCollection originalPoints = e.GetStylusPoints(Paper);

                  StylusPointCollection newPoints = new StylusPointCollection();

                  StylusPointCollection moreNewPoints = new StylusPointCollection();

                  if (this.drawingContinuousLine)

                  {

                        newPoints.Add(new StylusPoint(this.lastDetectedPoint.X + 3, this.lastDetectedPoint.Y + 3, this.lastDetectedPoint.PressureFactor));

                        moreNewPoints.Add(new StylusPoint(this.lastDetectedPoint.X + 6, this.lastDetectedPoint.Y + 6, this.lastDetectedPoint.PressureFactor));

                  }

                  foreach (StylusPoint thisPoint in originalPoints)

                  {

                        newPoints.Add(new StylusPoint(thisPoint.X + 3, thisPoint.Y + 3, thisPoint.PressureFactor));

                        moreNewPoints.Add(new StylusPoint(thisPoint.X + 6, thisPoint.Y + 6, thisPoint.PressureFactor));

                  }

                  Paper.Strokes.Add(new Stroke(newPoints));

                  Paper.Strokes.Add(new Stroke(moreNewPoints));

                  this.drawingContinuousLine = true;

                  this.lastDetectedPoint = originalPoints[originalPoints.Count - 1];

            }

      }

      private void OnStylusDown(object sender, System.Windows.Input.StylusDownEventArgs e)

      {

            this.drawing = true;

      }

      private void OnStylusUp(object sender, System.Windows.Input.StylusEventArgs e)

      {

            this.drawing = false;

            this.drawingContinuousLine = false;

      }

}