Finger Painting in SkiaSharp
Use your fingers to paint on the canvas.
An SKPath
object can be continually updated and displayed. This feature allows a path to be used for interactive drawing, such as in a finger-painting program.
The touch support in Xamarin.Forms does not allow tracking individual fingers on the screen, so a Xamarin.Forms touch-tracking effect has been developed to provide additional touch support. This effect is described in the article Invoking Events from Effects. The sample program includes two pages that use SkiaSharp, including a finger-painting program.
The sample solution includes this touch-tracking event. The .NET Standard library project includes the TouchEffect
class, the TouchActionType
enumeration, the TouchActionEventHandler
delegate, and the TouchActionEventArgs
class. Each of the platform projects includes a TouchEffect
class for that platform; the iOS project also contains a TouchRecognizer
class.
The Finger Paint page in SkiaSharpFormsDemos is a simplified implementation of finger painting. It does not allow selecting color or stroke width, it has no way to clear the canvas, and of course you can't save your artwork.
The FingerPaintPage.xaml file puts the SKCanvasView
in a single-cell Grid
and attaches the TouchEffect
to that Grid
:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
xmlns:tt="clr-namespace:TouchTracking"
x:Class="SkiaSharpFormsDemos.Paths.FingerPaintPage"
Title="Finger Paint">
<Grid BackgroundColor="White">
<skia:SKCanvasView x:Name="canvasView"
PaintSurface="OnCanvasViewPaintSurface" />
<Grid.Effects>
<tt:TouchEffect Capture="True"
TouchAction="OnTouchEffectAction" />
</Grid.Effects>
</Grid>
</ContentPage>
Attaching the TouchEffect
directly to the SKCanvasView
does not work under all platforms.
The FingerPaintPage.xaml.cs code-behind file defines two collections for storing the SKPath
objects, as well as an SKPaint
object for rendering these paths:
public partial class FingerPaintPage : ContentPage
{
Dictionary<long, SKPath> inProgressPaths = new Dictionary<long, SKPath>();
List<SKPath> completedPaths = new List<SKPath>();
SKPaint paint = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.Blue,
StrokeWidth = 10,
StrokeCap = SKStrokeCap.Round,
StrokeJoin = SKStrokeJoin.Round
};
public FingerPaintPage()
{
InitializeComponent();
}
...
}
As the name suggests, the inProgressPaths
dictionary stores the paths that are currently being drawn by one or more fingers. The dictionary key is the touch ID that accompanies the touch events. The completedPaths
field is a collection of paths that were finished when a finger that was drawing the path lifted from the screen.
The TouchAction
handler manages these two collections. When a finger first touches the screen, a new SKPath
is added to inProgressPaths
. As that finger moves, additional points are added to the path. When the finger is released, the path is transferred to the completedPaths
collection. You can paint with multiple fingers simultaneously. After each change to one of the paths or collections, the SKCanvasView
is invalidated:
public partial class FingerPaintPage : ContentPage
{
...
void OnTouchEffectAction(object sender, TouchActionEventArgs args)
{
switch (args.Type)
{
case TouchActionType.Pressed:
if (!inProgressPaths.ContainsKey(args.Id))
{
SKPath path = new SKPath();
path.MoveTo(ConvertToPixel(args.Location));
inProgressPaths.Add(args.Id, path);
canvasView.InvalidateSurface();
}
break;
case TouchActionType.Moved:
if (inProgressPaths.ContainsKey(args.Id))
{
SKPath path = inProgressPaths[args.Id];
path.LineTo(ConvertToPixel(args.Location));
canvasView.InvalidateSurface();
}
break;
case TouchActionType.Released:
if (inProgressPaths.ContainsKey(args.Id))
{
completedPaths.Add(inProgressPaths[args.Id]);
inProgressPaths.Remove(args.Id);
canvasView.InvalidateSurface();
}
break;
case TouchActionType.Cancelled:
if (inProgressPaths.ContainsKey(args.Id))
{
inProgressPaths.Remove(args.Id);
canvasView.InvalidateSurface();
}
break;
}
}
...
SKPoint ConvertToPixel(Point pt)
{
return new SKPoint((float)(canvasView.CanvasSize.Width * pt.X / canvasView.Width),
(float)(canvasView.CanvasSize.Height * pt.Y / canvasView.Height));
}
}
The points accompanying the touch-tracking events are Xamarin.Forms coordinates; these must be converted to SkiaSharp coordinates, which are pixels. That's the purpose of the ConvertToPixel
method.
The PaintSurface
handler then simply renders both collections of paths. The earlier completed paths appear underneath the paths in progress:
public partial class FingerPaintPage : ContentPage
{
...
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKCanvas canvas = args.Surface.Canvas;
canvas.Clear();
foreach (SKPath path in completedPaths)
{
canvas.DrawPath(path, paint);
}
foreach (SKPath path in inProgressPaths.Values)
{
canvas.DrawPath(path, paint);
}
}
...
}
Your finger paintings are only limited by your talent:
You've now seen how to draw lines and to define curves using parametric equations. A later section on SkiaSharp Curves and Paths covers the various types of curves that SKPath
supports. But a useful prerequisite is an exploration of SkiaSharp Transforms.