[SkiaSharp] Attempting to draw a bitmap from an existing file

MHaider22MHaider22 CAMember ✭✭
edited June 2018 in Xamarin.Forms

Hello, I'm currently attempting to load an image on a canvas but so far I have only been able to load it once if I create a SKCanvasView immediately after InitializeComponents() on a page. Creating a canvas in xaml results in the bitmap not rendering.
I have checked the size of the canvas,canvasView, the loaded bitmap and both height and width are greater than zero. What else am I missing here?
I have looked up the skia sharp samples to help in my search and took pieces from the TouchManipulationBitmap. For now what I would like to accomplish is have a bitmap that is rendered onto a canvas with its ratio respected(not stretched)

Here is the xaml

<?xml version="1.0" encoding="utf-8" ?>

<Grid x:Name="GridView"
      BackgroundColor="Azure">

    <skia:SKCanvasView x:Name="CanvasView"
                       PaintSurface="OnCanvasViewPaintSurface"
                       BackgroundColor="Transparent"
                       Grid.Row="0"/>
    <StackLayout Orientation="Horizontal"
                 Grid.Row="0"
                 VerticalOptions="End"
                 x:Name="StackLView"
                 HeightRequest="35"
                 HorizontalOptions="EndAndExpand"
                 Padding="10,0,10,0">
        <Button Text="Cancel"
                Command="{Binding CancelCommand}" />
        <Button Text="Apply"
                x:Name="ApplyButton"
                Command="{Binding ApplyCommand}" />
    </StackLayout>
    <Grid.Effects>
        <effects:TouchEffect Capture="True"
                             TouchAction="OnTouchEffectAction" />
    </Grid.Effects>
</Grid>

The PhotoPage looks like this

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Xamarin.Forms;

    namespace PhotoCropping.Views
    {
        public abstract class PhotoPage: ContentPage
        { 
            public string PhotoPath
            {
                get { return (string)GetValue(PhotoPathProperty); }
                set { SetValue(PhotoPathProperty, value); }
            }

            public static BindableProperty PhotoPathProperty = BindableProperty.Create(
                    propertyName: nameof(PhotoPath),
                    returnType: typeof(string),
                    declaringType: typeof(PhotoEditPage),
                    propertyChanged: PhotoPathChanged
                );
            public static void PhotoPathChanged(BindableObject bindable, object oldValue, object newValue)
            {
                if (bindable is PhotoEditPage page)
                {
                    var path = newValue as string;
                    page.UpdateBitmapFromPath(path);
                }
            }

            public abstract void UpdateBitmapFromPath(string path);


        }
    }

