SkiaSharp icon indicating copy to clipboard operation
SkiaSharp copied to clipboard

Xamarin Android Image Render [BUG]

Open sisaacks opened this issue 1 year ago • 1 comments

Description I was running version 2.80.0 and upgraded to 2.88.0. When I did that the image I was drawing with Skiasharp scaled completely different on Android but the same on IOS. I am going to copy in my code below...what I do know is that the SkiaSharp.Views.Forms.SKCanvasView behaves completely different in 2.88.0. The image no longer seems to be scaled correctly on Android. I am not sure if its the canvas.RestMatrix or the canvas.Scale that is not working correclty. I rolled back to 2.80.0 and it behaves correctly.

I added two images, one entitled 2.88.0 where the image displays incorrectly and 2.80.0 where the image displays correctly

Code

<Grid BackgroundColor="Transparent">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="450"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>

                    <Grid Grid.Row="0" BackgroundColor="Transparent">
                        <skia:SKCanvasView  x:Name="CanvasView"  IgnorePixelScaling="False" 
                                   IsVisible="true" BackgroundColor="Transparent" EnableTouchEvents="True" Touch="CanvasView_Touch" 
                                   PaintSurface="CanvasView_PaintSurface" >
                            <skia:SKCanvasView.GestureRecognizers>
                                <PinchGestureRecognizer PinchUpdated="PinchGestureRecognizer_PinchUpdated"  />
                                <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" NumberOfTapsRequired="1" />
                            </skia:SKCanvasView.GestureRecognizers>
                        </skia:SKCanvasView>
                    </Grid>
                    <Grid Grid.Row="1" Padding="0">
                     </Grid>
</Grid>
public static void DrawLayout(SKImageInfo info, SKCanvas canvas, SKSvg svg, ReticleSetupViewModel vm, float redBoxAdjustment = 0)
        {

            var layout = vm.SelectedReticleLayout;
            float magScale;
            float yRatio;
            float xRatio;

            canvas.Translate(info.Width / 2f, info.Height / 2f);
            SKRect bounds = svg.ViewBox; 

            if (layout.ReticleLayoutScaleType == ReticleLayoutScaleType.ReticleOnly || layout.ReticleLayoutScaleType == ReticleLayoutScaleType.WidgetAndReticle)
            {
                magScale = vm.SelectedReticle.ReticleZoomScale[vm.SelectedScopeAndReticles.CurrentMag];
                xRatio = (info.Width / bounds.Width) + ((info.Width / bounds.Width) * magScale);
                yRatio = (info.Height / bounds.Height) + ((info.Height / bounds.Height) * magScale);
            }
            else
            {
                magScale = vm.SelectedReticle.ReticleZoomScale[vm.SelectedScopeAndReticles.LowMag];
                xRatio = (info.Width / bounds.Width) + ((info.Width / bounds.Width) * magScale);
                yRatio = (info.Height / bounds.Height) + ((info.Height / bounds.Height) * magScale);
            }

            float ratio = Math.Min(xRatio, yRatio);
            canvas.Scale(ratio);
            canvas.Translate(-bounds.MidX, -bounds.MidY);
            canvas.DrawPicture(svg.Picture);

            canvas.ResetMatrix();

            float imageCenter = canvas.LocalClipBounds.Width / 2;

            float redBorderXOffSet = imageCenter - (imageCenter / 2.0f) + canvas.LocalClipBounds.Left;

            // .0654450261780105f this number is the calculation of where to draw the top of the red border below the image.  
            // This was calculated from an Android phone by setting the off set to image.Top + 75. 
            // To get the percentage take 75/Image.Bottom = .0654450261780105f
            // now for any given platform we take the following to calculate the correct place for the Y offset for the red box
            //float redBorderYOffSet = (float)(svg.Picture.CullRect.Top + Math.Ceiling(.0654450261780105f * svg.Picture.CullRect.Bottom));
            float redBorderYOffSet = (float)(canvas.LocalClipBounds.Top + Math.Ceiling(.0654450261780105f * canvas.LocalClipBounds.Bottom));


            float adjustedRedBorderWidth = canvas.LocalClipBounds.Width / 2.0f;

            // this centers the red square, it may be different between phones and how they lay out.  
            var adjustedRedborderHeight = (float)(canvas.LocalClipBounds.Bottom - Math.Ceiling(.0654450261780105f * canvas.LocalClipBounds.Bottom * 2)) - canvas.LocalClipBounds.Top;

            //now account for the red box adjustment
            adjustedRedborderHeight += redBoxAdjustment * 2;

            // we need to clip the red square so that the widgets do not grow beyond the red square 
            if (layout.ReticleLayoutScaleType == ReticleLayoutScaleType.WidgetOnly || layout.ReticleLayoutScaleType == ReticleLayoutScaleType.WidgetAndReticle)
            {
                var redSquare = SKRect.Create(redBorderXOffSet - 2, redBorderYOffSet - 2, adjustedRedBorderWidth + 4, adjustedRedborderHeight + 4);
                canvas.ClipRect(redSquare, SKClipOperation.Intersect);
            }

            canvas.DrawRect(redBorderXOffSet, redBorderYOffSet, adjustedRedBorderWidth, adjustedRedborderHeight, RedBorderPaint);

            // set the node scale on the layout, I will need this to convert to send to the scope and saving to the DB for user created layouts
            layout.NodeScaleWidth = adjustedRedBorderWidth / ActualLayoutWidth;
            layout.NodeScaleHeight = adjustedRedborderHeight / ActualLayoutHeight;

            // configure the X and Y offset locations of the Red Box
            TopLeftDefaultOuterXOffSet = redBorderXOffSet + 5;
            TopLeftDefaultInnerXOffSet = TopLeftDefaultOuterXOffSet + (MediumDataBoxWidth * layout.NodeScaleWidth * 2) + 15;
            TopRightDefaultOuterXOffset = (redBorderXOffSet + adjustedRedBorderWidth) - 5;
            TopRightDefaultInnerXOffset = TopRightDefaultOuterXOffset - ((MediumDataBoxWidth * layout.NodeScaleWidth * 3) + 10);
        }

Expected Behavior

Actual Behavior

Basic Information

  • Version with issue: 2.88.0
  • Last known good version: 2.80.0
  • IDE: Visual Studio for Windows 2022
  • Platform Target Frameworks:
    • Android and IOS...but Android is the only one that seems to have the issue
  • Target Devices:
  • Samsung Galaxy S10, Samsung Flip (android 12)
  • Samsung Galaxy Z Flip 3 (android 12)
Detailed IDE/OS information (click to expand)

PASTE ANY DETAILED VERSION INFO HERE
Xamarin Forms 5.0.0.2515
Xamarin Essentials 1.7.3

Screenshots 2 80 0 2 88 0

Reproduction Link

sisaacks avatar Aug 10 '22 17:08 sisaacks

What happens if you wrap the draw logic inside a save restore instead of resetting the matrix?

https://docs.microsoft.com/en-us/dotnet/api/skiasharp.skcanvas.save?view=skiasharp-2.88

That should reset the matrix correctly. If not, then something else has gone bad.

mattleibow avatar Aug 17 '22 11:08 mattleibow