Skip to content

Instantly share code, notes, and snippets.

@Phrogz
Created February 27, 2011 04:28
Show Gist options
  • Save Phrogz/845901 to your computer and use it in GitHub Desktop.
Save Phrogz/845901 to your computer and use it in GitHub Desktop.
Convert a SVG path to a Polygon by sampling the path, but also including all control points in the result. See http://phrogz.net/SVG/convert_path_to_polygon.xhtml
// http://phrogz.net/SVG/convert_path_to_polygon.xhtml
function pathToPolygon(path,samples){
if (!samples) samples = 0;
var doc = path.ownerDocument;
var poly = doc.createElementNS('http://www.w3.org/2000/svg','polygon');
// Put all path segments in a queue
for (var segs=[],s=path.pathSegList,i=s.numberOfItems-1;i>=0;--i) segs[i] = s.getItem(i);
var segments = segs.concat();
var seg,lastSeg,points=[],x,y;
var addSegmentPoint = function(s){
if (s.pathSegType == SVGPathSeg.PATHSEG_CLOSEPATH){
}else{
if (s.pathSegType%2==1 && s.pathSegType>1){
// All odd-numbered path types are relative, except PATHSEG_CLOSEPATH (1)
x+=s.x; y+=s.y;
}else{
x=s.x; y=s.y;
}
var lastPoint = points[points.length-1];
if (!lastPoint || x!=lastPoint[0] || y!=lastPoint[1]) points.push([x,y]);
}
};
for (var d=0,len=path.getTotalLength(),step=len/samples;d<=len;d+=step){
var seg = segments[path.getPathSegAtLength(d)];
var pt = path.getPointAtLength(d);
if (seg != lastSeg){
lastSeg = seg;
while (segs.length && segs[0]!=seg) addSegmentPoint( segs.shift() );
}
var lastPoint = points[points.length-1];
if (!lastPoint || pt.x!=lastPoint[0] || pt.y!=lastPoint[1]) points.push([pt.x,pt.y]);
}
for (var i=0,len=segs.length;i<len;++i) addSegmentPoint(segs[i]);
for (var i=0,len=points.length;i<len;++i) points[i] = points[i].join(',');
poly.setAttribute('points',points.join(' '));
return poly;
}
@nicholaswmin
Copy link

Hi man, what about adaptive sampling? A friend showed me this and it seems to be more ''efficient''. http://antigrain.com/research/adaptive_bezier/

@deusaquilus
Copy link

This will return NaNs if you have SVGPathSegLineto(Vertical/Horizontal)(Abs/Rel) i.e. 'V/H' and 'v/h' elements. Eg. if you have a path that contains c0,2.995,2.776,5.398h85.489c3.431... it will return a 'NaN' for the y choord since there's only an x choord in the element. To fix it, change addSegmentPoint to:

var addSegmentPoint = function(s) {
    if (s.pathSegType == SVGPathSeg.PATHSEG_CLOSEPATH) {

    } else {
        if (s.pathSegType % 2 == 1 && s.pathSegType > 1) {
            // All odd-numbered path types are relative, except PATHSEG_CLOSEPATH (1)
            x += s.x ? s.x : 0;    // if it's a relative vertical i.e. 'v' there's no horizontal coord so use 0 as the horizontal
            y += s.y ? s.y : 0;    // if it's a relative horizontal i.e. 'h' there's no vertical coord so use 0 as the vertical
        } else {
            x = s.x ? s.x : x;    // if it's a absolute vertical i.e. 'V' there's no horizontal coord so keep the horizontal where it was last time
            y = s.y ? s.y : y;    // if it's a absolute horizontal i.e. 'H' there's no vertical coord so keep the vertical where it was last time
        }
        var lastPoint = points[points.length - 1];
        if (!lastPoint || x != lastPoint[0] || y != lastPoint[1]) {
            points.push([x, y]);
            if (isNaN(x) || isNaN(y)) {
                throw new Error("found a choord with a NaN value")
            }
        }
    }
};

@peteruithoven
Copy link

Great work! We've implemented it in svg.topoly.js a plugin for svg.js.

@croonerg
Copy link

croonerg commented Jan 8, 2018

SVGPathElement.getPathSegAtLength is removed from Chrome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment