You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 23:07:10