I wonder if there's a more intuitive way to define an elliptical arc given 2 endpoints. Dimension analysis shows we need 3 parameters. The SVG spec uses rx/ry/rotation, plus a couple booleans, but maybe there's a better parameterization without booleans? For example, it could be two coordinates of a control point - if the endpoints are A and B and the control point is C, the ellipse would be tangent to AC and BC - and an additional positive number for squishing the ellipse closer or farther from C, with values above 1 making the arc bubble outward. So it would occupy a natural spot between quadratic Bezier (with 2 parameters = 1 control point) and cubic Bezier (4 parameters = 2 control points).
I think an intuitive way is: Your starting point is on a circle at angle theta_1 and you want to draw an arc of length delta_theta (positive or negative). You could define the circle via center (x,y) or radius.
Edit: it looks like the SVG docs include the math for converting to and from this representation,
Get ready for a trip involving some projective 2D geometry..
If you recall the Greek's fascination with conic sections, an ellipse, hyperbola, parabola, and of course circle are all the same subject to projective 2D transformations. You can "slice through" an infinitely extending (double) cone with a plane and get conic sections, hence the name.
So you could have two end points (interpolating control points as the curve goes through these points) and third extrapolating control point. So like a quadratic Bezier segment with its three control points, but allow the extrapolating ("middle") control point to have a homogeneous "w" coordinate.
So think of three points (x0,y0), (x1,y1,w1), and (x2,y2); alternatively you can think of this as (x0,y0,1), (x1,y1,w1) and (x2,y2,1). It's effectively what is called a rational quadratic Bezier segment (rational in the same sense as the R in NURBS).
It turns out it is always possible to re-parameterize three homogeneous general control points of the form (X0,Y0,W0), (X1,Y1,W1), and (X2,Y2,W2) into the canonical form (x0,y0,1), (x1,y1,w1) and (x2,y2,1) -- basically forcing unit w values for the end-points.
In this canonical form, the w1 value has a natural interpretation. When w1==1, the curve is a parabolic segment (so is parameterized to match exactly SVG's quadratic Bezier segment). Hyperbolic segments have w1>1; elliptical segments have 0<w1<1 and with SVG's large-arc flag set false. When w1==0, that's a degenerate line segment (best parameterized as two line segments). When -1<w1<0, this is an elliptical segment consistent with having SVG's large-arc flag set true. When w1<-1, you get a hyperbolic segment that shoots out to infinity in two directions, as if emanating from w-negated extrapolated control point (x1,y1,-w1). As w1 tends to either negative or positive infinity, the conic degenerates to two crossing lines clipped by a dividing line (defined by the two interpolating control points).
So the conic segment is what cousin_it is asking for.. rather than "2 parameters = [extrapolating] 1 control point" or "4 parameters = 2 [extrapolating] control points)", the canonical conic segment is "3 parameters = 1.5 control points(!) in cousin_it's usage.
See this rather obscure but excellent paper:
Eugene T.Y. Lee. 1987. The Rational Bezier Representation for Conics. In Geometric Modeling: Algorithms and New Trends, Gerald E. Farin (Ed.). SIAM, Philadelphia, 3-19.
Google's Skia API actually supports a conic segment where you can supply a (non-negative!) value for w1. So you aren't allowed to draw the so-called "external" conics, but this is for a pretty good reason. Rendering gets pretty wonky when negative w values are allowed (normally such negative values are intuitively considered "behind the viewer" when thinking about 3D and so clipped).
There's lots of cool geometric intuition that can be assigned to the actual conic segment control points and the value of w. For an elliptical segment, the w1 value is the cosine of half the angle where the two tangent lines at the conic segment end-points, (x0,y0) and (x2,y2), where tangent is with respect to the ellipse.
We can use this geometric intuition to build a perfect unit 360-degree circle out of four conic segments. Take four (counter-clockwise winding) points on the unit circle (+1,0), (0,+1), (-1,0), and (0,-1) where the unit circle intersects the X and Y axes. The tangents at these points are vertical, horizontal, vertical, and horizontal lines respectively. A vertical and horizontal line meet at a right angle, so 90 degrees. Half of 90 degrees is 45 degrees; the cos(45 degrees) = 1/sqrt(2) ~= 0.7071.
So now we can build four conic segments:
1: (+1,0), (+1,+1,1/sqrt(2)), (0,+1) # upper-right quadrant
2: (0,+1), (-1,+1,1/sqrt(2)), (-1,0) # upper-left quadrant
3: (-1,0), (-1,-1,1/sqrt(2)), (0,-1) # lower-left quadrant
4: (0,-1), (+1,-1,1/sqrt(2)), (+1,0) # lower-right quadrant
It's pretty straightforward to see you can draw a 360-degree ellipse this same way, just scaling (to squish or stretch) the x and y values.
> maybe there's a better parameterization without booleans
Yes, there is; yep, the conic segment parameterization has no booleans.
> SVG spec uses rx/ry/rotation, plus a couple booleans
Honestly, the SVG partial elliptical arc specification looks reasonable at first glance but is honestly bonkers and hardly intuitive. If you play with an SVG tool that let's you modify rx/ry/rotation and the booleans, changing these can be really surprising. With a conic segment, it's just like a quadratic Bezier segment but with an extra control to "tug" at w1. Start at w1==1 and it IS a quadratic Bezier segment. Increase w above 1 and the curve "gets sharper" (increasingly hyperbolic). Decrease w below 1 and the curve "gets flatter" (increasingly elliptical). It's much like a tension control.
Since negative w1 values are problematic, you can always construct "large arcs" (also known as "external" conic segments) from a spline of w>0 conic segments. This isn't particularly hard.
You also get a big representational advantage with conic segments. All the scalars involved are scalar "coordinates" (x, y, and also w).
Contrast this with SVG's partial elliptical arc parameterization where you need nine(!) values of desperate types: two lengths (radii), one angle (rotation), two booleans (large-flag, sweep-direction), and two control points (four coordinates). SVG's parameterization cannot draw hyperbolic segments! Yet, it allows for weird situations such as an angle greater than 360 (so the arc can loop around multiple times??).
(SVG makes it look like an arc is specified with just 7 values, but that's a ruse because the initial end-point is borrowed from the prior command; 9 values is more honest to describe a "freestanding" partial elliptical arc.)
And if I'm putting my curved segments including arcs on the GPU, it's really nice to have a buffer of consistently uniform floating-point coordinates, not values of disparate type. Even more compelling, (x0,y0,1), (x1,y1,w1), and (x2,y2,1) can be treated as homogeneous points that can be transformed by a 3x3 projective 2D matrix and the curve that results from the transformed control points is the same as transforming the curve's points.
Want to apply a shear/scale/translate/rotate and even projection on an SVG parameterized partial elliptical arc?? Good luck, sucker. Hint for someone really wanting to do this: convert the SVG arc to a spline of one or more conic segments, transform those conic segment control points by your transform's matrix, then try to reassemble the transformed conic segments back into awkward SVG arcs.
Basically, the SVG arc parameterization is to 2D transformations of arcs what Euler angles are to composing 3D rotations!
Worth noting that SVG manipulation via JSX support (such as in React) can be incredibly useful in practice. I used this to add markers and highlights over scanned documents in the past, as well as bar charts, line graphs etc... all without having to add in massive module libraries. Most charting libraries are excessively large when you just want to show/chart a little data.
I really could have used this last year when I was making a web adventure game with randomly created map. SVG is one of those frontend technologies that seem pointless until you really need them. Dynamically creating SVG in response to user actions in the game provided a very nice graphical representation of the adventure.
Unlike the author of this piece, I found the lower-case relative commands extremely useful for creating shapes like arrows (or in my case, sectors of space) that can be translated around later.
And SVG means you can make a single file that contains everything. Pretty much irrelevant on the web, but we lack a good container format for putting a web page into a single document on disk.
I've done entire multi-layer maps in SVG, complete with clicking on the map taking you to any associated details. The part I never found a good answer for is when I wanted to use higher detail glyphs on higher resolution outputs. (Think of the sequence of lines typically used to represent stairs.)
I always took for granted that devs knew about SVG, but I had come into the industry by way of design, so I had already been toying with vectors for years with Adobe Illustrator and Sketch.
SVG is awesome. Heavily underinvested in the web spec, would love to see SVG2 get some attention.
Loved this. Thought it was perfect for my skill level (I know what an .svg is; that's about it). I think what I learned will actually come in handy the next time I'm trying to adjust / optimize an icon.
I really liked the sound effects! Spent perhaps too long just picking up and dragging stuff.
Well done on the excellent interactive visual explanation!
Having experience reading/writing SVG paths from scratch, here are a few additions for the "Lil' extras" section:
* 'H' (horizontal) is a shorthand for 'L' (line) where the y-coordinate does not change. For example, "M 12,34 H 56" is shorthand for "M 12,34 L 56,34". There is of course a lowercase relative version too, 'h', so "M 12,34 h 56" means "M 12,34 l 56,0".
* 'V' (vertical) is a shorthand for 'L' (line) where the x-coordinate does not change. For example, "M 12,34 V 56" is shorthand for "M 12,34 L 12,56". There is of course a lowercase relative version too, 'v', so "M 12,34 v 56" means "M 12,34 v 0,56".
Is there a drawing program which focuses on the programmability aspect of SVG editing while allowing drawing in a natural fashion?
I've done it in Inkscape, but it's a bit awkward --- there was a programming orienting tool mentioned here a while back, but it was light on the drawing aspect.
M and L and C are abbreviations for "moveto" and "lineto" and "curveto" from Postscript (which SVG was based on). I thought pen up and pen down in logo were "PENUP" and "PENDOWN."
It looks completely ordinary to me--exactly the sort of thing I wrote in HP/GL2 stuff for plotters. Your only possible tool on a plotter is a pen, of course the language is based on pen movements.
Just something that would be worthwhile looking into is preventing blanking out the entire page if cookies (and by extension local storage) are disabled. The page loads fully briefly then blanks out to a full viewport React error. I see this on various sites where it's avoidable.
Very nice! We had a use case where we needed to convert some images into SVGs. Since AI performed poorly at that task, we had to spend time reading and understanding the SVG code. This makes things much easier.
Curves were tough for me when I did logos. And at one point I decided one would be better off rotated, and realized a transform would be far, far easier than trying to modify the paths to run on a slant.
heh, im currently working on an svg pathstring to canvs2d command compiler [1] and had to dive into this topic last week, including converting arc commands from centerpoint parameterization to endpoint parameterization [2]
Yes, TrueType fonts use quadratic beziers while PostScript/OpenType use cubic beziers, and modern GPUs offer acceleration through tessellation shaders and compute pipelines like NV_path_rendering extension.
Note that while cubic bezier curves are nice for designing shapes, quadratic bezier curves allow a much more optimized renderer. It's usually worth approximating.
I wonder if there's a more intuitive way to define an elliptical arc given 2 endpoints. Dimension analysis shows we need 3 parameters. The SVG spec uses rx/ry/rotation, plus a couple booleans, but maybe there's a better parameterization without booleans? For example, it could be two coordinates of a control point - if the endpoints are A and B and the control point is C, the ellipse would be tangent to AC and BC - and an additional positive number for squishing the ellipse closer or farther from C, with values above 1 making the arc bubble outward. So it would occupy a natural spot between quadratic Bezier (with 2 parameters = 1 control point) and cubic Bezier (4 parameters = 2 control points).
I think an intuitive way is: Your starting point is on a circle at angle theta_1 and you want to draw an arc of length delta_theta (positive or negative). You could define the circle via center (x,y) or radius.
Edit: it looks like the SVG docs include the math for converting to and from this representation,
https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpoin...
The answer is YES.
Get ready for a trip involving some projective 2D geometry..
If you recall the Greek's fascination with conic sections, an ellipse, hyperbola, parabola, and of course circle are all the same subject to projective 2D transformations. You can "slice through" an infinitely extending (double) cone with a plane and get conic sections, hence the name.
So you could have two end points (interpolating control points as the curve goes through these points) and third extrapolating control point. So like a quadratic Bezier segment with its three control points, but allow the extrapolating ("middle") control point to have a homogeneous "w" coordinate.
So think of three points (x0,y0), (x1,y1,w1), and (x2,y2); alternatively you can think of this as (x0,y0,1), (x1,y1,w1) and (x2,y2,1). It's effectively what is called a rational quadratic Bezier segment (rational in the same sense as the R in NURBS).
It turns out it is always possible to re-parameterize three homogeneous general control points of the form (X0,Y0,W0), (X1,Y1,W1), and (X2,Y2,W2) into the canonical form (x0,y0,1), (x1,y1,w1) and (x2,y2,1) -- basically forcing unit w values for the end-points.
In this canonical form, the w1 value has a natural interpretation. When w1==1, the curve is a parabolic segment (so is parameterized to match exactly SVG's quadratic Bezier segment). Hyperbolic segments have w1>1; elliptical segments have 0<w1<1 and with SVG's large-arc flag set false. When w1==0, that's a degenerate line segment (best parameterized as two line segments). When -1<w1<0, this is an elliptical segment consistent with having SVG's large-arc flag set true. When w1<-1, you get a hyperbolic segment that shoots out to infinity in two directions, as if emanating from w-negated extrapolated control point (x1,y1,-w1). As w1 tends to either negative or positive infinity, the conic degenerates to two crossing lines clipped by a dividing line (defined by the two interpolating control points).
So the conic segment is what cousin_it is asking for.. rather than "2 parameters = [extrapolating] 1 control point" or "4 parameters = 2 [extrapolating] control points)", the canonical conic segment is "3 parameters = 1.5 control points(!) in cousin_it's usage.
See this rather obscure but excellent paper:
Eugene T.Y. Lee. 1987. The Rational Bezier Representation for Conics. In Geometric Modeling: Algorithms and New Trends, Gerald E. Farin (Ed.). SIAM, Philadelphia, 3-19.
Google's Skia API actually supports a conic segment where you can supply a (non-negative!) value for w1. So you aren't allowed to draw the so-called "external" conics, but this is for a pretty good reason. Rendering gets pretty wonky when negative w values are allowed (normally such negative values are intuitively considered "behind the viewer" when thinking about 3D and so clipped).
There's lots of cool geometric intuition that can be assigned to the actual conic segment control points and the value of w. For an elliptical segment, the w1 value is the cosine of half the angle where the two tangent lines at the conic segment end-points, (x0,y0) and (x2,y2), where tangent is with respect to the ellipse.
We can use this geometric intuition to build a perfect unit 360-degree circle out of four conic segments. Take four (counter-clockwise winding) points on the unit circle (+1,0), (0,+1), (-1,0), and (0,-1) where the unit circle intersects the X and Y axes. The tangents at these points are vertical, horizontal, vertical, and horizontal lines respectively. A vertical and horizontal line meet at a right angle, so 90 degrees. Half of 90 degrees is 45 degrees; the cos(45 degrees) = 1/sqrt(2) ~= 0.7071.
So now we can build four conic segments: 1: (+1,0), (+1,+1,1/sqrt(2)), (0,+1) # upper-right quadrant 2: (0,+1), (-1,+1,1/sqrt(2)), (-1,0) # upper-left quadrant 3: (-1,0), (-1,-1,1/sqrt(2)), (0,-1) # lower-left quadrant 4: (0,-1), (+1,-1,1/sqrt(2)), (+1,0) # lower-right quadrant
It's pretty straightforward to see you can draw a 360-degree ellipse this same way, just scaling (to squish or stretch) the x and y values.
> maybe there's a better parameterization without booleans
Yes, there is; yep, the conic segment parameterization has no booleans.
> SVG spec uses rx/ry/rotation, plus a couple booleans
Honestly, the SVG partial elliptical arc specification looks reasonable at first glance but is honestly bonkers and hardly intuitive. If you play with an SVG tool that let's you modify rx/ry/rotation and the booleans, changing these can be really surprising. With a conic segment, it's just like a quadratic Bezier segment but with an extra control to "tug" at w1. Start at w1==1 and it IS a quadratic Bezier segment. Increase w above 1 and the curve "gets sharper" (increasingly hyperbolic). Decrease w below 1 and the curve "gets flatter" (increasingly elliptical). It's much like a tension control.
Since negative w1 values are problematic, you can always construct "large arcs" (also known as "external" conic segments) from a spline of w>0 conic segments. This isn't particularly hard.
You also get a big representational advantage with conic segments. All the scalars involved are scalar "coordinates" (x, y, and also w).
Contrast this with SVG's partial elliptical arc parameterization where you need nine(!) values of desperate types: two lengths (radii), one angle (rotation), two booleans (large-flag, sweep-direction), and two control points (four coordinates). SVG's parameterization cannot draw hyperbolic segments! Yet, it allows for weird situations such as an angle greater than 360 (so the arc can loop around multiple times??).
(SVG makes it look like an arc is specified with just 7 values, but that's a ruse because the initial end-point is borrowed from the prior command; 9 values is more honest to describe a "freestanding" partial elliptical arc.)
And if I'm putting my curved segments including arcs on the GPU, it's really nice to have a buffer of consistently uniform floating-point coordinates, not values of disparate type. Even more compelling, (x0,y0,1), (x1,y1,w1), and (x2,y2,1) can be treated as homogeneous points that can be transformed by a 3x3 projective 2D matrix and the curve that results from the transformed control points is the same as transforming the curve's points.
Want to apply a shear/scale/translate/rotate and even projection on an SVG parameterized partial elliptical arc?? Good luck, sucker. Hint for someone really wanting to do this: convert the SVG arc to a spline of one or more conic segments, transform those conic segment control points by your transform's matrix, then try to reassemble the transformed conic segments back into awkward SVG arcs.
Basically, the SVG arc parameterization is to 2D transformations of arcs what Euler angles are to composing 3D rotations!
> "nine(!) values of desperate types"
Appropriate typo...
Thanks for the explainer, interesting stuff!
If you were to create a web page explaining these conics with some interactive visuals, I bet it would be popular here.
Worth noting that SVG manipulation via JSX support (such as in React) can be incredibly useful in practice. I used this to add markers and highlights over scanned documents in the past, as well as bar charts, line graphs etc... all without having to add in massive module libraries. Most charting libraries are excessively large when you just want to show/chart a little data.
I'm really glad the article links to Freya's "Beauty of Bezier Curves" videos, it's genuinely one of the best math/graphics/explainer videos ever made
https://youtu.be/aVwxzDHniEw
It’s worth a watch even if you aren’t interested in the subject just because it’s so well done.
Same title, but different article:
An interactive guide to SVG paths - https://news.ycombinator.com/item?id=36574645 - July 2023 (39 comments)
I really could have used this last year when I was making a web adventure game with randomly created map. SVG is one of those frontend technologies that seem pointless until you really need them. Dynamically creating SVG in response to user actions in the game provided a very nice graphical representation of the adventure.
Unlike the author of this piece, I found the lower-case relative commands extremely useful for creating shapes like arrows (or in my case, sectors of space) that can be translated around later.
And SVG means you can make a single file that contains everything. Pretty much irrelevant on the web, but we lack a good container format for putting a web page into a single document on disk.
I've done entire multi-layer maps in SVG, complete with clicking on the map taking you to any associated details. The part I never found a good answer for is when I wanted to use higher detail glyphs on higher resolution outputs. (Think of the sequence of lines typically used to represent stairs.)
NaN published a really great page about SVG paths a while ago: https://www.nan.fyi/svg-paths
I always took for granted that devs knew about SVG, but I had come into the industry by way of design, so I had already been toying with vectors for years with Adobe Illustrator and Sketch.
SVG is awesome. Heavily underinvested in the web spec, would love to see SVG2 get some attention.
Everything Comeau puts out is so goddamn good. I paid for his CSS course a year ago and the effort that went into the pedagogy is just spectacular.
This is so detailed and easy to understand. Thank you for writing this up.
Anything by Josh is gold.
Great article, but the entire website is excellent too. Definitely worth exploring.
He seems like a genuinely nice person too.
Loved this. Thought it was perfect for my skill level (I know what an .svg is; that's about it). I think what I learned will actually come in handy the next time I'm trying to adjust / optimize an icon.
I really liked the sound effects! Spent perhaps too long just picking up and dragging stuff.
Thanks!
Well done on the excellent interactive visual explanation!
Having experience reading/writing SVG paths from scratch, here are a few additions for the "Lil' extras" section:
* 'H' (horizontal) is a shorthand for 'L' (line) where the y-coordinate does not change. For example, "M 12,34 H 56" is shorthand for "M 12,34 L 56,34". There is of course a lowercase relative version too, 'h', so "M 12,34 h 56" means "M 12,34 l 56,0".
* 'V' (vertical) is a shorthand for 'L' (line) where the x-coordinate does not change. For example, "M 12,34 V 56" is shorthand for "M 12,34 L 12,56". There is of course a lowercase relative version too, 'v', so "M 12,34 v 56" means "M 12,34 v 0,56".
Is there a drawing program which focuses on the programmability aspect of SVG editing while allowing drawing in a natural fashion?
I've done it in Inkscape, but it's a bit awkward --- there was a programming orienting tool mentioned here a while back, but it was light on the drawing aspect.
GodSVG: https://www.godsvg.com/
Boxy SVG: https://boxy-svg.com/#demo-elements
Repath Studio: https://repath.studio/
MacSVG: http://macsvg.org
I usually use this: https://yqnn.github.io/svg-path-editor/
I wonder if Seymour Papert's Logo programming language influenced the SVG path syntax. M and L correspond directly to "pen up/pen down" and move.
M and L and C are abbreviations for "moveto" and "lineto" and "curveto" from Postscript (which SVG was based on). I thought pen up and pen down in logo were "PENUP" and "PENDOWN."
It looks completely ordinary to me--exactly the sort of thing I wrote in HP/GL2 stuff for plotters. Your only possible tool on a plotter is a pen, of course the language is based on pen movements.
Just something that would be worthwhile looking into is preventing blanking out the entire page if cookies (and by extension local storage) are disabled. The page loads fully briefly then blanks out to a full viewport React error. I see this on various sites where it's avoidable.
The aside about whitespace and punctuation was interesting. Is there an SVG tidy program out there that will make your files more readable?
Very nice! We had a use case where we needed to convert some images into SVGs. Since AI performed poorly at that task, we had to spend time reading and understanding the SVG code. This makes things much easier.
Were they images of pelicans on bicycles?
No, but I need to look that up now!
https://simonwillison.net/tags/pelican-riding-a-bicycle/
Curves were tough for me when I did logos. And at one point I decided one would be better off rotated, and realized a transform would be far, far easier than trying to modify the paths to run on a slant.
This is a fantastic article, and the interactive visuals really drive it home!
heh, im currently working on an svg pathstring to canvs2d command compiler [1] and had to dive into this topic last week, including converting arc commands from centerpoint parameterization to endpoint parameterization [2]
[1] https://github.com/leeoniya/uMarks (WIP!)
[2] https://www.w3.org/TR/SVG/implnote.html#ArcConversionCenterT...
I enjoyed the linked explainer video about the math of Bézier curves.
Of all the technologies I got exposure to on my Comp Sci degree, SVG is the only one I really still use every day.
Wow someone put a lot of work into this. Always had trouble with the path element. Thanks!
10 years hearing about SVG and now with 2 minutes I understand, it's like Logo language, the turtle. Thanks for the tutorial!
those sounds are wonderful
Yeh it gets an upvote even just for the plop and ticks :-)
Would the same bezier curves be used in font rendering? And do we have GPU bezier drawing functions or shader kernels available?
Yes, TrueType fonts use quadratic beziers while PostScript/OpenType use cubic beziers, and modern GPUs offer acceleration through tessellation shaders and compute pipelines like NV_path_rendering extension.
Note that while cubic bezier curves are nice for designing shapes, quadratic bezier curves allow a much more optimized renderer. It's usually worth approximating.
[dead]