Code Sample: Windows 8 Input: Ink sample in C#/Xaml
If you are planning to develop an application that is going to have the drawing capability, one of the first namespaces to resort to in WinRT is the Inking namespace. Using the InkManager in Windows 8 (Metro) applications, you can:
- Draw ink strokes
- Capture ink strokes
- Render Strokes on a Canvas using different geometries
- Select Strokes
- Delete Strokes
- Save and Load Strokes
- Convert strokes to text through handwriting recognition
- Copy/Paste strokes from the clipboard
- Hit Test the pen input
In this article, we will be talking about different methodologies for rendering the pen input on the Canvas element on Metro applications.
You can get the complete solution that will be used as the example from TechNet : Windows 8 Input: Ink sample in C#/Xaml
Rendering Pointer Movements
Maybe the easiest and the most efficient way to render the pen input is to render each pointer movement when the pointer is pressed until it is released.
For this purpose, we add a canvas element on our page:
<Grid Background=”White”>
<Canvas Name=”InkCanvas” Background=”White” Margin=”0,0,0,0″ />
</Grid>
Let us add the pointer handlers to our page. We need two handlers for PointerPressed, PointerMoved.
public void OnCanvasPointerPressed(object sender, PointerRoutedEventArgs e) { // Get information about the pointer location. PointerPoint pt = e.GetCurrentPoint(InkCanvas); m_PreviousContactPoint = pt.Position; // Accept input only from a pen or mouse with the left button pressed. PointerDeviceType pointerDevType = e.Pointer.PointerDeviceType; if (pointerDevType == PointerDeviceType.Pen || pointerDevType == PointerDeviceType.Mouse && pt.Properties.IsLeftButtonPressed) { e.Handled = true; } else if (pointerDevType == PointerDeviceType.Touch) { // Process touch input } }
When the user presses the pointer or uses the Ink Pen, we just record the starting point (m_PreviousContactPoint) to use.
Then when the pointer moves, we assume that the user is trying to draw something on the canvas:
public void OnCanvasPointerMoved(object sender, PointerRoutedEventArgs e) { if (e.Pointer.PointerId == m_PenId) { PointerPoint pt = e.GetCurrentPoint(InkCanvas); currentContactPt = pt.Position; x1 = m_PreviousContactPoint.X; y1 = m_PreviousContactPoint.Y; x2 = currentContactPt.X; y2 = currentContactPt.Y; var color = m_CurrentDrawingColor; var size = m_CurrentDrawingSize; if (CalculateDistance(x1, y1, x2, y2) > 2.0) { // // If the delta of the mouse is significant enough, // we add a line geometry to the Canvas Line line = new Line() { X1 = x1, Y1 = y1, X2 = x2, Y2 = y2, StrokeThickness = size, Stroke = new SolidColorBrush(color) }; m_PreviousContactPoint = currentContactPt; // Draw the line on the canvas by adding the Line object as // a child of the Canvas object. InkCanvas.Children.Add(line); } } else if (e.Pointer.PointerId == m_TouchId) { // Process touch input } }
And for the PointerReleased, we just set e.Handled to true.
At this point we already have an example that can draw images and of which you can adjust the size and the color of the drawn lines.
But what if you want to use some more advanced functions such as hand writing recognition or copy/paste and save/load functionality. For these options, it is better to use InkManager and render the strokes that the InkManager records.
Rendering Strokes from InkManager
For the InkManager to be able to record the strokes, we need to pass on the pointer events to the InkManager.
In the PointerPressed event handler:
// (Already have) PointerPoint pt = e.GetCurrentPoint(InkCanvas);
// Pass the pointer information to the InkManager.
CurrentManager.ProcessPointerDown(pt);
In the PointerMoved event handler:
// Pass the pointer information to the InkManager.
CurrentManager.ProcessPointerUpdate(pt);
In the PointerReleased event handler:
// Pass the pointer information to the InkManager.
CurrentManager.ProcessPointerUp(pt);
S0 once we are recording the movements of the pointer, the InkManager will create a list of strokes for us to use to render.
You can render each stroke using a BezierSegment and a PathFigureCollection.
private void RenderStroke(InkStroke stroke, Color color, double width, double opacity = 1) { // Each stroke might have more than one segments var renderingStrokes = stroke.GetRenderingSegments(); // // Set up the Path to insert the segments var path = new Windows.UI.Xaml.Shapes.Path(); path.Data = new PathGeometry(); ((PathGeometry)path.Data).Figures = new PathFigureCollection(); var pathFigure = new PathFigure(); pathFigure.StartPoint = renderingStrokes.First().Position; ((PathGeometry)path.Data).Figures.Add(pathFigure); // // Foreach segment, we add a BezierSegment foreach (var renderStroke in renderingStrokes) { pathFigure.Segments.Add(new BezierSegment() { Point1 = renderStroke.BezierControlPoint1, Point2 = renderStroke.BezierControlPoint2, Point3 = renderStroke.Position }); } // Set the general options (i.e. Width and Color) path.StrokeThickness = width; path.Stroke = new SolidColorBrush(color); // Opacity is used for highlighter path.Opacity = opacity; InkCanvas.Children.Add(path); }
You can retrieve a list of strokes by calling the GetStrokes method of the InkManager.
Further Implementation
To create a complete implementation, you can create a Refresh method which would remove the Line elements that are added while the user is still moving the pointer and only render the strokes from the InkManager.
The refresh method would then be called when the user releases the pointer and the temporary paths that are drawn during the action are replaced by the BezierSegments.
You can continue the second part where we discuss the loading and saving of the Ink Strokes here: Drawing / Inking API in WinRT (C#) – II
Pingback: Windows 8 Developer Links – 2012-07-27Dan Rigby | Dan Rigby
Pingback: Drawing / Inking API in WinRT (C#) – I
Hi
First of all, thanks for the article!
Second, I have a little problem with the ink manager.
I need to write the ink canvas to a WriteableBitmap in my app, and normally I would do this in Silverlight without much effort, using “var bmp = new WriteableBitmap(my_InkPresenter,null)”
Only this constructor doesn’t exist in .NET.
So I tried saving the strokes to a memory stream through the InkManager.SaveAsync(..) method, then using the stream to create the WriteableBitmap. But this only saves the strokes and not the whole ink canvas. And I really need to get the position of the strokes on the canvas.
Any ideas that can help?
Thanks!
LikeLike
Hey Peter,
I will add the second part of the series tonight which is about loading and saving the strokes as/from image file.
Hope it will help you…
LikeLike
I just noticed… The saveasync method should work without any problems… When you are passing stokes to the inkmanager, the positions of the strokes are relative to the canvas itself since the pointer events are captured from the canvas. So even if it is saving the strokes only, it is as good as the WriteableBitmap(inkpresenter, null).
Could you explain a bit more what the problem is with the stoke positions?
LikeLike
I’d like to have a “capture” of the canvas, with all the transparent parts as well as the strokes. However, it’s like the SaveAsync method “crops out” the transparent parts, since it’s only really saving the strokes.
Also, did you find any other method for rendering than Bezier? Bezier can go out of control if the user draws points close together (ie drawing the same line thrice before rendering).
I have to say I miss the InkPresenter class!!
LikeLike
This explains it
LikeLike
yeap I was just testing it my self…You are absolutely right the bounding rectangle from the ink manager basically shows what is wrong with the saving… (i.e. Canvas.ActualWidth = InkManager.BoundingRectangle.Left + InkManager.BoundingRectangle.Width + InkManager.BoundingRectangle.Right)
LikeLike
I’d like to have a “capture” of the canvas, with all the transparent parts as well as the strokes. However, it’s like the SaveAsync method “crops out” the transparent parts, since it’s only really saving the strokes.
Also, did you find any other method for rendering than Bezier? Bezier can go out of control if the user draws points close together (ie drawing the same line thrice before rendering).
I have to say I miss the InkPresenter class!!
LikeLike
Pingback: Drawing / Inking API in WinRT (C#) – I | azkafaiz.com
Pingback: Drawing / Inking API in WinRT (C#) – II | Can Bilgin
Hi Can,
Is these a way to undo the last Stroke using InkManager?? There is no way for us to remove Strokes in code from whatever I have seen. Any ideas??
LikeLike
How to process touch inputs ?
LikeLike
Hi, what if PointerDeviceType.Touch? I’ve tried that but it thown an exception.
LikeLike
güzel paylaşım eline sağlık teşekkürler
LikeLike
Ben tesekkur ederim, yine bekleriz :). Ilk turkce yorum, bu blogda…
LikeLike
doesn’t seem to have any way to undo redo. See http://stackoverflow.com/questions/15206693/windows-8-how-to-undo-redo-ink-using-built-in-inking-functionality
LikeLike
Guys, I am unable to merge Both background image and storkes of inkmanager into single image.
Is it possible to do that?
LikeLike
Pingback: Drawing / Inking API in WinRT (C#) – I | Can Bilgin | Ra Puke Moana
hi, Can, is there any example of touch input?
i install my application in windows tablet, and when i try to draw something in canvas, the screen keeps moving …
LikeLike
By the way, is it possible for you to post an example on saving the stroke with the image background?
LikeLike
Pingback: Drawing / Inking API in WinRT (C#) – III | Can Bilgin
Hello! Thanks a lot for this article and sample. Is it possible to disable the Ink on the Canvas? It is never disabled here! And is it possible to zoom into the canvas without interfering with the Ink Pointer Entered events?
LikeLike
Disabling ink is as simple as not feeding the pointer move events to the InkManager, you can maybe add a flag in there somewhere to check if it is disabled currently or not and add Bezier curves accordingly and push the pointer moved events to the InkManager (i.e. I am referring to the line CurrentManager.ProcessPointerUpdate(pt); )
As for the zoom, it might complicate things a little. There is no easy way. You would need to the deal with gestures on the app level and distribute the events to the controls (i.e. it wouldn’t work as it is done in the example attaching to the events on the canvas control)
LikeLike