SVG : Confusion about relative path coordinates - svg

I'm trying to cut up a long SVG path generated by inkscape into several smaller paths. Specifically, I am cutting up this path:
"m 42.333333,13.895833 c 0,21.166668 21.166666,19.843751 21.166666,19.843751 h 9.260417 c 0,0 19.84375,-11.906251 13.229166,7.9375 -6.614583,19.84375 -13.229166,31.75 -33.072916,21.166667 C 33.072916,52.260417 31.75,13.895833 31.75,13.895833"
The output I generate at the moment are the following paths:
"M 42.333333,13.895833 c 0,21.166668 21.166666,19.843751 21.166666,19.843751"
"M 63.499999,33.739584 l 9.260417,0"
"M 72.760416,33.739584 c 0,0 19.84375,-11.906251 13.229166,7.9375 -6.614583,19.84375 -13.229166,31.75 -33.072916,21.166667"
"M 39.6875,54.906251 c -6.61458400000001,-2.645834 -7.93750000000001,-41.010418 -7.93750000000001,-41.010418"
This is not correct. You can see the difference between input and output here.
As you can see, almost all points are correct, except that the fourth path of the output does not start at the same point that the corresponding part of the original path does. This is probably caused by some error in my understanding of how SVG path coordinates exactly work.
I arrived at the starting point (39.6875,54.906251) for the fourth path as follows:
We start at (42.33333,13.895833). We then curve to relative position (21.166666,19.843751), so that gives us absolute position (42.33333+21.166666,13.895833+19.843751) = (63.499996,33.739584).
The path is then extended with a horizontal line with relative x coordinate 9.260417. So that gives new absolute position (63.499996 + 9.260417, 33.739584 + 0 ) = (72.760413,33.739584). We then curve to relative position (-33.072916,21.166667). Giving the start position for the fourth path as (72.760413-33.072916,33.739584+21.166667) = (39.6875,54.906251) (with some rounding).
Why is this wrong?

I figured it out myself.
The problem was caused due to the third curve actually being a polybezier consisting of 2 cubic curves. The coordinates of that second cubic bezier should be relative to its starting point, rather than the starting point of the complete polybezier.

Related

What is the difference of "z" and "Z" when closing an SVG path?

When creating an SVG path, typically the capital letters (M, L...) in the d attribute refer to absolute coordinates and the lower case letters (m, l...) refer to relative coordinates to the last point.
Here's an example that draws a small right triangle in absolute coordinates:
<path style="stroke:black;fill:none;" d="M100,100 L150,100 V50 Z" />
This draws the same triangle in relative coordinates:
<path style="stroke:black;fill:none;" d="m100,100 l50,0 v-50 z" />
I can use a capital or lowercase M and Z in either case and visually, nothing is changed. Regarding M, I assume that since it is the first point, it is absolute or relative to (0, 0), but please correct me if that is wrong. What is the difference of z and Z?
In all cases:
An upper-case command specifies absolute coordinates, while a lower-case command specifies coordinates relative to the current position. path_commands
But in the case of z/Z, there is no difference (ClosePath). There is no absolute or relative coordinate associated with the z/Z, so it will just create a straight line between the last point and the starting point.
Both z and Z define the Path Command: ClosePath
Path commands are instructions that define a path to be drawn. Each command is composed of a command letter and numbers that represent the command parameters.
SVG defines 6 types of path commands, for a total of 20 commands:
MoveTo: M, m
LineTo: L, l, H, h, V, v
Cubic Bézier Curve: C, c, S, s
Quadratic Bézier Curve: Q, q, T, t
Elliptical Arc Curve: A, a
ClosePath: Z, z

How to know if a point belongs more or less to a circle?