**The code behind for the page **

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    using Xamarin.Forms;
    using Xamarin.Forms.Xaml;
    using SkiaSharp;
    using SkiaSharp.Views.Forms;
    using System.ComponentModel;
    using System.IO;
    using Plugin.Media;

    namespace PhotoCropping.Views
    {
        [XamlCompilation(XamlCompilationOptions.Compile)]
        public partial class PhotoEditPage : PhotoPage
        {
            private TouchManipulationBitmap _photo;
            private HashSet<long> _touchIds = new HashSet<long>();
            private MatrixDisplay matrixDisplay = new MatrixDisplay();
            //because we need to hook up CanvasView.PaintSurface twice -once during initialization and a second time on view.OnAppearing
            //this flag is needed to avoid double hooks. The view unhooks this even when dissapears, subsequently re-hooks on appearing
            private bool _initialized = false;

            public PhotoEditPage()
            {
                InitializeComponent();

            }

            private async void V_Clicked(object sender, EventArgs e)
            {
                if (_photo == null  )
                {
                    _photo = new TouchManipulationBitmap()
                    {
                        TouchManager = new TouchManipulationManager()
                    };
                    _photo.TouchManager.Mode = TouchManipulationMode.ScaleRotate | TouchManipulationMode.IsotropicScale;

                }
                var file = await CrossMedia.Current.PickPhotoAsync();
                _photo.UpdateBitmapFromStream(file.GetStream());
                CanvasView.InvalidateSurface();
            }

            private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
            {
                var info = args.Info;
                var surface = args.Surface;
                var canvas = surface.Canvas;

                // Display the bitmap
                if (_photo != null && _photo.Bitmap != null)
                {
                    canvas.Clear();

                    _photo?.Paint(args);
                }
            }

            protected override void OnDisappearing()
            {
                base.OnDisappearing();
                CanvasView.PaintSurface -= OnCanvasViewPaintSurface;
            //  ApplyButton.Clicked -= V_Clicked;
            }
            protected override void OnAppearing()
            {
                base.OnAppearing();
            //  ApplyButton.Clicked += V_Clicked;
                if (_initialized)
                {
                    CanvasView.PaintSurface += OnCanvasViewPaintSurface;
                }
                else
                {
                    _initialized = true;
                }
            }



            public override void UpdateBitmapFromPath(string path)
            {
                if (_photo == null && path != null)
                {
                    _photo = new TouchManipulationBitmap()
                    {
                        TouchManager = new TouchManipulationManager()
                    };
                    _photo.TouchManager.Mode = TouchManipulationMode.ScaleRotate | TouchManipulationMode.IsotropicScale;

                }

                if(path != "icon.png")
                {
                    _photo.UpdateBitmapFromPath(path);
                    CanvasView.InvalidateSurface();
                }


            } 

            private void OnTouchEffectAction(object sender, TouchActionEventArgs args)
            {
                var pt = args.Location;
                var point = new SKPoint((float)(CanvasView.CanvasSize.Width * pt.X / CanvasView.Width),
                                        (float)(CanvasView.CanvasSize.Height * pt.Y / CanvasView.Height));
                switch (args.Type)
                {
                    case TouchActionType.Pressed:
                        if (_photo.HitTest(point))
                        {
                            _touchIds.Add(args.Id);
                            _photo.ProcessTouchEvent(args.Id, args.Type, point);
                        }
                        break;

                    case TouchActionType.Moved:
                        if (_touchIds.Contains(args.Id))
                        {
                            _photo.ProcessTouchEvent(args.Id, args.Type, point);
                            CanvasView.InvalidateSurface();
                        }
                        break;

                    case TouchActionType.Released:
                    case TouchActionType.Cancelled:

                        if (_touchIds.Contains(args.Id))
                        {
                            _photo.ProcessTouchEvent(args.Id, args.Type, point);
                            _touchIds.Remove(args.Id);
                            CanvasView.InvalidateSurface();
                        }
                        break;
                }
            }

        }
    }

TouchManipulationBitmap

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

using Xamarin.Forms;

using SkiaSharp;
using SkiaSharp.Views.Forms;

namespace PhotoCropping.Views
{
    public class TouchManipulationBitmap
    {
        private SKBitmap _bitmap;
        private Dictionary<long, TouchManipulationInfo> _touchDictionary = new Dictionary<long, TouchManipulationInfo>();


        public SKBitmap Bitmap
        {
            get
            {
                return _bitmap;
            }
            private set
            {
                _bitmap = value;
            }
        }
        public TouchManipulationManager TouchManager { get; set; }

        public SKMatrix Matrix { get; set; } 
        public TouchManipulationBitmap(SKBitmap bitmap)
        {
            _bitmap = bitmap;
        }
        public TouchManipulationBitmap() { }
        public void Paint(SKPaintSurfaceEventArgs args)
        {
            if(Bitmap == null)
            {
                return;
            }
            var canvas = args.Surface.Canvas;
            var info = args.Info;
            canvas.Save();
            SKMatrix matrix = Matrix;
            canvas.Concat(ref matrix);
            float scale = Math.Min((float)info.Width / Bitmap.Width,
                                  info.Height / 3f / Bitmap.Height);

            float left = (info.Width - scale * Bitmap.Width) / 2;
            float top = (info.Height / 3 - scale * Bitmap.Height) / 2;
            float right = left + scale * Bitmap.Width;
            float bottom = top + scale *  Bitmap.Height;
            SKRect rect = new SKRect(left, top, right, bottom);
            rect.Offset(0, 2 * info.Height / 3);

            canvas.DrawBitmap(Bitmap, rect);
            canvas.Restore();
        }

