Forum Xamarin.Forms

Skiasharp for drawing and photo manipulation?

Hey Everyone,

I've been tasked to add a feature in are Xamarin.froms App for drawing over an image from the users gallery or recent photo then save the edited image back into the gallery.

Half of this feature is completed thanks to the media plugin. My Issue would come to editing the image to draw over it in xamarin. Luckily, I found the SkiaSharp nuget package that can help in creating 2D graphics in a cross-platform application.

I have not made a feature like this before in xamarin. I found that the xamarin documents lacked the information to adding the canvas into the xamarin page and that i can't seem to get the current demo of Skiasharp from github to work on my computer. So My question would be:

  • Is it possible to create the feature of editing an image with Skiasharp? is there an easier method?
    as well as:

  • How can I implement Skiasharp to work in this method?

Best Answer

Answers

  • RichardPresleyRichardPresley USMember ✭✭

    I was able to get the demo now and took it as base for my own demo. The demo simply takes a pictures then is added to the canvas of the custom Skiaview. Unfortunatly, the custom Renderer and the native Skiaview does not want to call the draw event to view the picture and edit it.Could someone check my code to see what is wrong? I am still pretty new in the custom renders in Xamarin.(Note I only implemented Android in this project)

    Edit:

    Now if only I could load the entire demo into a zip and posting it here! oh well!

    So here's the important bits:
    (Note: I'm using the Media Plugin and Skiasharp)

    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:PictureCanvas"
                 x:Class="PictureCanvas.MainPage">
    
    <StackLayout>
      <Label Text="Welcome to Xamarin Forms!"
               VerticalOptions="Center"
               HorizontalOptions="Center" /> 
      <Button x:Name="btnTakePhoto" Text="Take Picture"></Button>
      <Image x:Name="image"/>
    </StackLayout>
    
    
    </ContentPage>
    

    MainPage.xaml.cs

    namespace PictureCanvas
    {
        public partial class MainPage : ContentPage
        {
            public MainPage()
            {
                InitializeComponent();
                btnTakePhoto.Clicked += async (sender, args) =>
                {
    
                    if (!CrossMedia.Current.IsCameraAvailable || !CrossMedia.Current.IsTakePhotoSupported)
                    {
                        await DisplayAlert("No Camera", ":( No camera avaialble.", "OK");
                        return;
                    }
    
                    var file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
                    {
    
                        Directory = "Sample",
                        Name = "test.jpg"
                    });
    
                    if (file == null)
                        return;
    
                    await DisplayAlert("File Location", file.Path, "OK");
    
                    await Navigation.PushAsync(new ImageEditer(file));
                    //using (var stream = new SKManagedStream(StreamImage))
                    //using (var source = SKBitmap.Decode(stream))
                    //using (var paint = new SKPaint())
                    //{
                    //    canvas.DrawBitmap(source, SKRect.Create(width, height), paint);
                    //}
                };
            }
        }
    }
    

    Skiaview.cs

    namespace PictureCanvas
    {
        public class SkiaView : View, ISkiaViewController
        {
            MediaFile sample;
            //private SKCanvas viewcanvas;
    
            public SkiaView (MediaFile sample)
            {
                this.sample = sample;
            }
    
            void ISkiaViewController.SendDraw (SKCanvas canvas)
            {
                Draw (canvas);
            }
    
            void ISkiaViewController.SendTap ()
            {
                //sample?.TapMethod?.Invoke ();
            }
    
    
            protected virtual void Draw (SKCanvas canvas)
            {
                using (var stream = new SKManagedStream(sample.GetStream()))
                using (var source = SKBitmap.Decode(stream))
                using (var paint = new SKPaint())
                {
                    canvas.DrawBitmap(source,(int)Width, (int)Height, paint);
                }
            }
    
            void ISkiaViewController.OnTouch(SKCanvas canvas)
            {
                Draw(canvas);
            }
        }
    }
    

    ISkiaViewController.cs

    namespace PictureCanvas
    {
        public interface ISkiaViewController : IViewController
        {
            void SendDraw (SKCanvas canvas);
            void OnTouch(SKCanvas canvas);
            void SendTap ();
        }
    }
    

    In Android:

    SkiaViewRenderer.cs

    [assembly: ExportRenderer (typeof(SkiaView), typeof(SkiaViewRenderer))]
    
    namespace PictureCanvas.Droid
    {
        public class SkiaViewRenderer : ViewRenderer<SkiaView, NativeSkiaView>
        {
            NativeSkiaView view;
    
            public SkiaViewRenderer()
            {
                this.SetWillNotDraw(false);
            }
    
            protected override void OnElementChanged(ElementChangedEventArgs<SkiaView> e)
            {
                base.OnElementChanged(e);
    
                if (Control == null)
                {
                    view = new NativeSkiaView(Context, Element);
                    SetNativeControl(view);
                    this.Invalidate();
    
                }
            }
    
            public override void Draw(Canvas canvas)
            {
                base.Draw(canvas);
            }
        }
    }
    

    NativeSkiaView.cs

    namespace PictureCanvas.Droid
    {
        public class NativeSkiaView : View
        {
            private bool isdrawing;
            private Path DrawPath;
            private Paint DrawPaint;
            private Paint CanvasPaint;
            private Canvas DrawCanvas;
            private Bitmap bitmap;
            readonly SkiaView skiaView;
            readonly ISkiaViewController iskiaView;
    
            public NativeSkiaView (Android.Content.Context context, SkiaView skiaView) : base (context)
            {
    
                this.skiaView = skiaView;
                this.iskiaView = skiaView;
                //this.Click += OnTapped;
    
                isdrawing = false;
                DrawPath = new Path();
                DrawPaint = new Paint
                {
                    Color = Color.Red,
                    AntiAlias = true,
                    StrokeWidth = 15.0f
                };
    
                DrawPaint.SetStyle(Paint.Style.Stroke);
                DrawPaint.StrokeJoin = Paint.Join.Round;
                DrawPaint.StrokeCap = Paint.Cap.Round;
    
                CanvasPaint = new Paint
                {
                    Dither = true
                };
            }
    
            protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
            {
                base.OnSizeChanged(w, h, oldw, oldh);
    
                //bitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
                //DrawCanvas = new Canvas(bitmap);
    
            }
    
            protected override void OnDraw (Android.Graphics.Canvas canvas)
            {
                base.OnDraw (canvas);
    
                if (!isdrawing)
                {
                    if (bitmap == null || bitmap.Width != canvas.Width || bitmap.Height != canvas.Height)
                    {
                        if (bitmap != null)
                            bitmap.Dispose();
    
                        bitmap = Bitmap.CreateBitmap(canvas.Width, canvas.Height, Bitmap.Config.Argb8888);
    
                        DrawCanvas = new Canvas(bitmap);
    
                    }
                    try
                    {
                        using (var surface = SKSurface.Create(canvas.Width, canvas.Height, SKImageInfo.PlatformColorType, SKAlphaType.Premul, bitmap.LockPixels(), canvas.Width * 4))
                        {
                            var skcanvas = surface.Canvas;
                            skcanvas.Scale(((float)canvas.Width) / (float)skiaView.Width, ((float)canvas.Height) / (float)skiaView.Height);
                            iskiaView.SendDraw(skcanvas);
                        }
                    }
                    finally
                    {
                        bitmap.UnlockPixels();
                    }
                }
    
    
    
    
                DrawPaint.Color = Color.Red;
                canvas.DrawBitmap (bitmap, 0, 0, CanvasPaint);
                canvas.DrawPath(DrawPath, DrawPaint);
            }
    
            public override bool OnTouchEvent(MotionEvent e)
            {
                var touchX = e.GetX();
                var touchY = e.GetY();
                isdrawing = true;
                switch (e.Action)
                {
                    case MotionEventActions.Down:
                        DrawPath.MoveTo(touchX, touchY);
                        break;
                    case MotionEventActions.Move:
                        DrawPath.LineTo(touchX, touchY);
                        break;
                    case MotionEventActions.Up:
                        DrawCanvas.DrawPath(DrawPath, DrawPaint);
                        DrawPath.Reset();
    
                        break;
                    default:
                        return false;
                }
    
                Invalidate();
    
                return true;
            }
    
      //      void OnTapped(object sender, EventArgs e)
            //{
            //  iskiaView.SendTap ();
            //}
        }
    }
    
  • RichardPresleyRichardPresley USMember ✭✭

    Here's the link of the small project that I am having trouble with:
    https://www.dropbox.com/s/3thpdjraq19gm05/PictureCanvas.zip?dl=0

    Here's the Skiasharp Demo I modified to draw on the canvas that works:
    https://www.dropbox.com/s/bukcnxbpd8gule8/Skia.Forms.Demo.zip?dl=0

  • PaulRiehlePaulRiehle USMember ✭✭

    Did you ever figure this out? I am working on a very similar project and I'd love to hear if you fixed the problems you were having.

  • PaulRiehlePaulRiehle USMember ✭✭
    edited November 2016

    @RichardPresley said:
    @PaulRiehle
    I was unable to do this in Skiasharp. however I did create a custom render for android (and IOS but I did not added in the demo). you can download it here:
    https://forums.xamarin.com/discussion/72968/export-renderer-of-canvas-android-issues

    @RichardPresley, this is exactly what I was trying to do (draw over an existing image). Thank you! Any chance you could upload the iOS version as well?

    It's a shame that Skiasharp lacks any simple, working examples. Their current model of putting all examples into one big, complex application is really daunting.

  • hkiddhkidd Member ✭✭

    Hi! @PaulRiehle and @RichardPresley did you both create custom renderers in order to implement this? As extensive as SkiaSharp is I am hoping that I can manipulate their classes and views to do this, but I can't figure out how to make this happen, which is very frustrating.

Sign In or Register to comment.