unoplatform / uno

Open-source platform for building cross-platform native Mobile, Web, Desktop and Embedded apps quickly. Create rich, C#/XAML, single-codebase apps from any IDE. Hot Reload included! 90m+ NuGet Downloads!!
https://platform.uno
Apache License 2.0
8.79k stars 707 forks source link

`XamlRoot.RasterizationScale` is 2.88 on iPhone Mini 13 instead of 3 #16793

Open inforithmics opened 4 months ago

inforithmics commented 4 months ago

Current behavior

RasterizationScale is 2.88 instead of 3 causing Touches to be some pixels off on iPhone Mini 13 on iOS 15.5 in the Simulator. On other devices it works.

Expected behavior

RasterizationScale is 3 causing Touches to be at the correct position. Could be an SkiaSharp issue that somehow the Density is calculated different on iPhone Mini 13 in Simulator.

How to reproduce it (as minimally and precisely as possible)

public sealed partial class MainPage : Page
{
    private readonly List<SKPoint> points;
    private readonly SKXamlCanvas _canvas;

    public MainPage()
    {
        this.InitializeComponent();
        points = new List<SKPoint>();
        _canvas = new SKXamlCanvas();
        _canvas.PaintSurface += DrawingCanvas_OnPaintSurface;
        _canvas.PointerPressed += DrawingCanvas_PointerPressed;
        _canvas.Background = new SolidColorBrush(Colors.Transparent);
        _canvas.HorizontalAlignment = HorizontalAlignment.Stretch;
        _canvas.VerticalAlignment = VerticalAlignment.Stretch;
        Background = new SolidColorBrush(Colors.White);
        Content = _canvas;
    }

    private double GetPixelDensity() => XamlRoot?.RasterizationScale ?? 1d;

    private void DrawingCanvas_PointerPressed(object sender, PointerRoutedEventArgs e)
    {
        // Get the position of the mouse click
        var position = e.GetCurrentPoint(_canvas).Position;
        var density = XamlRoot?.RasterizationScale ?? 1d;
        var x = Convert.ToSingle(position.X * density);
        var y = Convert.ToSingle(position.Y * density);
        points.Add(new SKPoint(x, y));
        _canvas.Invalidate();
    }

    private void DrawingCanvas_OnPaintSurface(object? sender, SKPaintSurfaceEventArgs e)
    {
        SKSurface surface = e.Surface;
        SKCanvas canvas = surface.Canvas;
        canvas.Clear(SKColors.White);
        using SKPaint paint = new SKPaint();
        paint.Color = SKColors.Blue;
        paint.Style = SKPaintStyle.Fill;

        foreach (var point in points) canvas.DrawCircle(point, 5, paint);
    }
}

Workaround

None yet except detecting iPhone Mini and taking 3 instead of 2.88 as the density.

Works on UWP/WinUI

Yes

Environment

Uno.UI / Uno.UI.WebAssembly / Uno.UI.Skia

NuGet package version(s)

5.2.132

Affected platforms

iOS

IDE

Visual Studio 2022

IDE version

17.10

Relevant plugins

Resharper

Anything else we need to know?

Reproduction Sample, Start it in iPhone Mini 13 Simulator comes with 15.5 SDK Devices. UnoClick.zip

jeromelaban commented 4 months ago

The NativeScale reported by the OS is 2.88, using the UIScreen.MainScreen.NativeScale. SkiaSharp is using the same density calculation using DisplayInformation.LogicalDpi which returns 2.88 as well.

Not sure where this issue comes from.

MartinZikmund commented 4 months ago

This is something I should look into, but I will get to it on Sunday at the earliest. If someone else has capacity to look into it, I am able to provide some pointers

inforithmics commented 4 months ago

I tested some things out and found a fix / workaround

private void DrawingCanvas_PointerPressed(object sender, PointerRoutedEventArgs e)
{
    // Get the position of the mouse click
    var position = e.GetCurrentPoint(_canvas).Position;
    // var density = XamlRoot?.RasterizationScale ?? 1d;
    var density = _canvas.CanvasSize.Width / _canvas.ActualWidth;
    var x = Convert.ToSingle(position.X * density);
    var y = Convert.ToSingle(position.Y * density);
    points.Add(new SKPoint(x, y));
    _canvas.Invalidate();
}

So the solution / workaround is to calculate the density of the _canvas and then it works, so it seems the SKCanvas has a different density than the RasterizationScale.

inforithmics commented 4 months ago

When I implemented this workaround / Fix in mapsui I discovered something strange. When I use the SKSwapChainPanel var density = _canvas.CanvasSize.Width / _canvas.ActualWidth; is 2.88 As it should be but the Rendering is totally broken, as it seems it doesn't fill out the whole area.

Rendering with Canvas

image

Rendering with SwapChainPanel

image