Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.isPointInPath() for stroked lines and polylines

I need a mechanism for detecting mouseover event for lines, curves and polylines which have a various stroke width, I have already made such a mechanism for rectangles and ellipses, so I'm not new with canvas API. I do outline all of the drawn objects and detect mouse position over them, when rectangles or ellipses have a stroke width more than 1 pixel I expand the path so that it contains the border too. For lines and polylines it is difficult for me to understand how should I expand them when I have a lineWidth of 20 pixels for example.

My question is: how to transform lines, curves and polylines in some shape path, so that this path could contain all their width?

red - path, black - stroked path

I would need that the path created would contain the line / curve width represented with black in this image.

----------Some more information----------

I will try to simplify the problem: We have 2 points (represented in red on the image below), they form a line that have a specific formula (y = mx + n), I need to dermine the formulas of perpendicular lines that are passing through these two initial points, after, it is necessary to determine the positions of the "blue" points, which are at the distance of the half of the value of context.lineWidth, when all points have been determined it is possible to create a new path using moveTo() and lineTo() sequence. This method should be applicable for quadratic and bezier curves using control points. The problem only remains in these mathematical calculations.

like image 508
micnic Avatar asked Jul 14 '12 16:07

micnic


3 Answers

I found the solution on math.stackexchange here, this solution is for lines only, but it is applicable for curves and polylines with some specific modifications. First of all, we need to determine the formula of the line which passes through the two initials points:

Step 1

Our points: P1(x1, y1) and P2(x2, y2)

Distance between points and their neighbors: d

General form: Ax + By + C = 0

Where: A = y2 - y1; B = x1 - x2; C = x2y1 - x1y2.

After, it is necessary to define this formula in the short form:

Step 2

Short form: y = mx +n

Where: m = - A / B; n = - C / B. (when B != 0)

If A == 0 then we have the formula: y = C (case when we have a horizontal line)

If B == 0 then we have the formula: x = C (case when we have a vertical line)

When we have the slope of the line, we need the slope of the perpendicular line on it:

Step 3

Perpendiculat line slope: m2 = - 1 / m

If A == 0 or B == 0 go to Step 4

Now we need to get the neighbor points for both initial points:

Step 4

I will note neighbor points as P1N1, P1N2 for the first point and P2N1 and P2N2 for the second one

For the special cases (horizontal and vertical lines, when A == 0 or B == 0) we will have:

For A == 0 (horizontal line):

P1N1(x1, y1 - d / 2); P1N2(x1, y1 + d / 2); P2N1(x2, y2 + d / 2); P2N2(x2, y2 - d / 2).

For B == 0 (vertical line):

P1N1(x1 - d / 2, y1); P1N2(x1 + d / 2, y1); P2N1(x2 + d / 2, y2); P2N2(x2 - d / 2, y2).

For other cases (A != 0 and B != 0):

P1N1:

x = (d / 2) / Math.sqrt(1 + Math.pow(m2, 2)) + x1;

y = (m2 * (d / 2)) / Math.sqrt(1 + Math.pow(m2, 2)) + y1;

P1N2:

x = - (d / 2) / Math.sqrt(1 + Math.pow(m2, 2)) + x1;

y = - (m2 * (d / 2)) / Math.sqrt(1 + Math.pow(m2, 2)) + y1;

P2N1:

x = (d / 2) / Math.sqrt(1 + Math.pow(m2, 2)) + x2;

y = (m2 * (d / 2)) / Math.sqrt(1 + Math.pow(m2, 2)) + y2;

P2N2:

x = - (d / 2) / Math.sqrt(1 + Math.pow(m2, 2)) + x2;

y = - (m2 * (d / 2)) / Math.sqrt(1 + Math.pow(m2, 2)) + y2;

If you want to implement these formulas in your application you should cache some results to improve performance.

like image 87
micnic Avatar answered Nov 17 '22 23:11

micnic


Have you tried using the .isPointInStroke() method of the Canvas 2D API:

ctx.isPointInStroke(x, y);
ctx.isPointInStroke(path, x, y);

Take a look at: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/isPointInStroke

like image 29
Laiacy Avatar answered Nov 17 '22 23:11

Laiacy


If you bezier curve is of the following form.

x(t) = x0 + x1*t + x2*t*t + x3*t*t*t
y(t) = y0 + y1*t + y2*t*t + y3*t*t*t

Then you have to compute the derivatives of it, which will give the tangential line at any point.

x'(t) = x1 + 2*x2*t + 3*x3*t*t
y'(t) = y1 + 2*y2*t + 3*y3*t*t

and the normal line at any point. The normal is the perpendicular at any point and the one which is the support of your two points.

(-y'(t), x'(t))
((y'(t), -(x'(t))
like image 2
karlcow Avatar answered Nov 17 '22 21:11

karlcow