mono / SkiaSharp

SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.
MIT License
4.54k stars 543 forks source link

SKCanvasView IOS data does not update in the OnPaintSurface method #2965

Closed NastyaZorro169 closed 2 months ago

NastyaZorro169 commented 4 months ago

Description

Good morning. I am drawing a circle using SKCanvasView. On Android, everything works fine, but on iOS, the data does not update in the OnPaintSurface method, so the circle does not change its radius when the points are moved. If I change the visibility, the radius changes.

Code

public partial class NewContent1 : ContentView
{
    private IDraggableDotOffsetService DDOS;
    private double startX, startY;
    public float width;
    public float height;
    float halfPointSize = 9 + 5;
    public NewContent1()
    {
        InitializeComponent();
        DDOS = IPlatformApplication.Current.Services.GetService<IDraggableDotOffsetService>();
        PanGestureRecognizer onPanUpdatedCircle = new PanGestureRecognizer();
        onPanUpdatedCircle.PanUpdated += (s, e) => OnPanUpdatedCircle(s, e);
        DotCircleLayout1.GestureRecognizers.Add(onPanUpdatedCircle);
        DotCircleLayout2.GestureRecognizers.Add(onPanUpdatedCircle);

        DotCircleLayout1.Initialize();
        DotCircleLayout2.Initialize();
        DotCircleLayout1.TranslationX = DotCircleLayout1.iosX = 100;
        DotCircleLayout1.TranslationY = DotCircleLayout1.iosY = 150;

        DotCircleLayout2.TranslationX = DotCircleLayout2.iosX = 200;
        DotCircleLayout2.TranslationY = DotCircleLayout2.iosY = 150;
        UpdateCircle();
    }

    public void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
    {
        SKImageInfo info = e.Info;
        SKSurface surface = e.Surface;
        SKCanvas canvas = surface.Canvas;
        canvas.Clear(SKColors.Transparent);

        SKPaint paint = new SKPaint
        {
            Style = SKPaintStyle.Stroke,
            Color = Color.FromRgb(255, 255, 255).ToSKColor(),
            StrokeWidth = 6
        };

        canvas.DrawCircle(info.Width / 2, info.Height / 2, info.Width / 2 - 3, paint);
    }
    //---------------------------------------------------------
    void OnPanUpdatedCircle(object sender, PanUpdatedEventArgs args)
    {
        var layout = (CustomAbsoluteLayout)sender;
        float radius = (float)Math.Sqrt(Math.Pow(DotCircleLayout2.TranslationX - DotCircleLayout1.TranslationX, 2) + Math.Pow(DotCircleLayout2.TranslationY - DotCircleLayout1.TranslationY, 2));
        float angle = (float)Math.Atan2(DotCircleLayout2.TranslationY - DotCircleLayout1.TranslationY, DotCircleLayout2.TranslationX - DotCircleLayout1.TranslationX);

        switch (args.StatusType)
        {
            case GestureStatus.Started:
                (startX, startY) = DDOS.Started(args.TotalX, args.TotalY, layout.iosX, layout.iosY);
                break;
            case GestureStatus.Running:

                if (layout == DotCircleLayout1)
                {
                    (var newCenterX, var newCenterY) = DDOS.Running(startX, startY, args.TotalX, args.TotalY, layout.TranslationX, layout.TranslationY);
                    DotCircleLayout1.TranslationX = newCenterX;
                    DotCircleLayout2.TranslationY = newCenterY;
                    DotCircleLayout2.TranslationX = layout.TranslationX + radius * Math.Cos(angle);
                    DotCircleLayout2.TranslationY = layout.TranslationY + radius * Math.Sin(angle);
                }
                else if (layout == DotCircleLayout2)
                {
                    (var newDot2X, var newDot2Y) = (layout.iosX, layout.iosY) = DDOS.Running(startX, startY, args.TotalX, args.TotalY, layout.TranslationX, layout.TranslationY);

                    float newRadius = (float)Math.Sqrt(Math.Pow(newDot2X - DotCircleLayout1.TranslationX, 2) + Math.Pow(newDot2Y - DotCircleLayout1.TranslationY, 2));
                    radius = newRadius;
                    // Устанавливаем DotLayout2 на новом радиусе от DotLayout1
                    angle = (float)Math.Atan2(newDot2Y - DotCircleLayout1.TranslationY, newDot2X - DotCircleLayout1.TranslationX);
                    DotCircleLayout2.TranslationX = DotCircleLayout1.TranslationX + radius * Math.Cos(angle);
                    DotCircleLayout2.TranslationY = DotCircleLayout1.TranslationY + radius * Math.Sin(angle);
                }
                UpdateCircle();
                break;
        }
    }

    private void Test_Clicked(object sender, EventArgs e)
    {
        canvas.IsVisible = DotCircleLayout1.IsVisible = DotCircleLayout2.IsVisible = !canvas.IsVisible;
    }

