> Stroke expansion is a global problem, with challenging constraints on continuity and correctness. Nonetheless, we implement it using a fully parallel algorithm suitable for execution in a GPU compute shader, with minimal preprocessing.
It's hard but we did it anyway.
Here's the full program: https://www.highperformancegraphics.org/2024/program/
(Disclosure: I'm on the HPG conference committee.)
This is something that Adobe Illustrator could do 20 years ago, but is still not possible in Inkscape. I.e., you select a path, set a calligraphic pen on it, and then optionally covert the stroke to its outline path.
1. Apply the inverse of that transform to your path.
2. Stroke the path.
3. Apply the transform to the result.
This way, the path stays in place but the stroke is transformed to give it a calligraphic look.
With a JavaScript Canvas, you can just set the transform after defining the path, but before stroking. JSFiddle example: [0].
(This was something that I tested in my tiny, single-header <canvas>-like 2D rasterizer library for C++ and my Javascript port of its test suite [1].)
For Inkscape, I think you can convert an object to a path, apply the inverse transform, do a minimal simplification to bake the transform into the path, stroke it, and then apply the forward transform. It's a bit clumsy, but I bet someone could easily create an extension script to do it.
[0] https://jsfiddle.net/y7m16wa0/
[1] https://github.com/a-e-k/canvas_ity/blob/main/test/test.cpp#..., https://github.com/a-e-k/canvas_ity/blob/main/test/test.html...
Illustrator's stroke control is a touch more advanced than most people are aware in that there is full control over the width of the stroke at any point on the line and that includes the ability to independently adjust the width on either side of the path.
That's especially useful for emulating handwork or script without forcing the object into a less editable format such as a filled shape.
To visualise this, see this graphic: https://imgur.com/a/303dgSf
The black thin line is a basic stroke to visualise the path. The light blue area is the stroke with edits to the stroke width showing how each side of the path can be controlled independently. I have kept the tool selection active such that a wireframe overlay is visible to help demonstrate how the stroke width is different on either side of the line.
The last area in the graphic is the pink area. The pink is another stroke applied to the same path. In fact all 3 strokes you see are all attached to one path. Illustrator allows a path to carry multiple strokes, and those stroke profiles be independent from one another.
Side note: This stroke-width information can then be applied to other strokes, and also saved for later use. (Handy if you want a custom swash pack.)
... and Metafont could do over 40 years ago. Part of the problem might be that finding a good class of curves closed under convolution (that’s the general term for this kind of thing) remains a research problem, thus not really something you see in open formats (including e.g. PostScript—part of why converting Metafont to more conventional vector formats is difficult). But it’s the first time I (an amateur) am seeing Levien’s thesis mentioned in this context, so I’ll have to check that out.
Also see the comment by the author of the paper and library: https://news.ycombinator.com/item?id=40890270
[1]: https://github.com/linebender/gpu-stroke-expansion-paper
They are based on this work, Vello is the vector graphic rendering library and Xilem a high level ui framework.
And he is the first author of the paper (and he is also commenting in this same thread)
I'd give my interest in Hell for a graphical interface for METAFONT which would take a series of pen strokes and provide the outline --- which reminds me that I never heard back from the METAFOG author on a license....
But it does mean that for outline-to-outline applications running on the CPU, methods like Skia's are likely better (though ours is still faster than the Nehab implementation).
I'm working on a cubic-to-cubic version also, but it's requiring some pretty deep math. See the Zulip thread to follow that work in progress[1].
[1]: https://xi.zulipchat.com/#narrow/stream/260979-kurbo/topic/C...
A stroked path is a path with a non-zero width.
Stroke expansion is going from a path to a stroked path.
Turns out that the math is quite complex.
I'm in search of a precise and consensus definition of a path with non-zero width... does anyone know where that is in the svg spec?
It's how you go from drawing 1px width line to drawing paint covered area left by some brush following this line.
(claim to knowledge: from time on Android watches, incl. building an automated power testing framework at Google, left in October)
- Power cost optimization is found by handwaving avoiding something spinning up, or due to very long actions (i.e. 5+ seconds)
- The GPU is already running if a frame is due to be rendered.
- If we're running drawing code that draws a stroke, a frame is due to be rendered. Therefore the additional cost to run the stroke render can be considered infinitesimal
- Its unintuitive how much power savings can be achieved by "racing to zero" (i.e. doing something faster)
- The algorithm is a better racer to zero because it runs in parallel
The general rule of thumb is that a GPU has about 10x the raw throughput per joule as a CPU (which, today, is basically the same metric as throughput per dollar). So if your GPU is only barely faster than the CPU because of overheads of the parallel algorithm, you might not win, but if it sails way past the CPU because it's work efficient and lean (as I believe this work is), you do win both on power and throughput (frames per second).
https://wwwtyro.net/2019/11/18/instanced-lines.html is also nice.