How to find a new path (shape) from intersection of SVG paths?

限于喜欢 提交于 2019-12-06 04:56:46

问题


I need to intersect 2 SVG paths and get path with thier intersection.

It should work in browser or in Node.js (any of them, not both).
I need intersection, usage of clip-path isn't ok.
If by some reason intersection will use transform it's ok (I'll remove it myself).

I think there is a library for that, but I found only

  • svg-intersections - returns array of points, but I need path
  • path-intersection - can't make it working - by some reason it always returns an empty array
  • snap.svg - seems to provide something usefull, but I don't understand how to use that
    like in Difference and Intersection of paths in SVG using Snap.path.intersection

For example intersecting following paths:

M 24.379464,51.504463 23.434524,23.156249 38.742559,12.572916 c 0,0 29.860118,-9.0714281 17.00893,0.755953 -12.851191,9.82738 13.229166,19.465774 13.229166,19.465774 z
m 32.883928,0.28869028 c 0,0 -15.686011,1.51190452 -8.504463,7.18154712 7.181546,5.6696426 50.270836,30.0491076 26.458332,42.3333336 -23.8125,12.284226 47.058036,14.174107 47.058036,14.174107 z

I want to get something like:

M 43.943359 11.123047 C 40.995759 11.900151 38.742188 12.572266 38.742188 12.572266 L 35.236328 14.996094 C 44.091432999999995 21.21816 55.052161 29.822765 57.455078 37.628906 L 66.939453 33.650391 63.632812 30.410156 C 58.77426 27.95814 52.364322 23.85552 52.214844 19.224609 L 43.943359 11.123047 z

Here is interactive snippet with paths from the example (ignore colors - they are for clarity) - need to get Intersection from #path1 and #path2:

svg { width: 10em; width: 100vmin; outline: 1px dotted blue; display: none; }
input { display: none; }
label { width: 10em; float: left; clear: left; cursor: pointer; line-height: 2em; margin: 0 .5em .25em 0; padding: 0 .25em; border: 1px solid; }
:checked + * + * + label { background: antiquewhite; color: blue; }
:checked + * + * + * + * + * + svg { display: inline-block; }
<input type=radio name=svg id=in checked>
<input type=radio name=svg id=out>
<input type=radio name=svg id=cp>

<label for=in>Input</label>
<label for=out>Intersection</label>
<label for=cp>Clip</label>

<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="22 0 76 64">
  <path id="path1"
    style="fill:rgba(255,0,0,.5); stroke:red;stroke-width:0.26458332px;"
    d="M 24.379464,51.504463 23.434524,23.156249 38.742559,12.572916 c 0,0 29.860118,-9.0714281 17.00893,0.755953 -12.851191,9.82738 13.229166,19.465774 13.229166,19.465774 z"
  />
  <path id="path2"
    style="fill:rgba(0,255,0,.5);stroke:green;stroke-width:0.26458332px;"
    d="m 32.883928,0.28869028 c 0,0 -15.686011,1.51190452 -8.504463,7.18154712 7.181546,5.6696426 50.270836,30.0491076 26.458332,42.3333336 -23.8125,12.284226 47.058036,14.174107 47.058036,14.174107 z"
  />
</svg>

<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="22 0 76 64">
  <path
    style="fill:rgba(0,0,255,.5);stroke:blue;stroke-width:0.26458332px;"
    d="M 43.943359 11.123047 C 40.995759 11.900151 38.742188 12.572266 38.742188 12.572266 L 35.236328 14.996094 C 44.091432999999995 21.21816 55.052161 29.822765 57.455078 37.628906 L 66.939453 33.650391 63.632812 30.410156 C 58.77426 27.95814 52.364322 23.85552 52.214844 19.224609 L 43.943359 11.123047 z"
  />
</svg>

<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="22 0 76 64">
  <clipPath id="clip2">
    <use xlink:href="#path2" />
  </clipPath>
  <use xlink:href="#path1" clip-path="url(#clip2)" />
</svg>

Example with Snap.svg:

var p1 = "M 24.379464,51.504463 23.434524,23.156249 38.742559,12.572916 c 0,0 29.860118,-9.0714281 17.00893,0.755953 -12.851191,9.82738 13.229166,19.465774 13.229166,19.465774 z"
var p2 = "m 32.883928,0.28869028 c 0,0 -15.686011,1.51190452 -8.504463,7.18154712 7.181546,5.6696426 50.270836,30.0491076 26.458332,42.3333336 -23.8125,12.284226 47.058036,14.174107 47.058036,14.174107 z"

var intersection = Snap.path.intersection(p1, p2)

console.log(intersection)
.as-console-wrapper.as-console-wrapper { max-height: 100vh }
<script src=//cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js></script>

PS: Same question in Russian.


回答1:


We can do it with PaperJS boolean operations, which can operate with SVG paths.