    //---------------------------------------------------------
    private void UpdateCircle()
    {
        double radius = Math.Sqrt(Math.Pow((DotCircleLayout2.TranslationX + halfPointSize) -
            (DotCircleLayout1.TranslationX + halfPointSize), 2) +
            Math.Pow((DotCircleLayout2.TranslationY + halfPointSize) -
            (DotCircleLayout1.TranslationY + halfPointSize), 2));
        canvas.WidthRequest = radius * 2;
        canvas.HeightRequest = radius * 2;
        canvas.TranslationX = DotCircleLayout1.TranslationX + halfPointSize - radius;
        canvas.TranslationY = DotCircleLayout1.TranslationY + halfPointSize - radius;
        canvas.InvalidateSurface();
    }
    //---------------------------------------------------------
}

You can also share some XAML:

<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiApp1.NewContent1"
             xmlns:controls="clr-namespace:MauiApp1"
             xmlns:skia="clr-namespace:SkiaSharp.Views.Maui.Controls;assembly=SkiaSharp.Views.Maui.Controls">
    <AbsoluteLayout x:Name="AbsoluteLayoutContainer">
        <Button x:Name="Test" Text="Click" Clicked="Test_Clicked" BackgroundColor="Blue"/>
        <AbsoluteLayout 
            AbsoluteLayout.LayoutBounds="0,0,1,1"
            AbsoluteLayout.LayoutFlags="All"
            InputTransparent="True">
            <skia:SKCanvasView x:Name="canvas" PaintSurface="OnPaintSurface" BackgroundColor="Crimson"/>
        </AbsoluteLayout>
        <controls:CustomAbsoluteLayout x:Name="DotCircleLayout1"
         WidthRequest="28"
         HeightRequest="28"
         TranslationX="22"
         TranslationY="22">
            <Ellipse WidthRequest="10"
      HeightRequest="10"
      Fill="Black"
      AbsoluteLayout.LayoutBounds="9,9,10,10"/>
            <Image Source="dotnet_bot.svg"
    AbsoluteLayout.LayoutBounds="5, 5, 18, 18"
    AbsoluteLayout.LayoutFlags="None">
            </Image>
        </controls:CustomAbsoluteLayout>
        <controls:CustomAbsoluteLayout x:Name="DotCircleLayout2"
         WidthRequest="28"
         HeightRequest="28"
         TranslationX="322"
         TranslationY="222">
            <Ellipse WidthRequest="10"
      HeightRequest="10"
      Fill="Black"
      AbsoluteLayout.LayoutBounds="9,9,10,10"/>
            <Image Source="dotnet_bot.svg"
    AbsoluteLayout.LayoutBounds="5, 5, 18, 18"
    AbsoluteLayout.LayoutFlags="None">
            </Image>
        </controls:CustomAbsoluteLayout>
    </AbsoluteLayout>
</ContentView>

DraggableDotOffsetService in IOS

namespace MauiApp1.iOS
{
    public class DraggableDotOffsetService : IDraggableDotOffsetService
    {
        public (double X, double Y) Running(double startX, double startY, double TotalX, double TotalY, double X, double Y)
        {
            return (startX + TotalX, startY + TotalY);
        }

        public (double X, double Y) Started(double startX, double startY, double iosX, double iosY)
        {
            return (iosX, iosY);
        }
    }
}

IDraggableDotOffsetService.cs

namespace MauiApp1
{
    public interface IDraggableDotOffsetService
    {
        (double X, double Y) Running(double startX, double startY, double TotalX, double TotalY, double X, double Y);
        (double X, double Y) Started(double startX, double startY, double iosX, double iosY);
    }
}

CustomAbsoluteLayout.cs

namespace MauiApp1
{
    public class CustomAbsoluteLayout : AbsoluteLayout
    {
        public double iosX = 0;
        public double iosY = 0;

        public void Initialize()
        {
            iosX = TranslationX;
            iosY = TranslationY;
        }
    }
}

Expected Behavior

I expect that the data will be updated in the OnPaintSurface method, similar to how it is done in Android.

Actual Behavior

In fact, the data in SKImageInfo is not being updated.

Version of SkiaSharp

2.88.3 (Current)

Last Known Good Version of SkiaSharp

2.88.2 (Previous)

IDE / Editor

Visual Studio (Windows)

Platform / Operating System

Android, iOS

Platform / Operating System Version

No response

Devices

No response

Relevant Screenshots

No response

Relevant Log Output

No response

Code of Conduct

mattleibow commented 2 months ago

Are you able to put this into a sample project so I can test. There are a lot of code snippets here and it is a bit hard to test.

dotnet-policy-service[bot] commented 2 months ago

Hi @NastyaZorro169. We have added the status/needs-repro label to this issue, which indicates that we require steps and sample code to reproduce the issue before we can take further action. Please try to create a minimal sample project/solution or code samples which reproduce the issue, ideally as a GitHub repo that we can clone.

This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

mattleibow commented 2 months ago

Also, I assume you are using VS on windows and either an iOS device plugged into the windows machine or connected to a mac? If you try the other option, does it work?

dotnet-policy-service[bot] commented 2 months ago

This issue has been automatically marked as stale because it has been marked as requiring author feedback to reproduce the issue but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.