Dibujo con los dedos en SkiaSharp

Download SampleDescargar el ejemplo

Use los dedos para dibujar en el lienzo.

Un objeto SKPath se puede actualizar y mostrar continuamente. Esta característica permite usar una ruta de acceso para el dibujo interactivo, como en un programa para dibujar con los dedos.

An exercise in finger painting

La compatibilidad táctil en Xamarin.Forms no permite hacer un seguimiento de los dedos en la pantalla, por lo que se ha desarrollado un efecto de seguimiento táctil en Xamarin.Forms para proporcionar compatibilidad táctil adicional. Este efecto se describe en el artículo Invocación de eventos desde efectos. El programa de ejemplo Demostraciones de efectos de seguimiento táctil incluye dos páginas que usan SkiaSharp, incluido un programa de dibujo con los dedos.

La solución SkiaSharpFormsDemos incluye este evento de seguimiento táctil. El proyecto de biblioteca de .NET Standard incluye la clase TouchEffect, la enumeración TouchActionType, el delegado TouchActionEventHandler y la clase TouchActionEventArgs. Cada uno de los proyectos de plataforma incluye una clase TouchEffect para esa plataforma; el proyecto de iOS también contiene una clase TouchRecognizer.

La página Finger Paint en SkiaSharpFormsDemos es una implementación simplificada para dibujar con los dedos. No permite seleccionar el color ni el ancho del trazo, no hay forma de borrar el lienzo y, por supuesto, no se puede guardar el dibujo.

El archivo FingerPaintPage.xaml coloca SKCanvasView en un objeto Grid de una sola celda y adjunta el efecto TouchEffect a ese objeto 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>

Adjuntar el efecto TouchEffect directamente a SKCanvasView no funciona en todas las plataformas.

El archivo de código subyacente FingerPaintPage.xaml.cs define dos colecciones para almacenar los objetos SKPath, así como un objeto SKPaint para representar estos trazados:

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();
    }
    ...
}

Como sugiere su nombre, el diccionario inProgressPaths almacena los trazados que están dibujando actualmente uno o varios dedos. La clave de diccionario es el identificador táctil que acompaña a los eventos táctiles. El campo completedPaths es una colección de trazados que se terminaron cuando el dedo que los estaba dibujando se levantó de la pantalla.

El manipulador TouchAction administra estas dos colecciones. Cuando un dedo toca por primera vez la pantalla, se agrega un nuevo objeto SKPath a inProgressPaths. A medida que se mueve ese dedo, se agregan puntos adicionales al trazado. Cuando se levanta el dedo, el trazado se transfiere a la colección completedPaths. Puede dibujar con varios dedos simultáneamente. Después de cada cambio en uno de los trazados o de las colecciones, SKCanvasView se invalida:

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));
    }
}

Los puntos que acompañan a los eventos de seguimiento táctil son coordenadas de Xamarin.Forms, las cuales deben convertirse en coordenadas de SkiaSharp, que son píxeles. Ese es el propósito del método ConvertToPixel.

El manipulador PaintSurface simplemente representa ambas colecciones de trazados. Los trazados completados anteriormente aparecen debajo de los trazados en curso:

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);
        }
    }
    ...
}

El dibujo con los dedos solo está limitado por su talento:

Triple screenshot of the Finger Paint page

Ya ha visto cómo dibujar líneas y definir curvas mediante ecuaciones paramétricas. En una sección posterior sobre las curvas y trazados de SkiaSharp se describen los distintos tipos de curvas que admite SKPath. Pero un requisito previo útil es explorar las transformaciones de SkiaSharp.