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;
}
}
}