Programming => Graphics => Graphics and Multimedia => BGRABitmap and LazPaint => Topic started by: trayres on April 30, 2019, 02:59:46 pm

Title: Hit testing with BGRABitmap
Post by: trayres on April 30, 2019, 02:59:46 pm
If I'm drawing objects on a Canvas2D, I can save the paths off in TBGRAPath and do hit tests against them, then call a function bound to the canvas item, right?

I'd like to bind events (on click/on enter/on leave/etc) to closed paths (or paths, + some amount of space for things like Bezier curves). It looks like I should be able to copy paths as they're drawn, then I suppose the naive option would be to simply test each canvas item against the points - a better way would be to make a first cut with bounding boxes, then only examine those that potentially overlap the hit point.

A method to return the items on the canvas at a certain point. So as things are added, store them, give them an ID tag, and be able to search for them by either tag or by coordinates.

Which basically makes it like Tk's Canvas object, only it's native and BGRABitmap looks infinitely better.

Am I off in the woods here? Any thoughts?
Title: Re: Hit testing with BGRABitmap
Post by: circular on May 04, 2019, 02:02:53 pm
If you would like to store the path, you can use a TBGRAPath object (unit BGRAPath). You can use it to draw on the Canvas2d with path() function and to check if it contains a point.
Note that to check for a point you need apply to the hit point the matrix if any transform was used in the Canvas2d (you can retrieve it with “matrix” property).
The path object has a function to compute its bounds. Though no function to directly test for a point. You need first to call ToPoints and use it as a parameter of global function IsPointInPolygon.
This way you can optimisé the hit test without having to redraw everything. Though you can test on the fly while you’re drawing on the Canvas2d if a point is in the current path.
Title: Re: Hit testing with BGRABitmap
Post by: trayres on May 12, 2019, 01:47:13 am
Hi Circular,

I was able to use ToPoints, and I see now what you mean about the bounds - this is so that we don't have to scan the entire region, correct?

So it would look something like:
where B is a TRationalQuadraticBezierCurve and ctx is a BGRACanvas2D, correct?

I was able to use the global function IsPointInPolygon, and now I can do hit testing (YAY!) but I was hoping to save off the path - right now I'm just calling ToPoints twice. I could save off the whole array of points, but when I try to just copy the path with copyTo (while using a BGRACanvas2D within a BGRAVirtualScreen), I get:

ucontroller.pas(118,7) Error: identifier idents no member "copyTo", which is weird because the IDE can find copyTo just fine.

Also: Is there a way I can enlarge the region for clicking slightly? Now that I think about it, I could draw a transparent Bezier with the same control points over the 'actual' bezier, and use those points for hit testing - is that the most efficient way to go about it?

Thanks for your help, and of course BGRABitmap!
Title: Re: Hit testing with BGRABitmap
Post by: trayres on May 12, 2019, 05:34:04 am
I think I see what you meant:

Code: Pascal  [Select]
  2.   _BezierPath := TBGRAPath.Create;
  3.   _BezierPath.beginPath();
  4.   _BezierPath.moveTo(B.p1);
  5.   _BezierPath.polylineTo(B.ToPoints(B.GetBounds,0.45));
  6.   _BezierPath.stroke(Bitmap,BGRABlack,0.45,0.1);

However, stroking this onto the Bitmap does not look as sharp/polished as the following (using a BGRAVirtualCanvas, ctx is the Canvas2D of Bitmap):

Code: Pascal  [Select]
  1.   ctx.beginPath();
  2.   boundsF := RectF(0,0, ctx.Width,ctx.Height);
  3.   ctx.moveTo(B.p1);
  4.   ctx.polylineTo(B.ToPoints(B.GetBounds,0.45));
  5.   ctx.stroke();

Is there a way to preserve the sharpness?

Also, it seems like IsPointInPolygon is the closed polygon that encloses the space, so the hit test on a concave bezier curve includes any of the interior region - while this can be disambiguated via program state and multiple items in the hit test, or a popup that would ask the user to make the correct selection, is there a way to make the selection like a fatter version of the stroked line?
Title: Re: Hit testing with BGRABitmap
Post by: circular on May 14, 2019, 07:04:36 pm

For a rational Bezier curve, GetBounds is not always finite. Indeed a rational Bezier curve can be infinite. Instead, ToPoints expect the bounds of the surface to avoid computing too much points in case of infinity. This is specific to this kind of Bezier curve. Other curves/path do not have this problem.

To check if it is an infinite curve, check with property IsInfinite. In this case, the bounds are the whole surface (you can't skip it with such a test).

To get an area around a line, you can call Bitmap.ComputeWidePolyline with the width you want. This will depend on the current pen style / arrow style of the Bitmap. For a hit test, you are better of setting the PenStyle to psSolid (or create your own TBGRAPenStroker object).
Code: Pascal  [Select]
  1. Bitmap.PenStyle := psSolid;
  2. hitPolygon := Bitmap.ComputeWidePolyline(B.ToPoints(Bitmap.ClipRect,0.45), 6);

About sharpness, there is no difference in principle. Maybe the pen width is different? Or the antialiasing mode (LinearAntialiasing / linearBlend property)?
Title: Re: Hit testing with BGRABitmap
Post by: trayres on May 20, 2019, 12:51:58 am
Hi Circular,
It is going great! I mean the code doesn't look 'good' as in sophisticated, but at least I'll be able to ask questions about how to better structure it without being embarrassed that it does nothing. Eventually. Right now it does precious little, and it's kinda convoluted at points, but it is doing things.

Thanks for answering my questions (even though they weren't well posed). I'll have something to show off eventually that maybe one or two other people on planet Earth would ever find interesting, but eh.

At least it will be done with Lazarus/FreePascal. Thanks for your help!

Title: Re: Hit testing with BGRABitmap
Post by: circular on May 22, 2019, 03:23:34 pm
Happy to help.

Indeed that's in FreePascal, yey!  :)