SkiaSharp实现裁剪矩形随图片反向移动的问题排查
问题:实现Instagram式图片裁剪(SkiaSharp触摸变换错误)
我参考了SkiaSharp触摸变换相关内容,实现了图片的触摸移动与缩放功能。现在需要在画布上展示启动时选择的Bitmap,同时在图片上方放置一个裁剪矩形,要求该矩形随图片反向移动,始终保持固定位置。但目前无法获取正确的反转变换矩阵来驱动矩形移动,矩形很快就会移出视图,找不到矩阵计算的错误。需求是实现类似Instagram的图片裁剪方式:移动图片来选择裁剪区域。
View
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:skia="clr-namespace:SkiaSharp.Views.Maui.Controls;assembly=SkiaSharp.Views.Maui.Controls" xmlns:tt="clr-namespace:Maui.FreakyEffects.TouchTracking;assembly=Maui.FreakyEffects" xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" xmlns:local="clr-namespace:ImageCroppingTry5" x:Class="ImageCroppingTry5.CroppingView" x:DataType="local:CroppingViewModel" Title="CroppingView"> <ContentPage.Resources> <ResourceDictionary> <toolkit:ByteArrayToImageSourceConverter x:Key="ByteArrayToImageSourceConverter" /> </ResourceDictionary> </ContentPage.Resources> <Grid BackgroundColor="White"> <Grid.RowDefinitions> <RowDefinition Height="0.5*"/> <RowDefinition Height="0.5*"/> </Grid.RowDefinitions> <skia:SKCanvasView x:Name="canvasView" Grid.Row="0" PaintSurface="canvasView_PaintSurface" /> <Grid.Effects> <tt:TouchEffect Capture="True" TouchAction="TouchEffect_TouchAction" /> </Grid.Effects> <VerticalStackLayout Grid.Row="1" BackgroundColor="Blue"> <Button Command="{Binding CropImageCommand}"/> <Image Source="{Binding CroppedImage,Converter={StaticResource ByteArrayToImageSourceConverter}}" WidthRequest="250" HeightRequest="250"/> </VerticalStackLayout> </Grid> </ContentPage>
Code behind
SKBitmap Bitmap { get; set; } Dictionary<long, SKPoint> touchDictionary = new Dictionary<long, SKPoint>(); SKMatrix matrix; CroppingViewModel croppingViewModel; SKRect CroppingRect { get; set; } SKMatrix Matrix { get { return matrix; } set { matrix = value; SKMatrix m = matrix.Invert(); CroppingRect = m.MapRect(CroppingRect); WeakReferenceMessenger.Default.Send(new MatrixChangedMessage(value)); } } public CroppingView(SKBitmap bitmap) { InitializeComponent(); Bitmap = bitmap; Matrix = SKMatrix.CreateIdentity(); this.BindingContext = new CroppingViewModel(bitmap); float x = (Bitmap.Width) / 2; float y = (Bitmap.Height) / 2; float size = 500; CroppingRect = SKRect.Create(x - (size / 2), y - (size / 2), size, size); } private async void canvasView_PaintSurface(object sender, SkiaSharp.Views.Maui.SKPaintSurfaceEventArgs e) { SKImageInfo info = e.Info; SKSurface surface = e.Surface; SKCanvas canvas = surface.Canvas; canvas.Clear(); // Display the bitmap canvas.SetMatrix(Matrix); canvas.DrawBitmap(Bitmap, new SKPoint()); canvas.DrawRect(CroppingRect, new SKPaint { TextSize = 64.0f, IsAntialias = true, Color = new SKColor(255, 0, 0), Style = SKPaintStyle.Fill }); } private void TouchEffect_TouchAction(object sender, Maui.FreakyEffects.TouchTracking.TouchActionEventArgs e) { Maui.FreakyEffects.TouchTracking.TouchTrackingPoint pt = e.Location; // Convert Xamarin.Forms point to pixels SKPoint point = new SKPoint((float)(canvasView.CanvasSize.Width * pt.X / canvasView.Width), (float)(canvasView.CanvasSize.Height * pt.Y / canvasView.Height)); switch (e.Type) { case TouchActionType.Pressed: // Find transformed bitmap rectangle SKRect rect = new SKRect(0, 0, Bitmap.Width, Bitmap.Height); rect = Matrix.MapRect(rect); // Determine if the touch was within that rectangle if (rect.Contains(point) && !touchDictionary.ContainsKey(e.Id)) { touchDictionary.Add(e.Id, point); } break; case TouchActionType.Moved: if (touchDictionary.ContainsKey(e.Id)) { // Single-finger drag if (touchDictionary.Count == 1) { SKPoint prevPoint = touchDictionary[e.Id]; // Adjust the matrix for the new position SKMatrix matrix = Matrix; matrix.TransX += point.X - prevPoint.X; matrix.TransY += point.Y - prevPoint.Y; Matrix = matrix; canvasView.InvalidateSurface(); } // Double-finger scale and drag else if (touchDictionary.Count == 2) { // Copy two dictionary keys into array long[] keys = new long[touchDictionary.Count]; touchDictionary.Keys.CopyTo(keys, 0); // Find index of non-moving (pivot) finger int pivotIndex = (keys[0] == e.Id) ? 1 : 0; // Get the three points involved in the transform SKPoint pivotPoint = touchDictionary[keys[pivotIndex]]; SKPoint prevPoint = touchDictionary[e.Id]; SKPoint newPoint = point; // Calculate two vectors SKPoint oldVector = prevPoint - pivotPoint; SKPoint newVector = newPoint - pivotPoint; float scale = newVector.LengthSquared/oldVector.LengthSquared; // Scaling factors are ratios of those float scaleX = newVector.X / oldVector.X; float scaleY = newVector.Y / oldVector.Y; if (!float.IsNaN(scaleX) && !float.IsInfinity(scaleX) && !float.IsNaN(scaleY) && !float.IsInfinity(scaleY)) { // If something bad hasn't happened, calculate a scale and translation matrix SKMatrix scaleMatrix = SKMatrix.CreateScale(scale, scale, pivotPoint.X,pivotPoint.Y); // SKMatrix.MakeScale(scaleX, scaleY, pivotPoint.X, pivotPoint.Y); SKMatrix matrix = Matrix; matrix.PostConcat(scaleMatrix); Matrix = matrix; canvasView.InvalidateSurface(); } } // Store the new point in the dictionary touchDictionary[e.Id] = point; } break; case TouchActionType.Released: case TouchActionType.Cancelled: if (touchDictionary.ContainsKey(e.Id)) { touchDictionary.Remove(e.Id); } break; } }
ViewModel(裁剪逻辑)
public partial class CroppingViewModel : ObservableObject { [ObservableProperty] public SKMatrix matrix; [ObservableProperty] public SKBitmap cropBitmap; [ObservableProperty] public byte[] croppedImage; [ObservableProperty] public SKRect cropRect; public CroppingViewModel(SKBitmap cropMap) { WeakReferenceMessenger.Default.Register<MatrixChangedMessage>(this, (r, m) => { Matrix = m.Matrix.Invert(); }); CropBitmap = cropMap; } public CroppingViewModel() { WeakReferenceMessenger.Default.Register<MatrixChangedMessage>(this, (r, m) => { Matrix = m.Matrix; }); } [RelayCommand] public void CropImage() { float x = (CropBitmap.Width) / 2; float y = (CropBitmap.Height) / 2; float size = 1000; SKRect sourceRect = SKRect.Create(x - (size / 2), y - (size / 2), size, size); //SKBitmap destination = new SKBitmap((int)sourceRect.Width, (int)sourceRect.Height); //SKRect destRect = SKRect.Create(0, 0, sourceRect.Width, sourceRect.Height); SKBitmap destination = new SKBitmap(1080, 1080); SKRect destRect = SKRect.Create(0,0,1080,1080); sourceRect = Matrix.MapRect(sourceRect); using (SKCanvas canvas = new SKCanvas(destination)) { canvas.Clear(); canvas.DrawBitmap(CropBitmap, sourceRect, destRect); } SKImage skImage = SKImage.FromBitmap(destination); SKData encoded = skImage.Encode(SKEncodedImageFormat.Jpeg, 100); using(MemoryStream memory = new MemoryStream()) { encoded.AsStream().CopyTo(memory); CroppedImage = memory.ToArray(); } } partial void OnMatrixChanged(SKMatrix value) { CropRect = value.MapRect(CropRect); } }
注:视图上的矩形仅用于调试,观察图片移动时矩形的变化情况。
内容的提问来源于stack exchange,提问作者WoistdasNiveau
相关产品推荐
相关产品推荐

