SkiaSharp
SkiaSharp copied to clipboard
Xamarin Android Image Render [BUG]
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
Reproduction Link
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.