I have a set of coordinates that I turn into an svg path (using cubic beziers to make it smooth). When I apply a certain stroke width, I get the following result (the blue dots are my coordinates)
What I am interested in is to get a path that runs around the gray shape (like: pick any point on the gray/white border, and round around the shape until you're back at the starting point).
How would I go about computing such a path?
for reference, this is my svg info:
<g>
<title>number 3</title>
<path d="m238,50c5.67569,-1.01351 11.8327,-3.8229 20.92029,-2.14724c8.68106,0.69732 14.21173,4.90255 18.07971,7.14724c6.23697,3.61945 13.47556,9.5931 15,18c1.07056,5.90372 1.17343,10.97649 -4,16c-6.76816,6.57204 -19.45392,9.57738 -25.69687,10.59046c-3.94836,0.64074 4.73492,3.29883 10.69687,5.40954c8.05417,2.85142 15,8 21,14c6,6 5.26578,10.94739 5.26578,17.03015c-2.4541,7.30975 -4.23343,11.08675 -11.26578,12.96985c-3.98279,1.0665 -11.92578,3.49756 -17,4c-8.95618,0.88684 -15.80411,2.97838 -26,0l-9.19197,-3.44464" id="svg_1" opacity="0.5" stroke-width="10" stroke-linejoin="round" stroke="#000000" fill="none"/>
</g>
A path is defined in SVG using the 'path' element. The basic shapes are all described in terms of what their equivalent path is, which is what their shape is as a path. (The equivalent path of a 'path' element is simply the path itself.)
I created the svgContour function that is thought for scenarios similar to this,
the resulting contour offset is not related to the stroke-width
value and must be set as a parameter to the function.
At the moment it will find one offset side at a time, but running it once per sides you can solve this issue.
TLDR
Actually through svgContour you can find the contour of any svg shapes, currently it doesn't support fill-modes but one of the next goals is to implement that.
it relies on getPathData() to get the pathData of any SVGGeometryElement,
then this data goes through three phases:
redrawSteepCurve
Premise: drawing a beizer-curve parallel to another isn't obtained just offsetting the points/control-points of the curve unless the curve is sufficiently flat (in this case the visual rendering will be fine); this method takes a SVGPathData and in case it finds a steep curve it splits it up until it's flat enough (return a visually equivalent SVGPathData).
contourPathData
In this stage the pathData is dissambled in points, points are connected in segments, each segment is offsetted, an intersection point is then found for each contiguous segment ( what we get back is a list of offsetted points).
drawLine
This phase places the points from step 2 in the pathData coming from step 1, and finally draws the contour.
An example:
const path = document.querySelector('path')
svgContour(path, 5)
svg {
width: 100vw;
height: 100vh
}
<script src="https://cdn.rawgit.com/fracalo/svg-contour/master/dist/svg-contour.js"></script>
<svg viewBox='300 0 100 200'>
<g>
<title>number 3</title>
<path d="m238,50c5.67569,-1.01351 11.8327,-3.8229 20.92029,-2.14724c8.68106,0.69732 14.21173,4.90255 18.07971,7.14724c6.23697,3.61945 13.47556,9.5931 15,18c1.07056,5.90372 1.17343,10.97649 -4,16c-6.76816,6.57204 -19.45392,9.57738 -25.69687,10.59046c-3.94836,0.64074 4.73492,3.29883 10.69687,5.40954c8.05417,2.85142 15,8 21,14c6,6 5.26578,10.94739 5.26578,17.03015c-2.4541,7.30975 -4.23343,11.08675 -11.26578,12.96985c-3.98279,1.0665 -11.92578,3.49756 -17,4c-8.95618,0.88684 -15.80411,2.97838 -26,0l-9.19197,-3.44464"
id="svg_1" opacity="0.5" stroke-width="10" stroke-linejoin="round" stroke="#000000" fill="none" />
</g>
</svg>
the contour is traced !
I'm not sure whether this solves your problem. It depends on what you want to do with the outline path.
PostScript has functionality for computing an outline for stroked paths which, when filling it, will produce the same visual output as stroking the original path. However, the resulting path data might be less elegant than you'd expect.
The following PostScript program (let's call it path2outlines.ps
turns a PostScript path into a "fillable" SVG path:
%!
% First, we're converting to outlines.
strokepath
% This is just a string buffer
/S 99 string def
% This defines a procedure for printing coordinates to stdout
/printCoordinates { % coord* number command
print % coord* number
array astore % array
{ % coord
( )print % coord
//S cvs % string
print %
}forall
(\n)print
} bind def
% This iterates over the path segments and prints the SVG path data.
{2(M) printCoordinates}
{2(L) printCoordinates}
{6(C) printCoordinates}
{(Z\n)print}
pathforall
quit
You have to feed it data files like this (%
starts a comment). Let's call this data.ps
:
%!
% First, set up the graphics state parameters
% Equivalent to stroke-width="10"
10 setlinewidth
% Equivalent to stroke-linejoin="round" (0 = miter, 1 = round, 2 = bevel)
1 setlinejoin
% Now, we're defining the path.
% Use postfix notation, i.e. first coordinates, then command.
% m/M = moveto
% l = rlineto
% L = lineto
% c = rcurveto
% C = curveto
238 50 moveto
5.67569 -1.01351 11.8327 -3.8229 20.92029 -2.14724 rcurveto
8.68106 0.69732 14.21173 4.90255 18.07971 7.14724 rcurveto
6.23697 3.61945 13.47556 9.5931 15 18 rcurveto
1.07056 5.90372 1.17343 10.97649 -4 16 rcurveto
-6.76816 6.57204 -19.45392 9.57738 -25.69687 10.59046 rcurveto
-3.94836 0.64074 4.73492 3.29883 10.69687 5.40954 rcurveto
8.05417 2.85142 15 8 21 14 rcurveto
6 6 5.26578 10.94739 5.26578 17.03015 rcurveto
-2.4541 7.30975 -4.23343 11.08675 -11.26578 12.96985 rcurveto
-3.98279 1.0665 -11.92578 3.49756 -17 4 rcurveto
-8.95618 0.88684 -15.80411 2.97838 -26 0 rcurveto
-9.19197 -3.44464 rlineto
Depending on the platform you're using, you can use Ghostscript to invoke it somewhat similar to:
gs -q data.ps path2outlines.ps > outlinePath.txt
Give it a try, but I'm not sure whether you will be satisfied. The complexity of the output might give a hint about the actual complexity of the problem. Especially self-intersecting paths are a problem.
Edit (speaking of problems): I believe that in general it's mathematically not possible to create a Bézier curve that is perfectly "parallel" to a given Bézier curve.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With