Search code examples
c#xamarinaccess-violationskiasharpmr.gestures

C#: AccessViolationException when drawing in SKCanvas


Introduction

I am creating my first Xamarin app (that will target UWP first, then Android, finally maybe iOS).

Basically, the app should detect multiple fingers and circles will pop over each finger and follow them.

The problem

I am having an System.AccessViolationException when trying to draw on a staic SKCanvas on my onPanning method from MR.gestures.

Screenshot

AccessViolationException on UWP app and Visual Studio 2017

Details

L'exception System.AccessViolationException s'est produite
HResult=0x80004003 Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Source= Arborescence des appels de procédure : à SkiaSharp.SkiaApi.sk_canvas_draw_circle(IntPtr t, Single cx, Single cy, Single radius, IntPtr paint) à SkiaSharp.SKCanvas.DrawCircle(Single cx, Single cy, Single radius, SKPaint paint) à App1.MainPage.drawCircleOnCanvas(Point pointerPos, Int32 radius) dans d:\Profiles\qpollet\Documents\Visual Studio 2017\Projects\App1\App1\App1\MainPage.xaml.cs :ligne 70 à App1.MainPage.onPanning(Object sender, PanEventArgs e) dans d:\Profiles\qpollet\Documents\Visual Studio 2017\Projects\App1\App1\App1\MainPage.xaml.cs :ligne 54 à MR.Gestures.GestureHandler.<>c__DisplayClass115_0`1.b__0() à Xamarin.Forms.Platform.UWP.WindowsBasePlatformServices.<>c__DisplayClass2_0.b__0()

The code

MainPage.xaml.cs

using SkiaSharp;
using SkiaSharp.Views.Forms;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using MR.Gestures;

namespace App1
{
    public partial class MainPage : Xamarin.Forms.ContentPage
    {
        private static SKCanvas canvas;

        public MainPage()
        {
            InitializeComponent();
        }

        private void OnPainting(object sender, SKPaintSurfaceEventArgs e)
        {
            canvas = e.Surface.Canvas;

            Point pointerPos = new Point
            {
                X = 200,
                Y = 400
            };
            drawCircleOnCanvas(pointerPos, 50);

            Point pointerPos1 = new Point
            {
                X = 1000,
                Y = 600
            };
            drawCircleOnCanvas(pointerPos1, 50);
        }

        private void onPanning(object sender, PanEventArgs e)
        {
            Debug.WriteLine("MR Panning !!!!!");

            try
            {
                List<Point> touches = e.Touches.ToList();
                //foreach (Point touch in touches)
                for(int i = 0; i <= touches.Count; i++)
                {
                    Point touch = touches[i];
                    Debug.WriteLine("index " + i + " : " + touch.X + " " + touch.Y);
                    drawCircleOnCanvas(touch, 50);
                }
            }
            catch (ArgumentNullException) { }
        }

        internal void drawCircleOnCanvas(Point pointerPos, int radius)
        {
            Debug.WriteLine(pointerPos.X + " " + pointerPos.Y);

            SKPaint circleFill = new SKPaint
            {
                IsAntialias = true,
                Style = SKPaintStyle.Fill,
                Color = SKColors.Red
            };
            canvas.DrawCircle(((float) pointerPos.X - radius), ((float) pointerPos.Y - radius), radius, circleFill);
        }
    }
}

MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:App1"
             xmlns:views="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             xmlns:mr="clr-namespace:MR.Gestures;assembly=MR.Gestures"
             x:Class="App1.MainPage">

    <Label Text="Ooooh, you touch my tralala" 
           VerticalOptions="Center" 
           HorizontalOptions="Center" />

    <RelativeLayout>

        <mr:AbsoluteLayout
            RelativeLayout.XConstraint="{ConstraintExpression Type=Constant, Constant=0}"
            RelativeLayout.YConstraint="{ConstraintExpression Type=Constant, Constant=0}"
            RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width}"
            RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToParent, Property=Height}"

            BackgroundColor="Blue"

            x:Name="touchLayout"
            Panning="onPanning">

        </mr:AbsoluteLayout>

        <views:SKCanvasView
            RelativeLayout.XConstraint="{ConstraintExpression Type=Constant, Constant=0}"
            RelativeLayout.YConstraint="{ConstraintExpression Type=Constant, Constant=0}"
            RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToParent, Property=Width}"
            RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToParent, Property=Height}"

            PaintSurface="OnPainting"/>

    </RelativeLayout>
</ContentPage>

Result Xamarin SKCanvas screenshot on Windows 10 emulator

Any idea ?


Solution

  • You will get that error because you are saving the canvas. The canvas is recreated on each frame render, so you cannot keep a handle to it.

    The correct way is to only ever reference the canvas INSIDE the paint method. Then, in your padding method, just invalidate the surface using InvalidateSurface.

    This will trigger a redraw, and then you can draw the items in new locations.

    class MainPage {
        List<Point> touches;
        void onPan() {
            touches = GetTouchPoints();
            view.InvalidateSurface();
        }
        void onPaint() {
            var canvas = ...;
            canvas.Draw(...);
        }
    }