I know the formula to know if a point is inside, outside and on a circle : https://math.stackexchange.com/q/198769 This quote explains that we must compare d to r (please read the quote, it's only 5 lines).
But I just want to know if a point is ON a circle. Moreover, and that's the real problem : if a point is a bit inside/outside a circle, I want to consider it as ON the circle.
How could I do that ? I tried to delimit d-r (ie. : the comparison) in a range. Example :
if(d-r > -100 && d-r < 100) { point is on the circle }
It works, with -100 and 100, for circles with a little radius (ie. : ALL the points that are a bit outside/inside the circle are considered as being on the circle).
But for circles for a big radius, only SOME points are considered as being on the circle (ie. : only some of the points that are a bit outside/inside the circle are considered as being on the circle)...
So I would want that ALL the points that are a bit outside/inside the circle are considered as being on the circle, independently of the circle's radius. How ?
Your comparison for absolute difference might be written shorter as
if Abs(d - r) < delta (i.e. 100) ...
But seems you need relative difference depending on circle radius like this:
if Abs(d - r) / r < reldelta (i.e. 0.001) ...
From a probabilistic perspective, you could define a sort of distance map (as proposed by #Mbo) adopting the relative distance and use it to build a probability distribution on each point. The probability would represent a sort of likelihood of the point to belong to the circle. Intuitively, the closer the point, the more likely it is to be part of the circle. For example:
rel_d = (d-r)/r;
// P(x on the circle) = 1 - rel_d
if(rel_d < 1){
P_on_circle = 1 - rel_d;
}else{
P_on_circle = 0;
}

Arc tangent to an arc at end point , and a line

I have :
Arc : defined by p1x, p1y - p2x,p2y , radius, center, initial - final ang.
Line : defined by ax,ay - bx,by.
As you can see at the image I want to figure out an arc tangent to arc and linea and passing by the end point of first arc .
I think there is a unique solution. (or maybe two, R + and R - )
I'm trying so see how to implement an algorithm, without results...
Any idea would be appreciated...
What you are trying to do is to find a circle tangent to an infinite line "L", and tangent to a circle at a particular point on the circle. The key observation is that, since the tangent vector to any circle at a given angle is perpendicular to the radius vector for that angle, what we need to find are the point "TC" ("Tangent Center") and distance "d" at which a line offset from the given line by the distance d intersects a normal drawn outward from the circle for the same distance (forgive my bad art):
The easiest way to solve for "d" is as follows:
Construct the normalized normal vector "R" at point "P" by taking P-C and normalizing. (This constructs the outer tangent to the circle. If you want an inner tangent, you can flip.)
Construct the normalized perpendicular vector "N" to the line "L". I'm not really sure what your variables "ax,ay - bx,by" mean, so let's define the line by a start point "A = (ax, ay)" and a direction vector "DA = (dax, day)". In that case the normal is +/- (-day, dax)/sqrt(day*day+dax*dax). (The normalized cross with the (0,0,1) vector in 3d.)
Choose the sign of "N" so that it points away from P, i.e. if the dot product of (P-A) and N is positive, flip N. if the dot product is (nearly) zero, then the tangent circle would have radius (nearly) zero, and so is not defined.
Now consider a point TC defined by P2 = P + d*(R + N) for some d. If P2 lies on the line L, then d is the radius of the tangent circle we seek! P2 lies on the line if and only if the dot product of (P2 - A) and N is zero. This defines a linear equation in one variable -- d -- so you can solve for it. Note that if R + N is (nearly) of length zero, then P is 180 degrees away from the closest point between the circle and the line; you will need to check for this explicitly and handle it.
Once you have d, you can get the center of the circle TC=P + d*R.
This method should have good numerical stability when the tangent to the circle at P is parallel or nearly parallel to the line.
You didn't specify a language, but hopefully this can get you started. My primary language is c#.

How can I detect and remove unneeded points in cubic bezier

Here is example image of what I want to do:
I want to calculate Path 1 from Path 2.
Screenshot made from Inkscape, where I'm, at first, create Path 1, then add p3 to the original path. This is didn't change the original path at all, because new point actually unneeded. So, how can I detect this point(p3) using Path 2 SVG path representation and calculate Path 1 from Path 2?
Basically, I search for the math formulas, which can help me to convert(also checking that whether it is possible):
C 200,300 300,250 400,250 C 500,250 600,300 600,400
to
C 200,200 600,200 600,400
You're solving a constraint problem. Taking your first compound curve, and using four explicit coordinates for each subcurve, we have:
points1 = point[8];
points2 = point[4];
with the following correspondences:
points1[0] == points2[0];
points1[7] == points2[3];
direction(points1[0],points1[1]) == direction(points2[0], points2[1]);
direction(points1[6],points1[7]) == direction(points2[2], points2[3]);
we also have a constraint on the relative placement for points2[1] and points2[2] due to the tangent of the center point in your compound curve:
direction(points1[2],points[4]) == direction(points2[1],points2[2]);
and lastly, we have a general constraint on where on- and off-curve points can be for cubic curves if we want the curve to pass through a point, which is described over at http://pomax.github.io/bezierinfo/#moulding
Taking the "abc" ratio from that section, we can check whether your compound curve parameters fit a cubic curve: if we construct a new cubic curve with points
A = points1[0];
B = points1[3];
C = points1[7];
with B at t=0.5 (in this case), then we can verify whether the resulting curve fits the constraints that must hold for this to be a legal simplification.
The main problem here is that we, in general, don't know whether the "in between start and end" point should fall on t=0.5, or whether it's a different t value. The easiest solution is to see how far that point is along the total curve (using arc length: distance = arclength(c1) / arclength(c1)+arclength(c2) will tell us) and use that as initial guess for t, iterating outward on either side for a few values.
The second option is to solve a generic cubic equation for the tangent vector at your "in between" point. We form a cubic curve with points
points3 = [ points1[0], points1[1], points1[6], points1[7] ];
and then solve its derivative equations to find one or more t values that have the same tangent direction (but not magnitude!) as our in-between point. Once we have those (and we might have more than 2), we evaluate whether we can create a curve through our three points of interest with the middle point set to each of those found t values. Either one or zero of the found t values will yield a legal curve. If we have one: perfect, we found a simplification. If we find none, then the compound curve cannot be simplified into a single cubic curve.

reconstructing circles from Bezier curves

I am trying to reconstruct original graphics primitives from Postscript/SVG paths. Thus an original circle is rendered (in SVG markup) as:
<path stroke-width="0.5" d="M159.679 141.309
C159.679 141.793 159.286 142.186 158.801 142.186
C158.318 142.186 157.925 141.793 157.925 141.309
C157.925 140.825 158.318 140.432 158.801 140.432
C159.286 140.432 159.679 140.825 159.679 141.309" />
This is an approximation using 4 Beziers curves to create a circle.In other places circular arcs are approximated by linked Bezier curves.
My question is whether there is an algorithm I can use to recognize this construct and reconstruct the "best" circle. I don't mind small errors - they will be second-order at worst.
UPDATE: Note that I don't know a priori that this is a circle or an arc - it could be anything. And there could be 2, 3 4 or possibly even more points on the curve. So I'd really like a function of the sort:
error = getCircleFromPath(path)
where error will give an early indication of whether this is likely to be a circle.
[I agree that if I know it's a circle it's an easier problem.]
UPDATE: #george goes some way towards answering my problem but I don't think it's the whole story.
After translation to the origin and normalization I appear to have the following four points on the curve:
point [0, 1] with control point at [+-d,1] // horizontal tangent
point [1, 0] with control point at [1,+-d] // vertical tangent
point [0, -1] with control point at [+-d,-1] // horizontal tangent
point [-1, 0] with control point at [-1,+-d] // vertical tangent
This guarantees that the tangent at each point is "parallel" to the path direction at the point. It also guarantees the symmetry (4-fold axis with reflection. But it does not guarantee a circle. For example a large value of d will give a rounded box and a small value a rounded diamond.
My value of d appears to be about 0.57. This might be 1/sqrt(3.) or it might be something else.It is this sort of relationship I am asking for.
#george gives midpoint of arc as;
{p1,(p1 + 3 (p2 + p3) + p4)/8,p4}
so in my example (for 1,0 to 0,1) this would be:
[[1,0]+3[1,d]+3[d,1]+[0,1]] / 8
i.e.
[0.5+3d/8, 3d/8+0.5]
and if d =0.57, this gives 0.71, so maybe d is
(sqrt(0.5)-0.5)*8./3.
This holds for a square diamond, but for circular arcs the formula must be more general and I'd be grateful if anyone has it. For example, I am not familiar with Bezier math, so #george's formula was new to me
enter code here
Without doing all the math for you.. this may help:
there are always 4 control points on a bezier.
Your curve is 4 beziers linked together with points 1-4 , 4-7 , 7-10 , and 10-13 the control points
for each part. Points 1 , 4 , 7 and 10 (&13==1) lie exactly on the curve. To see if you have a nice circle calculate:
center = ( p1+p7 )/2 =( {159.679, 141.309} + {157.925, 141.309} ) / 2
= {158.802, 141.309}
verify you get the same result using points 4+10 -> {158.801, 141.309}
Once you know the center you can sample points along the curve and see if you have a constant distance.
If you only have a single bezier arc with 4 points a useful formula is that the midpoint is at
(p1 + 3 (p2 + p3) + p4)/8. So you can find the circle passing through three points:
{p1,(p1 + 3 (p2 + p3) + p4)/8,p4}
and again sample other points on the curve to decide if you indeed have a near circular arc.
Edit
the bezier formula is this:
x=(1-t)^3 p1 + 3 (1-t)^2 t p2 + 3 (1-t) t^2 p3 + t^3 p4 with parameter 0 < t < 1
so for example at t=1/4 you have
x=( 27 p1 + 27 p2 + 9 p3 + 1 p4 ) / 64
so once you find the center you can readily check a few points and calculate their distance.
I suspect if you only want to detect nearly exact circular arcs then checking two extra points with a tight tolerance will do the job. If you want to detect things that are approximately circular I would compute a bunch of points and use the average error as a criteria.
If all your elements are circle-like then you can just get the dimensions through path.getBBox() and generate a circle from there. In this case I'm considering ellipses, but you can easily translate it to actual circle elements:
var path = document.getElementById("circle_path");
var bbox = path.getBBox();
var rx = bbox.width/2;
var ry = bbox.height/2;
var cx = bbox.x + rx;
var cy = bbox.y + ry;
var ellipse = document.createElementNS(xmlns, "ellipse");
ellipse.setAttribute("fill", "none");
ellipse.setAttribute("stroke", "red");
ellipse.setAttribute("stroke-width", 0.1);
ellipse.setAttribute("cx", cx);
ellipse.setAttribute("cy", cy);
ellipse.setAttribute("rx", rx);
ellipse.setAttribute("ry", ry);
svg.appendChild(ellipse);
You can see a demo here:
http://jsfiddle.net/nwHm6/
The endpoints of the Bézier curves are probably on the circle. If so, it's easy to reconstruct the original circle.
Another possibility is to take the barycenter of the control points as the center of the circle because the control points are probably laid out symmetrically around the center. From the center, you get the radius as the average distance of the four control points closest to the center.
One can define an ellipse as a unit circle centred on (0,0), translated (2 params), scaled (2 params), and rotated (1 param). So on each arc take five points (t=0 ¼ ½ ¾ 1) and solve for these five parameters. Next take the in-between four points (t=⅛ ⅜ ⅝ ⅞), and test whether these lie on the same transformed circle. If yes, whoopee!, this is (part of) a transformed circle.
Immediately before and after might be another arc or arcn. Are these the same ellipse? If yes, and the subtended angles touch, then join together your descriptions of the pieces.

Resources