Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simplifying SVG path in javascript

Does anybody know a js library/algorithm for optimizing SVG path? I need to optimize paths only (reduce number of nodes). My path is autogenerated and is full of beziers, so simplify.js mentioned in a similar question won't fit. I'm also required to use browser, so node-backed modules won't fit too.

The most perfect optimization is done by Inkscape, but I don't want to port 1000+ lines of path optimization code from C++ to JS.

I'm looking for something like this: http://paperjs.org/examples/path-simplification/

like image 638
user37741 Avatar asked May 25 '26 08:05

user37741


2 Answers

Finally used paperjs. Unfortunately they don't support modular builds yet

like image 132
user37741 Avatar answered May 27 '26 20:05

user37741


I found this years ago for my purpose from simplify.js

// square distance between 2 points
function getSqDist(p1, p2) {
  var dx = p1.x - p2.x,
    dy = p1.y - p2.y;

  return dx * dx + dy * dy;
}

// square distance from a point to a segment
function getSqSegDist(p, p1, p2) {
  var x = p1.x,
    y = p1.y,
    dx = p2.x - x,
    dy = p2.y - y;

  if (dx !== 0 || dy !== 0) {
    var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);

    if (t > 1) {
      x = p2.x;
      y = p2.y;
    } else if (t > 0) {
      x += dx * t;
      y += dy * t;
    }
  }

  dx = p.x - x;
  dy = p.y - y;

  return dx * dx + dy * dy;
}

// rest of the code doesn't care about point format

// basic distance-based simplification
function simplifyRadialDist(points, sqTolerance) {
  var prevPoint = points[0],
    newPoints = [prevPoint],
    point;

  for (var i = 1, len = points.length; i < len; i++) {
    point = points[i];

    if (getSqDist(point, prevPoint) > sqTolerance) {
      newPoints.push(point);
      prevPoint = point;
    }
  }

  if (prevPoint !== point) newPoints.push(point);

  return newPoints;
}

// simplification using optimized Douglas-Peucker algorithm with recursion elimination
function simplifyDouglasPeucker(points, sqTolerance) {
  var len = points.length,
    MarkerArray = typeof Uint8Array !== "undefined" ? Uint8Array : Array,
    markers = new MarkerArray(len),
    first = 0,
    last = len - 1,
    stack = [],
    newPoints = [],
    i,
    maxSqDist,
    sqDist,
    index;

  markers[first] = markers[last] = 1;

  while (last) {
    maxSqDist = 0;

    for (i = first + 1; i < last; i++) {
      sqDist = getSqSegDist(points[i], points[first], points[last]);

      if (sqDist > maxSqDist) {
        index = i;
        maxSqDist = sqDist;
      }
    }

    if (maxSqDist > sqTolerance) {
      markers[index] = 1;
      stack.push(first, index, index, last);
    }

    last = stack.pop();
    first = stack.pop();
  }

  for (i = 0; i < len; i++) {
    if (markers[i]) newPoints.push(points[i]);
  }

  return newPoints;
}

// both algorithms combined for awesome performance
export function simplify(points, tolerance, highestQuality) {
  if (points.length <= 1) return points;

  var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;

  points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
  points = simplifyDouglasPeucker(points, sqTolerance);

  return points;
}

usage:

const points = [{x:10, y:20}, {x:15, y:23}, ...];
const simplifiedPoints = simplify(points)
like image 40
curious developer Avatar answered May 27 '26 20:05

curious developer