SkiaSharp での指による描画

指を使用してキャンバスに描画します。

SKPath オブジェクトは継続的に更新して表示できます。 この機能を使用すると、指による描画プログラムなどの対話型描画にパスを使用できます。

指による描画の演習

Xamarin.Forms のタッチ サポートは画面上の個々の指を追跡しないため、追加のタッチ サポートを提供するために、Xamarin.Forms タッチ トラッキング エフェクトが開発されました。 このエフェクトについては、「エフェクトからのイベントの呼び出し」を参照してください。 サンプル プログラムには、指による描画プログラムなど、SkiaSharp を使用する 2 つのページが含まれています。

サンプル ソリューションには、このタッチ追跡イベントが含まれています。 .NET Standard ライブラリ プロジェクトには、TouchEffect クラス、TouchActionType 列挙型、TouchActionEventHandler デリゲート、および TouchActionEventArgs クラスが含まれています。 各プラットフォームのプロジェクトには、そのプラットフォームの TouchEffect クラスが含まれています。iOS プロジェクトには TouchRecognizer クラスも含まれています。

SkiaSharpFormsDemosFinger Paint ページは、指による描画のシンプルな実装です。 色やストロークの幅を選択することはできません。キャンバスをクリアする方法はなく、もちろんアートワークを保存することはできません。

FingerPaintPage.xaml ファイルは、SKCanvasView を単一セルの Grid に配置し、その GridTouchEffect をアタッチします。

<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>

TouchEffect を直接 SKCanvasView にアタッチしても、すべてのプラットフォームで機能するわけではありません。

FingerPaintPage.xaml.cs 分離コード ファイルは、SKPath オブジェクトを保存するための 2 つのコレクションと、これらのパスをレンダリングするための SKPaint オブジェクトを定義します。

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

名前が示すように、inProgressPaths ディクショナリには、現在 1 本以上の指で描画されているパスが保存されます。 ディクショナリ キーは、タッチ イベントに付随するタッチ ID です。 completedPaths フィールドは、パスを描画していた指が画面から離れたときに完結したパスのコレクションです。

TouchAction ハンドラーは、これら 2 つのコレクションを管理します。 指が最初に画面に触れると、新しい SKPathinProgressPaths に追加されます。 その指が移動すると、パスに点が追加されます。 指が離れると、パスが completedPaths コレクションに転送されます。 複数の指で同時に描画できます。 パスまたはコレクションのいずれかに変更を行うたびに、SKCanvasView が無効になります。

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

タッチ トラッキング イベントに付随する点は Xamarin.Forms 座標です。これらは SkiaSharp 座標 (ピクセル) に変換する必要があります。 これが ConvertToPixel メソッドの目的です。

次に、PaintSurface ハンドラーがパスの両方のコレクションをシンプルにレンダリングします。 前に完結したパスは、進行中のパスの下に表示されます。

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

指による描画を制限するものは、才能のみです。

[Finger Paint] ページのトリプル スクリーンショット

ここまで、パラメトリック方程式を使用して線を描画し、曲線を定義する方法を見てきました。 SkiaSharp の曲線とパスに関する後続のセクションでは、SKPath がサポートするさまざまな種類の曲線について説明します。 しかし、前提条件として「SkiaSharp の変換」を確認すると有益です。