        public bool HitTest(SKPoint location)
        {
            //invert the matrix
            SKMatrix inverseMatrix;

            if (Matrix.TryInvert(out inverseMatrix))
            {
                var transformPoint = inverseMatrix.MapPoint(location);

                //check if it's in the untransformed bitmap rectangle
                var rect = new SKRect(0, 0, Bitmap.Width, Bitmap.Height);
                return rect.Contains(transformPoint);
            }
            return false;
        }

        internal void UpdateBitmapFromStream(Stream stream)
        {

            if (Bitmap != null)
            {
                Bitmap.Dispose();
            }
            using (MemoryStream memStream = new MemoryStream())
            {
                stream.CopyTo(memStream);
                memStream.Seek(0, SeekOrigin.Begin);
                using (SKManagedStream skStream = new SKManagedStream(memStream))
                {
                    _bitmap = SKBitmap.Decode(skStream);
                }
            }
        }

        internal void UpdateBitmapFromPath(string path)
        {
            if(Bitmap!=null)
            {
                Bitmap.Dispose();
            }
            Bitmap = SKBitmap.Decode(path);
        }

        public void ProcessTouchEvent(long id, TouchActionType type, SKPoint location)
        {
            switch (type)
            {
                case TouchActionType.Cancelled:
                    _touchDictionary.Remove(id);
                    break;

                case TouchActionType.Entered:
                    break;

                case TouchActionType.Exited:
                    break;

                case TouchActionType.Moved:
                    var info = _touchDictionary[id];
                    info.NewPoint = location;
                    Manipulate();
                    info.PreviousPoint = info.NewPoint;
                    break;

                case TouchActionType.Pressed:
                    _touchDictionary.Add(id, new TouchManipulationInfo()
                    {
                        PreviousPoint = location,
                        NewPoint = location
                    });
                    break;

                case TouchActionType.Released:
                    _touchDictionary[id].NewPoint = location;
                    Manipulate();
                    _touchDictionary.Remove(id);
                    break;
            }
        }

        private void Manipulate()
        {
            var infos = new TouchManipulationInfo[_touchDictionary.Count];
            _touchDictionary.Values.CopyTo(infos, 0);
            var touchMatrix = SKMatrix.MakeIdentity();  

            if (infos.Length == 1)
            {
                var prevPoint = infos[0].PreviousPoint;
                var newPoint = infos[0].NewPoint;
                var pivotPoint = Matrix.MapPoint(Bitmap.Width / 2, Bitmap.Height / 2);

                touchMatrix = TouchManager.OneFingerManipulation(prevPoint, newPoint, pivotPoint);
            }
            else if (infos.Length >= 2)
            {
                var pivotIndex = infos[0].NewPoint == infos[0].PreviousPoint ? 0 : 1;
                var pivotPoint = infos[pivotIndex].NewPoint;
                var newPoint = infos[1 - pivotIndex].NewPoint;
                var prevPoint = infos[1 - pivotIndex].PreviousPoint;

                touchMatrix = TouchManager.TwoFingerManipulation(prevPoint, newPoint, pivotPoint);
            }

            var matrix = Matrix;
            SKMatrix.PostConcat(ref matrix, touchMatrix);
            Matrix = matrix;
        }

    }
}
Tagged:

Best Answer

  • MHaider22MHaider22 CA ✭✭
    Accepted Answer

    I realized what my problem was. I wasn't initializing the matrix to an identity matrix.

Answers

  • MHaider22MHaider22 CAMember ✭✭

    Here are some screenshots of the values after the paint call.
    Bitmap properties at runtime
    Page properties at runtime

  • MHaider22MHaider22 CAMember ✭✭

    Any ideas?

  • MHaider22MHaider22 CAMember ✭✭
    Accepted Answer

    I realized what my problem was. I wasn't initializing the matrix to an identity matrix.

Sign In or Register to comment.