Skaisharp Bind CroppedBitmap

Viewed 28

I followed this tutorial to create an Image cropping page https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/bitmaps/cropping. However i now bound two views of the PhotoCropperCanvasView to an carousel view an wanted to Bind the CroppedBitMap so that i can access this property directl in the viewmodel. I just cannot figure ouit how i would achieve that. I f i just make this a Bindable Property the property does not change when i make a new rectangle. So i think i kind of have to exclude the Code of the property but i am very confused.

The whole ocde:

public class PhotoCropperCanvasView : SKCanvasView, INotifyPropertyChanged
    {
        const int CORNER = 50;      // pixel length of cropper corner
        const int RADIUS = 100;     // pixel radius of touch hit-test

        //SKBitmap bitmap;
        //CroppingRectangle croppingRect;
        SKMatrix inverseBitmapMatrix;

        public SKBitmap testmap;

        //public SKBitmap bitmap { get; set; }

        public Image testImage { get; set; }

        public static readonly BindableProperty mapProperty =
            BindableProperty.Create(nameof(map), typeof(SKBitmap), typeof(PhotoCropperCanvasView), null);

        public SKBitmap map
        {
            get
            {
                return (SKBitmap)GetValue(mapProperty);
            }
            set
            {
                SetValue(mapProperty, value);
            }
        }

        public CroppingRectangle croppingRect { get; set; }
        

        
        //public SKMatrix inverseBitmapMatrix { get; set; }

        public static readonly BindableProperty bitmapProperty = 
            BindableProperty.Create(nameof(bitmap), typeof(SKBitmap), typeof(Image),null,propertyChanged: OnbitmapChanged);

        static void OnbitmapChanged(BindableObject bindable, object oldValue, object newValue)
        {
            Console.WriteLine("test");
        }

        public SKBitmap bitmap
        {
            get
            {
                return (SKBitmap)GetValue(bitmapProperty);
            }
            set
            {
                SetValue(bitmapProperty, value);
            }
        }


        
        public SKBitmap CroppedBitmap
        {
            get
            {
                SKRect cropRect = new SKRect(croppingRect.Rect.Left,croppingRect.Rect.Top,croppingRect.Rect.Right,croppingRect.Rect.Bottom);
                SKBitmap croppedBitmap = new SKBitmap((int)cropRect.Width,
                                                      (int)cropRect.Height);
                SKRect dest = new SKRect(0, 0, cropRect.Width, cropRect.Height);
                SKRect source = new SKRect(cropRect.Left, cropRect.Top,
                                           cropRect.Right, cropRect.Bottom);

                using (SKCanvas canvas = new SKCanvas(croppedBitmap))
                {
                    canvas.DrawBitmap(bitmap, source, dest);
                }

                return croppedBitmap;

            }
        }



        // Touch tracking 
        TouchEffect touchEffect = new TouchEffect();
        struct TouchPoint
        {
            public int CornerIndex { set; get; }
            public SKPoint Offset { set; get; }
        }

        Dictionary<long, TouchPoint> touchPoints = new Dictionary<long, TouchPoint>();

        // Drawing objects
        SKPaint cornerStroke = new SKPaint
        {
            Style = SKPaintStyle.Stroke,
            Color = SKColors.White,
            StrokeWidth = 10
        };

        SKPaint edgeStroke = new SKPaint
        {
            Style = SKPaintStyle.Stroke,
            Color = SKColors.White,
            StrokeWidth = 2
        };
            // this constructor for profile image
        public PhotoCropperCanvasView(SKBitmap bitmap, float? aspectRatio = null)
        {
            this.bitmap = bitmap;

            SKRect bitmapRect = new SKRect(0, 0, bitmap.Width, bitmap.Height);
            croppingRect = new CroppingRectangle(bitmapRect, aspectRatio);

            touchEffect.TouchAction += OnTouchEffectTouchAction;
        }
        // this constructor for post images
        public PhotoCropperCanvasView()
        {
        }

        protected override void OnPropertyChanged([CallerMemberName] string propertyName = nameof(bitmap))
        {
            base.OnPropertyChanged(propertyName);
            if (bitmap != null)
            {
                SKRect bitmapRect = new SKRect(0, 0, bitmap.Width, bitmap.Width);
                croppingRect = new CroppingRectangle(bitmapRect, 1);

                touchEffect.TouchAction += OnTouchEffectTouchAction;
            }
        }

        protected override void OnParentSet()
        {
            base.OnParentSet();

            // Attach TouchEffect to parent view
            Parent.Effects.Add(touchEffect);
        }

        protected override void OnPaintSurface(SKPaintSurfaceEventArgs args)
        {
            base.OnPaintSurface(args);

            SKImageInfo info = args.Info;
            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;

            canvas.Clear(SKColors.Gray);

            // Calculate rectangle for displaying bitmap 
            float scale = Math.Min((float)info.Width / bitmap.Width, (float)info.Height / bitmap.Height);
            float x = (info.Width - scale * bitmap.Width) / 2;
            float y = (info.Height - scale * bitmap.Height) / 2;
            SKRect bitmapRect = new SKRect(x, y, x + scale * bitmap.Width, y + scale * bitmap.Height);
            canvas.DrawBitmap(bitmap, bitmapRect);

            // Calculate a matrix transform for displaying the cropping rectangle
            SKMatrix bitmapScaleMatrix = SKMatrix.MakeIdentity();
            bitmapScaleMatrix.SetScaleTranslate(scale, scale, x, y);

            // Display rectangle
            SKRect scaledCropRect = bitmapScaleMatrix.MapRect(croppingRect.Rect);
            canvas.DrawRect(scaledCropRect, edgeStroke);

            // Display heavier corners
            using (SKPath path = new SKPath())
            {
                path.MoveTo(scaledCropRect.Left, scaledCropRect.Top + CORNER);
                path.LineTo(scaledCropRect.Left, scaledCropRect.Top);
                path.LineTo(scaledCropRect.Left + CORNER, scaledCropRect.Top);

                path.MoveTo(scaledCropRect.Right - CORNER, scaledCropRect.Top);
                path.LineTo(scaledCropRect.Right, scaledCropRect.Top);
                path.LineTo(scaledCropRect.Right, scaledCropRect.Top + CORNER);

                path.MoveTo(scaledCropRect.Right, scaledCropRect.Bottom - CORNER);
                path.LineTo(scaledCropRect.Right, scaledCropRect.Bottom);
                path.LineTo(scaledCropRect.Right - CORNER, scaledCropRect.Bottom);

                path.MoveTo(scaledCropRect.Left + CORNER, scaledCropRect.Bottom);
                path.LineTo(scaledCropRect.Left, scaledCropRect.Bottom);
                path.LineTo(scaledCropRect.Left, scaledCropRect.Bottom - CORNER);

                canvas.DrawPath(path, cornerStroke);
            }

            // Invert the transform for touch tracking
            bitmapScaleMatrix.TryInvert(out inverseBitmapMatrix);
        }

        void OnTouchEffectTouchAction(object sender, TouchActionEventArgs args)
        {
            int i = 0;
            SKPoint pixelLocation = ConvertToPixel(args.Location);
            SKPoint bitmapLocation = inverseBitmapMatrix.MapPoint(pixelLocation);

            switch (args.Type)
            {
                case TouchActionType.Pressed:
                    // Convert radius to bitmap/cropping scale
                    float radius = inverseBitmapMatrix.ScaleX * RADIUS;

                    // Find corner that the finger is touching
                    int cornerIndex = croppingRect.HitTest(bitmapLocation, radius);

                    if (cornerIndex != -1 && !touchPoints.ContainsKey(args.Id))
                    {
                        TouchPoint touchPoint = new TouchPoint
                        {
                            CornerIndex = cornerIndex,
                            Offset = bitmapLocation - croppingRect.Corners[cornerIndex]
                        };

                        touchPoints.Add(args.Id, touchPoint);
                    }
                    break;

                case TouchActionType.Moved:
                    if (touchPoints.ContainsKey(args.Id))
                    {
                        TouchPoint touchPoint = touchPoints[args.Id];
                        croppingRect.MoveCorner(touchPoint.CornerIndex, 
                                                bitmapLocation - touchPoint.Offset);
                        InvalidateSurface();
                    }
                    break;
                      
                case TouchActionType.Released:
                case TouchActionType.Cancelled:
                    if (touchPoints.ContainsKey(args.Id))
                    {
                        touchPoints.Remove(args.Id);
                        //map = CroppedBitmap;
                    }
                    break;
            }
            
        }

        SKPoint ConvertToPixel(Xamarin.Forms.Point pt)
        {
            return new SKPoint((float)(CanvasSize.Width * pt.X / Width),
                                (float)(CanvasSize.Height * pt.Y / Height));
        }
    }
}

