I have an SVG path, for example
<path id="stroke" d="M1 1V8" fill="none" stroke="#FFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
and what I would like some kind of function, that takes in the path (M1 1V8
) and the stroke width (2
) and returns it's outline, so that if I put the outline in
<path id="outline" d="..." fill="#FFF" fill-rule="nonzero"/>
it will look the exact same as the original stroke path.
So the outline of my example stroke (M1 1V8
) would have been
M0 1L0 9C0 9.552 0.448 10 1 10C1.552 10 2 9.552 2 9L2 1C2 0.448 1.552 0 1 0C0.448 0 0 0.448 0 1
But the problem is, that I have no idea, how to do this when multiple Bézier curves cross, like for example in M1 1Q1 5 9 9M9 1Q9 5 1 9
, or when there is a hole, like for example in M1 1H8V8H1Z
.
I have spent hours searching the internet for how this could be done and libraries that can do it, however, all I was able to find was
Maker.js, however, while it can calculate the outline just like I want, it will often fill in holes (like for example M1 1H8V8H1Z
) and it will often break and just ignore some line segments and adding tiny offsets (like .001
) to points will often cause it to just cut of half the outline. I used the browser version of Maker.js by running
makerjs.exporter.toSVGPathData(makerjs.model.simplify(makerjs.model.outline(makerjs.importer.fromSVGPathData(stroke), strokeWidth / 2)), {
accuracy: 10e-9,
fillRule: "nonzero",
origin: [0, 0]
});
I would really appreciate it if someone knew how to do this or a JavaScript library that can do it.
There is no exact way to calculate offset paths from given pathdata as explained here:
"Primer on Bézier Curves: §39 Curve offsetting".
That's why we need some tricks to get a visually appealing result.
Dedicated vector graphic apps provide the best results
Pretty much any vector editor (such as free/open-source Inkscape or commercial solutions like Adobe Illustrator, Affinity Designer, Corel Draw etc.) provides a way to convert strokes to paths.
So you should first check batch processing options like e.g Inkscape's cli or Adobe illustrator actions or user scripts to streamline this process.
The following examples highlight the command end points to illustrate the conversion result (less points and more symmetric distribution = better approximation).
JS approach: maker.js
Actually not bad at all but still not on par with aforementioned graphic apps. The merging of separate sup paths should also work just fine.
However, maker.js can't reproduce all fine-grained options like
stroke-linejoin
or specific meter-limits. Worth noting you can at least emulatestroke-linecap
with 3 options using theexpandPaths()
methodMake sure you also load bezier.js that does the heavy lifting when calculating/approximating offset paths.
Converter web app services
iconly also provides an automated service to convert stroked graphics to outlines. "iconly: Convert SVG Strokes to Fills"
As you can see, the result introduces a lot of additional commands. However it still rebuilds the original curves quite accurately.
AFAIK this conversion is based on a tracing concept (using potrace).
JS: retrace svg via potrace.js
Although the idea of rasterizing a vector graphic and then tracing it back to vector image feels terribly wrong ... we can in fact bypass a lot of challenges such as converting advanced
stroke-linejoin
ormiter-limit
settings that are otherwise difficult to emulate.Besides we can make sure we have a super-clean bitmap source for tracing.
See this working example on codepen.
JS: convert via paper.js with paperjs-offset extension
paperjs-offset is an extension for paper.js allowing you to calculate offset paths.
The results are also quite clean. Depending on the curvature you might also sometimes return undesired results.