SkiaSharp (x,y) on canvasview after scale and transforme on SkCanvas

Seth Isaacks 11 Reputation points
2022-10-04T19:31:50.977+00:00

First let me state, this is my first time messing with any type of drawing utility and I am way outside of my wheelhouse. First I will describe the issue I am having, then what I am trying to do and then my code.

Issue:
After scaling and transforming the canvas, when I touch the canvas on the device screen, then break on the 'location' point in the touch event, its not the same location that the canvas drew something. I need the exact location I drew something to show the user a popup.

What I am trying to do:
I have an image that I scale and draw to the canvas. Then I draw a large red rectangle on the image and smaller rectangles inside the larger rectangle. When the user presses the zoom button I scale by an increasing factor giving the allusion that we are zooming in on the center. So essentially the image grows in size, the large rectangle grows in size and the little rectangles grow in size. This seems to work flawlessly...but I feel I have done something wrong being that when I touch one of the little rectangles, the location is not the same (x,y) it was drawn at.

I thought it may have something to do with canvas.ResetMatrix() or canvas.Save() or canvas.Restore() but none of those seem to work so I am absolutely baffled. Again any help with this endeavor would but much appreciated.

public static void DrawLayout(SKImageInfo info, SKCanvas canvas, SKSvg svg, SetupViewModel vm)  
  
var layout = vm.SelectedReticleLayout;  
float yRatio;  
float xRatio;  
float widgetHeight = 75;  
float widgetWidth = 170;  
float availableWidth = 720;  
float availableHeight = 1280;  
  
var currentZoomScale = getScale();  
canvas.Translate(info.Width / 2f, info.Height / 2f);  
SKRect bounds = svg.ViewBox;  
  
xRatio = (info.Width / bounds.Width) + ((info.Width / bounds.Width) * currentZoomScale);  
yRatio = (info.Height / bounds.Height) + ((info.Height / bounds.Height) * currentZoomScale);  
float ratio = Math.Min(xRatio, yRatio);  
  
canvas.Scale(ratio);  
canvas.Translate(-bounds.MidX, -bounds.MidY);  
canvas.DrawPicture(svg.Picture, new SKPaint { Color = SKColors.White, Style = SKPaintStyle.Fill });  
  
// now set the X,Y and Width and Height of the large Red Rectangle  
float imageCenter = canvas.LocalClipBounds.Width / 2;  
layout.RedBorderXOffSet = imageCenter - (imageCenter / 2.0f) + canvas.LocalClipBounds.Left;  
float redBorderYOffSet = (float)(svg.Picture.CullRect.Top + Math.Ceiling(.0654450261780105f * svg.Picture.CullRect.Bottom));  
layout.RedBorderYOffSet = (float)(canvas.LocalClipBounds.Top + Math.Ceiling(.0654450261780105f * canvas.LocalClipBounds.Bottom));  
layout.RedBorderWidth = canvas.LocalClipBounds.Width / 2.0f;  
layout.RedBorderWidthXOffSet = layout.RedBorderWidth + layout.RedBorderXOffSet;  
layout.RedBorderHeight = (float)(canvas.LocalClipBounds.Bottom - Math.Ceiling(.0654450261780105f * canvas.LocalClipBounds.Bottom * 2)) - canvas.LocalClipBounds.Top;  
layout.RedBorderHeightYOffSet = layout.RedBorderYOffSet + layout.RedBorderHeight;  
  
// draw the large red rectangle  
canvas.DrawRect(layout.RedBorderXOffSet, layout.RedBorderYOffSet, layout.RedBorderWidth, layout.RedBorderHeight, RedBorderPaint);  
  
// clear the tracked widgets, tracked widgets are updated every time we draw the widgets  
// base widgets contain the default size and location relative to the scope. base line widgets  
// will need to be multiplied by the node scale height and width  
  
layout.TrackedWidgets.Clear();  
  
var widget = new widget  
{  
X = layout.RedBorderXOffSet + 5;  
Y = layout.RedBorderYOffSet + layout.TrackedReticleWidgets[0].Height + 15;  
Height = layout.RedBorderHeight * (widgetHeight / availableHeight);  
Width = layout.RedBorderWdith * (widgetWidth / availableWidth);  
}  
  
// define colors for text and border colors for small rectangles (widgets)  
public static SKPaint SelectedWidgetColor => new SKPaint { Color = SKColors.LightPink, Style = SKPaintStyle.StrokeAndFill, StrokeWidth = 3 };  
  
public static SKPaint EmptyWidgetBorder => new SKPaint { Color = SKColors.DarkGray, Style = SKPaintStyle.Stroke, StrokeWidth = 3 };  
  
public static SKPaint EmptyWidgetText => new SKPaint { Color = SKColors.Black, TextSize = 10, FakeBoldText = false, Style = SKPaintStyle.Stroke, Typeface = SKTypeface.FromFamilyName("Arial") };  
  
public static SKPaint DefinedWidgetText => new SKPaint { Color = SKColors.DarkRed, FakeBoldText = false, Style = SKPaintStyle.Stroke };  
  
// create small rectangle (widget) and draw the widget  
var widgetRectangle = SKRect.Create(widget.X, widget.Y, widget.Width, widget.Height);  
canvas.DrawRect(widgetRectangle, widget.IsSelected ? SelectedWidgetColor : EmptyWidgetBorder);  
  
// now lets create the text to draw in the widget  
string text = EnumUtility.GetDescription(widget.WidgetDataType);  
float textWidth = EmptyWidgetText.MeasureText(text);  
EmptyWidgetText.TextSize = widget.Width * GetUnscaledWidgetWith(widget) * EmptyWidgetText.TextSize / textWidth;  
  
SKRect textBounds = new SKRect();  
EmptyWidgetText.MeasureText(text, ref textBounds);  
  
float xText = widgetRectangle.MidX - textBounds.MidX;  
float yText = widgetRectangle.MidY - textBounds.MidY;  
  
canvas.DrawText(text, xText, yText, EmptyWidgetText);  
Xamarin
Xamarin
A Microsoft open-source app platform for building Android and iOS apps with .NET and C#.
5,297 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Rob Caplan - MSFT 5,422 Reputation points Microsoft Employee
    2022-10-06T19:40:27.65+00:00

    The key is that the problem exists only if you transform (Scale and Translate) the Canvas, and that if you don't transform the canvas then the touch coordinates match.

    This is expected behavior, since transforming the Canvas shifts its coordinates into a new coordinate system, but the touch coordinates are still in the original. To compensate, you'll also need to apply the same transform to the touch coordinates. You can get it Canvas' final transform from its TotalMatrix property.

    See Matrix Transforms in SkiaSharp