How to save finger painting from SKCanvasView into file?

Guides on Xamarin Forms contain nice examples on how to capture user input using SKCanvasView (https://developer.xamarin.com/guides/xamarin-forms/advanced/skiasharp/paths/finger-paint/)
Unfortunately, those examples don't explain how to save captured finger painting into any graphic file, png for example.

Is it possible at all and could anyone provide a small example of it if yes?
Any help would be greatly appreciated

Answers

  • mattleibowmattleibow ZAXamarin Team Xamurai

    Saving this could be done with a PDF or SVG using the SKDocument or SKSvgCanvas types respectively, or, you can just create a new SKBitmap.

    These three ways will require you to track the paths traced, and any paint (color, width) and then store those in variables. Then you can just re-draw these onto any canvas that you construct. The example basically records all the touches and paths, and then just draws them in the paint method. There is nothing stopping you from creating a new surface on a save button click:

    void OnSaveClicked(object sender, EventArgs e)
    {
        var surface = SKSurface.Create(...);
        var canvas = surface.Canvas;
        canvas.Clear();
    
        foreach (SKPath path in completedPaths)
            canvas.DrawPath(path, paint);
    
        foreach (SKPath path in inProgressPaths.Values)
            canvas.DrawPath(path, paint);
    
        canvas.Flush();
    
        var snap = surface.Snapshot();
        var pngImage = snap.Encode();
    }
    
  • Hi Matthew,

    Could you elaborate on your var surface = SKSurface.Create(...); ?

    My guess is that it is supposed to be something like the following statement (where canvasView is the mane of SKCanvasView control defined in XAML file) but I'm not sure on the 3rd and 4th arguments:

    var surface = SKSurface.Create((int)canvasView.CanvasSize.Width, (int)canvasView.CanvasSize.Height, SKColorType.Rgba8888, SKAlphaType.Unknown);

    Thank you

  • mattleibowmattleibow ZAXamarin Team Xamurai

    That is correct. you could also create a new surface using the canvasView's info:

    var info = new SKImageInfo(canvasView.CanvasSize.Width, canvasView.CanvasSize.Height);
    var newSurface = SKSurface.Create(info);
    

    This will create a surface that matches what you see on the screen (the last rendered frame).

  • Hi Matthew,

    Thank you for your answer.

    Using the following code for surface creation, I hadn't been able to get a snapshot:

    var surface = SKSurface.Create((int)canvasView.CanvasSize.Width, (int)canvasView.CanvasSize.Height, SKColorType.Rgba8888, SKAlphaType.Unknown);
    

    The following code just returned null:

    var snap = surface.Snapshot();
    

    Above code returns snapshot if I use your suggestion for surface creation:

        var info = new SKImageInfo(canvasView.CanvasSize.Width, canvasView.CanvasSize.Height);
        var newSurface = SKSurface.Create(info);
    

    Thank you very much for your help with that part. Now I'm stuck with the last line of your example:

    var pngImage = snap.Encode();
    

    My understanding was that above line returns stream which contains png image data.
    I saved that stream as a file with png extension into file system but no one Windows program recognizes saved file as a valid.
    Is my assumption wrong and some additional code supposed to be written for getting valid png data before saving into file system or there is some special way for saving that stream for getting valid png file?

    Thank you

  • Hi Matthew,

    I'm able to save painting into file if I explicitly specify height and width of SKCanvasView element using HeightRequest and WidthRequest attributes:

    <skia:SKCanvasView x:Name="canvasView" BackgroundColor="White" HeightRequest="200" HorizontalOptions="Start" PaintSurface="CanvasView_PaintSurface" VerticalOptions="Start" WidthRequest="400" />
    
  • mattleibowmattleibow ZAXamarin Team Xamurai
    edited September 8

    The issue with your new surface is probably due to you color type and alpha type. In order to save properly, you usually need to specify a 32-bit color (which you did - partially) and a premultiplied alpha type. When I created the SKImageInfo, this was done automatically.

    var surface = SKSurface.Create(
        (int)canvasView.CanvasSize.Width,
        (int)canvasView.CanvasSize.Height,
        SKImageInfo.PlatformColorType, // SKImageInfo knows what colors to use on each platform
        SKAlphaType.Premul); // Premul instead of Unknown (which can't be saved)
    

    One thing to be aware of is that the alpha type and color type always need to be specified - Unknown will never work properly. Another thing is that on iOS, Android, Mac and some linux, the color type Rgba8888 is valid. Windows and some other linux use Bgra8888. If you use the wrong type on the wrong OS, you will just get a null surface. Thus, it is always best to use SKImageInfo.PlatformColorType.

    Another reason why you can save only when you specified the width and height is that maybe you were trying to save before the first paint operation. the CanvasSize is only calculated before the save is about to take place. This is because the size may change between paints, even though the view remains the same - the scaling/density may change.

  • Hi Matthew,

    Your suggestion work perfectly.

    Thank you very much.

Sign In or Register to comment.