Since the case TouchActionType.Cancelled gets only triggerd once everytime the rectangel was moved, i thought i would set thew bindable Proeprty map to the Cropped bitmap property so that i can get the Cropped Image from the view obver a Binding to the viewmodel. This part works, however, when i activate the line map = CroppedBitmap the cropping rectangle can only be moved by opposite corners. So if i start moving it with the bottom right corner i con only use the top left or bottom right. If i leave the line map = CroppedBitman(249) deactivated i can move the rectangle on all corners at every times. I do not understand this behaviour.

the view:

<CarouselView Grid.Row="0"
                          IsSwipeEnabled="False"
                          x:Name="carousel"
                          Margin="0,-40,0,0"
                          CurrentItem="{Binding CurrentCutImage, Mode=TwoWay}"
                          CurrentItemChanged="CarouselView_CurrentItemChanged"
                          HorizontalScrollBarVisibility="Always"
                            
                          IsScrollAnimated="True"
                          ItemsSource="{Binding ImageObjects}"
                          VerticalScrollBarVisibility="Always"
                           
                          >
               <CarouselView.ItemTemplate>
                    <DataTemplate x:DataType="viewmodel:CutImages">
                        <Grid>
                            <bitmaps:PhotoCropperCanvasView bitmap="{Binding ImageSource }" map="{Binding MapSource, Mode=TwoWay}" >

                            </bitmaps:PhotoCropperCanvasView>
                        </Grid>
                    </DataTemplate>
                </CarouselView.ItemTemplate> 
            </CarouselView>

and the VM:

 public partial class CutImagesViewModel : ObservableObject
    {
        // == observable properties ==
        [ObservableProperty]
        public Collection<CutImages> imageObjects = new Collection<CutImages>();

        [ObservableProperty]
        CutImages currentCutImage;

        

        [ObservableProperty]
        public SKBitmap maps;

        

        public CutImagesViewModel(Collection<SKBitmapImageSource> images)
        {
            foreach(var image in images)
            {
                ImageObjects.Add(new CutImages(image));
            }
            this.CurrentCutImage = this.ImageObjects.FirstOrDefault();
        }
    }

    public partial class CutImages : ObservableObject
    {
        [ObservableProperty]
        public SKBitmap imageSource;

        [ObservableProperty]
        public SKBitmap mapSource;

        partial void OnMapSourceChanged(SKBitmap value)
        {
            if(!images.Contains(value))
            {
                images.Add(value);
            }
            
        }

        [ObservableProperty]
        Collection<SKBitmap> images = new Collection<SKBitmap>();

        public CutImages(ImageSource imageSource)
        {
            SKBitmapImageSource sourceImage = (SKBitmapImageSource)imageSource;
            SKBitmap image = sourceImage;
            ImageSource = image;
        }
    }

0 Answers
Related