PaperJS has 5 different boolean operations: exclude, subtract, unite, intersect, divide and we will use one from them with the name intersect. This operations are also functions with the same name and they return item object, which has the function exportSVG(). It returns true SVG Path element, which has a new shape of both paths intersection.

Example of correct solution

paper.install(window);
window.onload = function()
{
    paper.setup('canvas');

    var p1 = 'M 24.379464,51.504463 23.434524,23.156249 38.742559,12.572916 c 0,0 29.860118,-9.0714281 17.00893,0.755953 -12.851191,9.82738 13.229166,19.465774 13.229166,19.465774 z',
        p2 = 'm 32.883928,0.28869028 c 0,0 -15.686011,1.51190452 -8.504463,7.18154712 7.181546,5.6696426 50.270836,30.0491076 26.458332,42.3333336 -23.8125,12.284226 47.058036,14.174107 47.058036,14.174107 z',
        path1 = new Path(p1),
        path2 = new Path(p2);

    path1.fillColor = 'rgba(255,0,0,.5)';
    path1.position = new Point(25, 25);

    path2.fillColor = 'rgba(0,255,0,.5)';
    path2.position = new Point(40, 25);
    
    var result = path2.intersect(path1);
    result.selected = true;
    result.fillColor = '#77f';

    //exportSVG() docu: http://paperjs.org/reference/item/#exportsvg
    var svgPathElement = result.exportSVG(),
        dPath = svgPathElement.getAttribute('d');
    
    document.querySelector('path').setAttribute('d', dPath);

    var output = document.querySelector('#output');
    output.innerHTML = '<pre>' + dPath + '</pre>';
    output.innerHTML += '<xmp>' + svgPathElement.outerHTML + '</xmp>';
};
table
{
    margin-left:14px;
    padding-left:14px;
    border-left:1px solid gray;
    display:inline-block
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.0/paper-full.min.js"></script>

<canvas id="canvas" width="75" height="75" resize></canvas>
<table><tr><td><b>Our new shape of both paths intersection in separate SVG:</b></td></tr>
<tr><td>
    <svg width="75" height="75" viewBox="0 0 75 75">
    <path fill="rgba(0,0,255,.5)" d=""/>
    </svg>
</td></tr></table>

<div id="output"></div>

Useful links:

  • Example PaperJS Boolean Operations: exclude, subtract, unite, intersect, divide
  • PaperJS examples
  • PaperJS Reference (documentation)



回答2:


Snap.svg seems to have the basic functions needed to do this. Some of them are be a bit akward, but this might be the direction to go. Please take this as pseudocode and not as working as-is:

// returns something like [ [ "M", 1, 2 ], ["C", 3, 4, 5, 6, 7, 8 ], ... ]
// and has a custom .toString() method
segList1 = Snap.path.toCubic(p1)
segList2 = Snap.path.toCubic(p2)

intersections = Snap.path.intersection(p1, p2)

// handle the subpaths between neighbouring intersections
intersectionPaths = intersections.map((point, id) => {
    from = point
    to = intersections[id + 1 < intersections.length ? id + 1 : 0]
    return pathBetweenIntersections(from, to)
})

// return the two paths between two neighbouring intersection points
function pathBetweenIntersections (from, to) {
    // list the segments between the intersection points on first path
    // TODO: handle cases when from.segment1 >= to.segment1
    subSegList1 = segList1.slice(from.segment1 + 1, to.segment1)

    // first segment has the intersection point
    startSegments = [ from.bez1.slice(0, 2).unshift('M'), from.bez1.slice(2, 8).unshift('C') ]
    startString1 = Snap.path.toCubic(startSegments).toString()
    // construct a path element from the segment
    startPath1 = Paper.path(startString1)
    startPathLength1 = startPath1.getTotalLength()
    // get the relevant subpath from intersection point to end
    startPathString1 = startPath1.getSubpath(from.t1 * startPathLength1, startPathLength1)
    subSegList1 = Snap.path.toCubic(startPathString1).concat(subSegList1)

    // and the same for the last segment
    endSegments = [ to.bez1.slice(0, 2).unshift('M'), to.bez1.slice(2, 8).unshift('C') ]
    endString1 = Snap.path.toCubic(endSegments).toString()
    endPath1 = Paper.path(endString1)
    endPathLength1 = endPath1.getTotalLength()
    endPathString1 = endPath1.getSubpath(0, to.t1 * endPathString1)
    subSegList1.push(Snap.path.toCubic(endPathString1)[1])

    subSegList2 = // do the same for segList2

    return {
        p1: Snap.path.toCubic(subSegList1).toString(),
        p2: Snap.path.toCubic(subSegList2).toString()
    }
}


来源:https://stackoverflow.com/questions/50011373/how-to-find-a-new-path-shape-from-intersection-of-svg-paths

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!