Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] The size of skiasharp circle is smaller than intended on Android. #9057

Closed
HobDev opened this issue Jan 1, 2020 · 5 comments
Closed

[Bug] The size of skiasharp circle is smaller than intended on Android. #9057

HobDev opened this issue Jan 1, 2020 · 5 comments

Comments

@HobDev
Copy link

@HobDev HobDev commented Jan 1, 2020

Description

I am developing a BadgeView using SkiaSharp. I created separate class and used SKCanvasView as Base class. The canvas height and width is both set to 15. Now DrawCircle method is used to create badge with same height and width as canvas. This way the circle should cover whole canvas. But on Android the circle is smaller than the canvas. Whereas the circle is expanded to full canvas on iOS

Basic Information

  • Version with issue: SkiaSharp.Views.Forms(1.68.1.1) and Xamarin.Forms(4.4.0.991265)

  • Affected Devices:Android

Screenshots

### iOS Screenshot

iOS

### Android Screenshot

Android

Reproduction Link

BadgeSkiaSample.zip

@samhouts

This comment has been minimized.

Copy link
Member

@samhouts samhouts commented Jan 2, 2020

@mattleibow Is this a known issue?

@samhouts samhouts moved this from New to Needs Info in Triage Jan 2, 2020
@mattleibow

This comment has been minimized.

Copy link
Contributor

@mattleibow mattleibow commented Jan 11, 2020

Sorry for the delay. I think this might be due to a misunderstanding with the way a drawing works in relation to Xamarin.Forms views.

This is the original drawing code:

var canvas = e.Surface.Canvas;
canvas.Clear();
canvas.Save();
canvas.Clear(SKColors.Yellow);
canvas.DrawCircle((float)(this.Width), (float)this.Height, 15, circleFillPaint);
canvas.DrawText(BadgeValue.ToString(), 6, 20, textPaint);

Really, the main issue is with this code, the drawing line:

canvas.DrawCircle((float)(this.Width), (float)this.Height, 15, circleFillPaint);

What is happening here is that a circle is being drawn from the center out with a radius. Don't forget, we got docs for all members:
https://docs.microsoft.com/dotnet/api/skiasharp.skcanvas.drawcircle

What this ends up doing is drawing a circle that has it's center at the bottom left corner, and the radius that fills the full width/height:
image

However, this is actually NOT what we see due to another slight difference in UNITS. SkiaSharp uses RAW pixels and Xamarin.Forms uses SCALED pixels. For example, if you have a view that is 100x100 pixels on iOS, the Retina display actually renders this as 200x200 or even 300x300. As a result, all views have the same height, and the device automatically scales. With SkiaSharp, this is not the case because there is no concept of views in the drawing engine. (There is an issue opened for this mono/SkiaSharp#527, but this has not seen much traction)

The reason this looks fine on iOS is that they have a scaling of exactly 2x on some devices. As a result, a center of 15x15 and a radius of 15 results in a 30 diameter circle on a 30x30 square. And, since the drawing is not scaled, the center of 15x15 is at the center of 30x30. The reason Android looks weird is that some devices have a 2.5x or 3.5x scaling. So a radius of 15 results in a total of 30 for the diameter, but a scaling of 2.6x (on my simulator) results in a 40x40 square.

So, to fix this today, there are 2 adjustments that need to be done: scaling and drawing.

To fix the scaling, just scale the canvas based on the screen density or Forms-to-Skia ratio. For example, this can be done with a Scale method before drawing using the actual canvas size in the args (e.Info.Width):

canvas.Scale(e.Info.Width / (float)this.Width);

To fix the drawing for this new world, we also just need to use the correct coordinates:

canvas.DrawCircle((float)this.Width / 2f, (float)this.Height / 2f, 15 / 2f, circleFillPaint);

That should give you the expected circle.

There is an additional improvement we can make to reduce the reliance on the view sizes or the canvas size. This is not actually required for this specific cases, but will make drawing easier to maintain and require less arithmetic when drawing If we scale the canvas into a set of coordinates that are easier to work with, then we can avoid much of the work.

For example, if we want to work with a total size of 100 units, then we can just scale that. For example, assume we have a 100x100 square and a text height of 70 as our base sizes:

SKPaint textPaint = new SKPaint
{
	TextSize = 70f,
	IsAntialias = true,
	Color = SKColors.White,
};

SKPaint circleFillPaint = new SKPaint
{
	Style = SKPaintStyle.Fill,
	Color = Color.FromHex("#FF5722").ToSKColor(),
	IsAntialias = true
};

protected override void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
	const float baseUnitSize = 100f;

	// clear the canvas
	var canvas = e.Surface.Canvas;
	canvas.Clear(SKColors.Yellow);

	// scale to our base unit of 100
	canvas.Scale(e.Info.Width / baseUnitSize);

	// center of 50x50 and a radius of 50
	canvas.DrawCircle(50f, 50f, 50f, circleFillPaint);

	// measure the text width and height
	var textBounds = SKRect.Empty;
	var advance = textPaint.MeasureText(BadgeValue.ToString(), ref textBounds);

	// draw the text at the center (x = 50 - half the width; y = 100 - half text height)
	canvas.DrawText(BadgeValue.ToString(), 50f - (advance / 2f), 100f - (textBounds.Height / 2f), textPaint);
}

This is a bit different from drawing with view sizes, but gives you the advantage of not really caring what the size is when drawing. The UI will set the size and you can do any logic there. But, as soon as it comes to drawing, then you can use a arbitrary size that makes sense when drawing. This gives you the benefit of having a pixel perfect drawing each time, regardless of the screen density, view size or any other UI system that my be hosting your drawing code.

I hope this makes sense and helps.

@mattleibow

This comment has been minimized.

Copy link
Contributor

@mattleibow mattleibow commented Jan 11, 2020

Not to totally plug my own, often neglected, blog... I just added a blog post with a bit more detail on this. I thought it was a very good question, so thanks @HobDev.

https://dotnetdevaddict.co.za/2020/01/12/who-cares-about-the-view-anyway/

@samhouts

This comment has been minimized.

Copy link
Member

@samhouts samhouts commented Jan 16, 2020

@HobDev I hope this answers your question. Let us know if it does not. Thank you!

@samhouts samhouts closed this Jan 16, 2020
Triage automation moved this from Needs Info to Closed Jan 16, 2020
@HobDev

This comment has been minimized.

Copy link
Author

@HobDev HobDev commented Jan 20, 2020

Thank you @mattleibow for the detailed answer and blog post.I really Couldn't ask for more. Unable to respond earlier as I was holidaying.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Triage
  
Closed
3 participants
You can’t perform that action at this time.