JavaScript-Clipper.js

Clipper.js下载地址:https://sourceforge.net/projects/jsclipper/files/latest/download
使用Javascript Clipper库,您可以通过多种方式修改路径(多边形和折线)的几何形状。

特点:

  1. 主要布尔运算:和、相交、差和或。

  2. 用正数或负数抵消路径。

其他功能包括:

  3. 简化多边形,这意味着将自相交的多边形转换为简单的多边形。执行此操作后,将具有自相交部分的面分解为多个简单面。

  4. 计算多边形的面积。

  5. 清洁多边形。合并得太靠近顶点,这会在偏移时导致变形。

  6. 减轻多边形。通过删除不必要的顶点来减少顶点数量。

  7. 计算多边形的Minkowski和和差。

  8. 计算路径的周长。

翻译说明

  这些功能与原始Clipper库中的功能相同,但有以下区别:

    例如,某些PolyTree和PolyNode属性被实现为函数。PolyTree.Total是PolyTree.Total()。

    C#的Int128结构是使用Tom Wu的大整数库JSBN来实现的,这是Javascript中最快的可用大整数库。由于Javascript缺乏64位整数支持,因此与C#版本相比,坐标空间受到的限制更大。

//  原始 (ç#)版本 具有 支持 用于 坐标 空间:
 + - 4611686018427387903  ( SQRT (2 ^ 127  - 1 )/ 2//  而 使用Javascript  版本 具有 支持 用于 坐标 空间:
 + - 4503599627370495  ( SQRT (2 ^ 106  - 1 )/ 2 )

    另外,由于Javascript中没有整数类型,因此必须确保在调用ClipperLib.IntPoint()时,使用Math.round()将参数值舍入为整数。

    ClipperLib.JS对象提供用于计算面积,边界和周长,清理,克隆,缩小顶点(lighten)和exPolygons相关函数的函数。

 A. 路径的布尔运算

  首先,为忙碌的你们准备一个实例:

<!DOCTYPE html>
<html>
<head>
    <title>Starter Boolean</title>
    <script src="clipper.js"></script>
</head>
<body>
<div id="svgcontainer"></div>
<script>
    var subj_paths = [[{X:10,Y:10},{X:110,Y:10},{X:110,Y:110},{X:10,Y:110}],
        [{X:20,Y:20},{X:20,Y:100},{X:100,Y:100},{X:100,Y:20}]];
    var clip_paths = [[{X:50,Y:50},{X:150,Y:50},{X:150,Y:150},{X:50,Y:150}],
        [{X:60,Y:60},{X:60,Y:140},{X:140,Y:140},{X:140,Y:60}]];

    var cpr = new ClipperLib.Clipper();

    var scale = 100;
    ClipperLib.JS.ScaleUpPaths(subj_paths, scale);
    ClipperLib.JS.ScaleUpPaths(clip_paths, scale);

    cpr.AddPaths(subj_paths, ClipperLib.PolyType.ptSubject, true);  // true意味着闭合路径
    cpr.AddPaths(clip_paths, ClipperLib.PolyType.ptClip, true);

    var solution_paths = new ClipperLib.Paths();
    var succeeded = cpr.Execute(ClipperLib.ClipType.ctUnion, solution_paths, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNonZero);

    // 按比例缩小坐标并绘图
    var svg = '<svg style="background-color:#dddddd" width="160" height="160">';
    svg += '<path stroke="black" fill="yellow" stroke-width="2" d="' + paths2string(solution_paths, scale) + '"/>';
    svg += '</svg>';
    document.getElementById("svgcontainer").innerHTML = svg;

    // 将路径转换为SVG路径字符串,按比例缩小坐标
    function paths2string (paths, scale) {
        var svgpath = "", i, j;
        if (!scale) scale = 1;
        for(i = 0; i < paths.length; i++) {
            for(j = 0; j < paths[i].length; j++){
                if (!j) svgpath += "M";
                else svgpath += "L";
                svgpath += (paths[i][j].X / scale) + ", " + (paths[i][j].Y / scale);
            }
            svgpath += "Z";
        }
        if (svgpath=="") svgpath = "M0,0";
        return svgpath;
    }
    var paths = [[{"X":10,"Y":10},{"X":110,"Y":10},{"X":110,"Y":110},{"X":10,"Y":110}]];
    console.log(JSON.stringify(paths));
    ClipperLib.Clipper.ReversePaths(paths);
    console.log(JSON.stringify(paths));
</script>
</body>
</html>

在上图中,两个黄色框(外部和孔多边形)合并为一种形状(一个具有三个孔的外部多边形)。

然后为需要更多信息的任何人提供更详细的分步说明:

A1:引入clipper库

<script src="clipper.js"></script>

A2: 创建路径

  有两种类型的路径,Subject(主题)和 Clip(剪辑)。在Union(联合)Intersection(相交)中,不管哪个是subject哪个是clip,结果都是一样的。DifferenceXor中,结果不同。

  有两种(或更多)创建路径的方法,我先搞一个复杂的弄一下原理,然后显示一个简单的。

A2.1:创建 Subject 路径

  创建路径的主要方法是: ClipperLib.Path()ClipperLib.Paths()ClipperLib.IntPoint()

var subj_paths = new ClipperLib.Paths();
var subj_path = new ClipperLib.Path();
subj_path.push(
  new ClipperLib.IntPoint(0, 0),
  new ClipperLib.IntPoint(100, 0),
  new ClipperLib.IntPoint(100, 100),
  new ClipperLib.IntPoint(0, 100));
subj_paths.push(subj_path);

A2.2: 创建 clip 路径  

var clip_paths = new ClipperLib.Paths();
var clip_path = new ClipperLib.Path();
clip_path.push(
  new ClipperLib.IntPoint(0, 0),
  new ClipperLib.IntPoint(100, 0),
  new ClipperLib.IntPoint(100, 100),
  new ClipperLib.IntPoint(0, 100));
clip_paths.push(clip_path);

A2.3:多边形的孔

  如果你想添加一个洞,这是不可能只使用一个多边形。你必须创建至少两个子多边形,一个用于外层,一个用于空穴。外多边形的缠绕顺序。非空穴)必须与空穴多边形的缠绕顺序相反。您可以根据需要添加任意多的子多边形。

A2.4:太复杂了?

  或者,您可以创建更简单(和更快)的路径,因为ClipperLib.Path()和ClipperLib.Paths()是数组。

  下面的代码创建了两个Path,subj_paths和clip_paths,它们都由两个子路径组成。第二个是一个洞。

var subj_paths = [[{X:10,Y:10},{X:110,Y:10},{X:110,Y:110},{X:10,Y:110}],
                      [{X:20,Y:20},{X:20,Y:100},{X:100,Y:100},{X:100,Y:20}]]; 
var clip_paths = [[{X:50,Y:50},{X:150,Y:50},{X:150,Y:150},{X:50,Y:150}],
                      [{X:60,Y:60},{X:60,Y:140},{X:140,Y:140},{X:140,Y:60}]];

A3:创建 Clipper 对象的实例

var cpr = new ClipperLib.Clipper();

A4:缩放坐标(若有需要)

  因为Clipper库将坐标处理为整数,所以您必须在将它们添加到Clipper之前将它们按比例放大(在绘图之前将它们按比例缩小)。通过遍历路径数组很容易实现缩放。

var scale = 100;
ClipperLib.JS.ScaleUpPaths(subj_paths, scale);
ClipperLib.JS.ScaleUpPaths(clip_paths, scale);
// 使用 ClipperLib.js.ScaleUpPath (如果你有单个Path)

注意!将来的Clipper版本中可能会删除此缩放要求,并在内部进行处理。

A5:添加路径到Clipper

  使用 AddPaths() 方法向 Clipper (对象)添加 Subject 和 Clip 路径。

cpr.AddPaths(subj_paths, ClipperLib.PolyType.ptSubject, true);
cpr.AddPaths(clip_paths, ClipperLib.PolyType.ptClip, true);

最后一个参数true表示路径是闭合的(多边形);false表示路径是打开的(行)。Subject 可以打开或关闭或两者的混合,但 Clip 必须关闭

A6:创建一个空的解决方案路径

  我们需要一个持有者的布尔运算结果:一个空的路径。

var solution_paths = new ClipperLib.Paths();

  或者更简单:

var solution_paths = [];

A7:选择ClipType和PolyFillType并执行剪切操作

A7.1:选择ClipType

  选择ClipType,这是实际的裁剪操作:

var clipType = ClipperLib.ClipType.ctIntersection;
// or simpler
var clipType = 0;

  剪贴类型可以是下列其中之一(或各自的数字0、1、2、3):

ClipperLib.ClipType.ctIntersection
ClipperLib.ClipType.ctUnion
ClipperLib.ClipType.ctDifference
ClipperLib.ClipType.ctXor

A7.2:选择PolyFillType

  选择PolyFillType,它告诉如何填充多边形。PolyFillType在subject和clip多边形中不一定是相同的,但是下面我们对两者使用相同的。 

var subject_fillType = ClipperLib.PolyFillType.pftNonZero;
var clip_fillType = ClipperLib.PolyFillType.pftNonZero;

  或者更简单的:

var subject_fillType = 1;
var clip_fillType = 1;

  该PolyFillType可以是下列其中之一(或各自的数字0、1、2、3):

ClipperLib.PolyFillType.pftEvenOdd
ClipperLib.PolyFillType.pftNonZero
ClipperLib.PolyFillType.pftPositive
ClipperLib.PolyFillType.pftNegative

注意!尽管Javascript裁剪器库支持所有这些polyfilltype,但图形后端可能只支持其中有限的子集。SVG格式支持EvenOdd(偶数)和NonZero(非零)。Html5 canvas目前似乎只有NonZero规则。

A7.3 执行布尔运算

var succeeded = cpr.Execute(clipType, solution_paths, subject_fillType, clip_fillType);

现在solution_paths由上述布尔运算的结果填充(在本例中是Intersection),可以在某处绘制(或修改或序列化以发送

 A8:绘制布尔运算的多边形

  在网络浏览器中,可以使用Javascript以多种方式绘制多边形。我们在这里介绍了现代浏览器中使用的两种流行方式:内联SVG和canvas。其他可能性是使用Microsoft的VML或Walter Zorn的矢量图形库(尽管我不确定是否可以绘制带有孔的多边形)

  例如,有许多库使用SVG,VML或canvas作为图形后端。Raphael JS

A8.1 完整的代码示例:使用SVG绘制布尔操作的多边形 

  我们可以在SVG中以各种方式绘制多边形。有一个本机<polygon>元素,但是缺点是它不支持孔和子多边形。因此,更好的方法是使用<path>element。每个子多边形被绘制为一个子路径。每个子路径均以M(moveTo)开头,以Z(closePath)结尾。

  以下是对多边形进行布尔运算并将其附加到SVG的完整示例。在这种情况下,我们将创建四个内联SVG文档,并在每个文档上绘制一个布尔结果。

<html>
<head>
    <title>Javascript Clipper Library / Boolean operations / SVG example</title>
    <script src="clipper.js"></script>
    <style>
        h3{ margin-bottom:2px}
        body,th,td,input,legend,fieldset,p,b,button,select,textarea {
            font-size: 14px;
            font-family: Arial, Helvetica, sans-serif;
        }
    </style>
    <script>
        function draw() {
            var subj_paths = [[{X:10,Y:10},{X:110,Y:10},{X:110,Y:110},{X:10,Y:110}],
                [{X:20,Y:20},{X:20,Y:100},{X:100,Y:100},{X:100,Y:20}]];
            var clip_paths = [[{X:50,Y:50},{X:150,Y:50},{X:150,Y:150},{X:50,Y:150}],
                [{X:60,Y:60},{X:60,Y:140},{X:140,Y:140},{X:140,Y:60}]];
            var scale = 100;
            ClipperLib.JS.ScaleUpPaths(subj_paths, scale);
            ClipperLib.JS.ScaleUpPaths(clip_paths, scale);
            var cpr = new ClipperLib.Clipper();
            cpr.AddPaths(subj_paths, ClipperLib.PolyType.ptSubject, true);
            cpr.AddPaths(clip_paths, ClipperLib.PolyType.ptClip, true);
            var subject_fillType = ClipperLib.PolyFillType.pftNonZero;
            var clip_fillType = ClipperLib.PolyFillType.pftNonZero;
            var clipTypes = [ClipperLib.ClipType.ctUnion, ClipperLib.ClipType.ctDifference, ClipperLib.ClipType.ctXor, ClipperLib.ClipType.ctIntersection];
            var clipTypesTexts = "Union, Difference, Xor, Intersection";
            var solution_paths, svg, cont = document.getElementById('svgcontainer');
            var i;
            for(i = 0; i < clipTypes.length; i++) {
                solution_paths = new ClipperLib.Paths();
                cpr.Execute(clipTypes[i], solution_paths, subject_fillType, clip_fillType);
                console.log(JSON.stringify(solution_paths));
                svg = '<svg style="margin-top:10px; margin-right:10px;margin-bottom:10px;background-color:#dddddd" width="160" height="160">';
                svg += '<path stroke="black" fill="yellow" stroke-width="2" d="' + paths2string(solution_paths, scale) + '"/>';
                svg += '</svg>';
                cont.innerHTML += svg;
            }
            cont.innerHTML += "<br>" + clipTypesTexts;
        }

        // Converts Paths to SVG path string
        // and scales down the coordinates
        function paths2string (paths, scale) {
            var svgpath = "", i, j;
            if (!scale) scale = 1;
            for(i = 0; i < paths.length; i++) {
                for(j = 0; j < paths[i].length; j++){
                    if (!j) svgpath += "M";
                    else svgpath += "L";
                    svgpath += (paths[i][j].X / scale) + ", " + (paths[i][j].Y / scale);
                }
                svgpath += "Z";
            }
            if (svgpath=="") svgpath = "M0,0";
            return svgpath;
        }
    </script>
</head>
<body onload="draw()">
<h2>Javascript Clipper Library / Boolean operations / SVG example</h2>
This page shows an example of boolean operations on polygons and drawing them using SVG.
<div id="svgcontainer"></div>
</body>
</html>

  执行完上面的代码后,您应该在svgcontainer元素中具有四个剪切的多边形作为SVG路径,如下图所示。

 

 A8.2 完整的代码示例:使用<canvas>绘制布尔操作的多边形

  画布不同于SVG。我们不能像在SVG中那样使用文本字符串。路径被绘制为命令moveTo()lineTo()。注意!绘制后,无法像在SVG中一样对其进行修改。如果要更新某些内容,则需要重新绘制整个画布。

<html>
<head>
    <title>Javascript Clipper Library / Boolean operations / Canvas example</title>
    <script src="clipper.js"></script>
    <style>
        h3{ margin-bottom:2px}
        body,th,td,input,legend,fieldset,p,b,button,select,textarea {
            font-size: 14px;
            font-family: Arial, Helvetica, sans-serif;
        }
    </style>
    <script>
        function draw() {
            var subj_paths = [[{X:10,Y:10},{X:110,Y:10},{X:110,Y:110},{X:10,Y:110}],
                [{X:20,Y:20},{X:20,Y:100},{X:100,Y:100},{X:100,Y:20}]];
            var clip_paths = [[{X:50,Y:50},{X:150,Y:50},{X:150,Y:150},{X:50,Y:150}],
                [{X:60,Y:60},{X:60,Y:140},{X:140,Y:140},{X:140,Y:60}]];
            var scale = 100;
            ClipperLib.JS.ScaleUpPaths(subj_paths, scale);
            ClipperLib.JS.ScaleUpPaths(clip_paths, scale);
            var cpr = new ClipperLib.Clipper();
            cpr.AddPaths(subj_paths, ClipperLib.PolyType.ptSubject, true);
            cpr.AddPaths(clip_paths, ClipperLib.PolyType.ptClip, true);
            var subject_fillType = ClipperLib.PolyFillType.pftNonZero;
            var clip_fillType = ClipperLib.PolyFillType.pftNonZero;
            var clipTypes = [ClipperLib.ClipType.ctUnion, ClipperLib.ClipType.ctDifference, ClipperLib.ClipType.ctXor, ClipperLib.ClipType.ctIntersection];
            var clipTypesTexts = "Union, Difference, Xor, Intersection";
            var solution_paths, canvas_str, canvas, ctx, desc = document.getElementById('desc'), i, i2, j, x, y;
            for(i = 0; i < clipTypes.length; i++) {
                solution_paths = new ClipperLib.Paths();
                cpr.Execute(clipTypes[i], solution_paths, subject_fillType, clip_fillType);
                //console.log(JSON.stringify(solution_paths));
                canvas = document.getElementById("canvas"+i);
                canvas.width = canvas.height = 160;
                ctx = canvas.getContext("2d");
                ctx.fillStyle = "red";
                ctx.strokeStyle = "black";
                ctx.lineWidth = 2;
                ctx.beginPath();
                for(i2 = 0; i2 < solution_paths.length; i2++) {
                    for(j = 0; j < solution_paths[i2].length; j++) {
                        x = solution_paths[i2][j].X / scale;
                        y = solution_paths[i2][j].Y / scale;
                        if (!j) ctx.moveTo(x, y);
                        else ctx.lineTo(x, y);
                    }
                    ctx.closePath();
                }
                ctx.fill();
                ctx.stroke();
            }
            desc.innerHTML = clipTypesTexts;
        }
    </script>
</head>
<style>
    canvas { background-color:#dddddd; margin:0px; margin-right:10px; margin-bottom:10px; }
</style>
<body onload="draw()">
<h2>Javascript Clipper Library / Boolean operations / Canvas example</h2>
This page shows an example of boolean operations on polygons and drawing them on html5 canvas as bitmap images.
<br><br>
<div id="canvascontainer">
    <canvas id="canvas0"></canvas>
    <canvas id="canvas1"></canvas>
    <canvas id="canvas2"></canvas>
    <canvas id="canvas3"></canvas>
</div>
<div id="desc"></div>
</body>
</html>

当您执行的代码,你会看到画布结果与SVG很相似:


 B:抵消路径

   Clipper库的另一个主要功能是多边形和折线偏移。其目的是通过一定量来增添(粗化,膨胀,扩张,正偏移)或细化(缩小,削弱,负偏移)多边形或使折线变宽。

  在B部分中,我们首先提供用于补偿的快速入门代码,然后提供详细的逐步说明,最后提供更复杂的完整代码示例。

  首先,为忙碌的你们提供一个简单的快速入门实例:

<html>
<head>
    <title>Starter Offset</title>
    <script src="clipper.js"></script>
</head>
<body>
<div id="svgcontainer"></div>
<script>
    var paths = [[{X:30,Y:30},{X:130,Y:30},{X:130,Y:130},{X:30,Y:130}],
        [{X:60,Y:60},{X:60,Y:100},{X:100,Y:100},{X:100,Y:60}]];
    var scale = 100;
    ClipperLib.JS.ScaleUpPaths(paths, scale);
    // Possibly ClipperLib.Clipper.SimplifyPolygons() here
    // Possibly ClipperLib.Clipper.CleanPolygons() here
    var co = new ClipperLib.ClipperOffset(2, 0.25);

    // ClipperLib.EndType = {etOpenSquare: 0, etOpenRound: 1, etOpenButt: 2, etClosedPolygon: 3, etClosedLine : 4 };
    co.AddPaths(paths, ClipperLib.JoinType.jtRound, ClipperLib.EndType.etClosedPolygon);
    var offsetted_paths = new ClipperLib.Paths();
    co.Execute(offsetted_paths, -10 * scale);
    //console.log(JSON.stringify(offsetted_paths));

    // Scale down coordinates and draw ...
    var svg = '<svg style="background-color:#dddddd" width="160" height="160">';
    svg += '<path stroke="black" fill="yellow" stroke-width="2" d="' + paths2string(offsetted_paths, scale) + '"/>';
    svg += '</svg>';
    document.getElementById("svgcontainer").innerHTML = svg;

    // Converts Paths to SVG path string
    // and scales down the coordinates
    function paths2string (paths, scale) {
        var svgpath = "", i, j;
        if (!scale) scale = 1;
        for(i = 0; i < paths.length; i++) {
            for(j = 0; j < paths[i].length; j++){
                if (!j) svgpath += "M";
                else svgpath += "L";
                svgpath += (paths[i][j].X / scale) + ", " + (paths[i][j].Y / scale);
            }
            svgpath += "Z";
        }
        if (svgpath=="") svgpath = "M0,0";
        return svgpath;
    }
</script>

</body>
</html>

  在上图中,将帧(带孔的外部多边形)细化了10个单位。

B1:引入 Clipper库

<script src="clipper.js"></script>

B2:创建路径

首先,我们创建一个将被偏移的路径。 

var paths = [[{X:30,Y:30},{X:130,Y:30},{X:130,Y:130},{X:30,Y:130}],
                 [{X:60,Y:60},{X:60,Y:100},{X:100,Y:100},{X:100,Y:60}]];

上面的路径是多路径的。由两个子路径组成,外层路径和一个孔。

B3:放大多边形坐标

因为Clipper库使用“整数”坐标,所以我们必须按比例放大坐标以保持精度。标度值应足够大,但出于性能方面的考虑,不要过高。如果坐标的精度为2位小数,则足够的比例系数为100

注意!尽管以上是主要原理,但在确定合适的比例因子时请小心。如果坐标原本是整数,则用系数1缩放可能并不总是足够的(即根本没有放大)。如果joinTypejtRound并且未完成放大,则整数不能表示结果的圆角并不总是正确的。最安全的方法是始终至少放大10或100。

var  scale  =  100 ; 
ClipperLib.JS.ScaleUpPaths(paths, scale); 
// 使用 ClipperLib.JS.ScaleUpPath()( 如果具有单个Path)

B4:简化和清洁

B4.1 简化

  偏移的要求是多边形没有自相交,尽管它们在所有情况下都不会引起问题。

   为了将复杂的多边形转换为简单的多边形,Clipper库提供了SimplifyPolygon()SimplifyPolygons()方法。复杂多边形表示具有自相交的多边形,简单多边形表示相反。

  简化是可选的。如果多边形没有自相交,则不需要此步骤。如果可能,则应在偏移之前简化多边形。简化确定性并不是一个坏错误。

  如果路径是开放的(折线),则无需简化,因为如果折线自身交叉,则没有危害。

要删除自相交,请使用:

paths = ClipperLib.Clipper.SimplifyPolygons(paths, ClipperLib.PolyFillType.pftNonZero);
                                             // or ClipperLib.PolyFillType.pftEvenOdd

B4.2 清洁

Clipper简化功能(或更具体地讲是内部Union操作)在较小的自相交方面具有局限性。它无法删除所有这些文件。它本来应该,但是目前还不能。原始库的创建者已经意识到了这个问题,但是到目前为止还没有提供解决方案,而是提供了一个CleanPolygon()合并了太近点的函数,似乎可以解决该问题。

下图显示了该问题的示例。左侧和右侧都有使用joinType jtMiter和偏移的多边形MiterLimit。右侧已使用Clean()函数进行了清理,并且失真消失了。当失真是坏的joinTypejtMiterMiterLimit大,但显然也明显与其他joinTypes。在某些情况下,增加缩放比例可能会有所帮助,但不是解决方案,因为它还会增加处理时间,因此不是一般解决方案。

 要删除较小的自相交,请使用:

var cleandelta = 0.1; // 在不同的情况下,0.1应该是合适的增量
paths = ClipperLib.JS.Clean(paths, cleandelta * scale);

内部Clean()使用“ 径向归约”算法,该算法可以合并顺序的顶点(如果它们彼此之间处于一定距离或相距一定距离)

注意!Clipper中还有清洁功能:CleanPolygonCleanPolygons。它们与Clean()不同,因为它们还合并了共线的顶点:

var cleandelta = 0.1; // 在不同的情况下,0.1应该是合适的增量
paths = ClipperLib.Clipper.CleanPolygons(paths, cleandelta * scale);

B5:创建ClipperOffset对象的实例

  ClipperOffset对象封装了打开和关闭路径的偏移(膨胀/放气)过程。

  可以使用不同的偏移量(Delta)多次调用Execute方法,而不必重新分配路径。可以在单个操作中混合使用开放路径和封闭路径来执行偏移 

  如果尚未创建,请使用以下方法创建ClipperOffset对象的实例:

var miterLimit = 2;
var arcTolerance = 0.25;
var co = new ClipperLib.ClipperOffset(miterLimit, arcTolerance);

另请参见文档:ClipperOffsetArcToleranceMiterLimit

B6:添加路径

添加ClipperOffset使用AddPathAddPaths方法的路径:

co.AddPaths(paths, ClipperLib.JoinType.jtRound, ClipperLib.EndType.etClosedPolygon); 

B7:创建一个空解决方案并执行偏移操作

现在可以使用delta值执行偏移操作,可以是负数,也可以是正数:

var delta = -10;
var offsetted_paths = new ClipperLib.Paths();
co.Execute(offsetted_paths, delta*scale);

 或者如果你想要PolyTree:

var delta = -10;
var offsetted_polytree = new ClipperLib.PolyTree();
co.Execute(offsetted_polytree, delta*scale);

就是这样。现在,offsetted_paths或offsetted_polytree由补偿操作的结果填充,在本例中为-10 * scale。请记住,在绘制这个之前,坐标必须按比例因子缩小,因为结果集中的坐标总是“整数”。

注意!结果可能是空的,或者有比原来更多或更少的子路径。

注意!如果joinType是jtRound,则结果中的圆角表示为直线。

B8:绘制偏移的多边形

正如我们在布尔运算多边形的示例中所做的那样,我们将提供偏移多边形的完整功能示例,并将其绘制在现代浏览器中的两个流行图形后端上:内联SVG和canvas。

B8.1 完整代码示例:使用svg绘制偏移的多边形

<html>
<head>
    <title>Javascript Clipper Library / Offsetting polygons / SVG example</title>
    <script src="clipper.js"></script>
    <style>
        h3{ margin-bottom:2px}
        body,th,td,input,legend,fieldset,p,b,button,select,textarea {
            font-size: 14px;
            font-family: Arial, Helvetica, sans-serif;
        }
    </style>
    <script>
        function draw() {
            var svg, cont = document.getElementById('svgcontainer'), i, ii, j;
            var paths = [[{X:30,Y:30},{X:130,Y:30},{X:130,Y:130},{X:30,Y:130}],
                [{X:60,Y:60},{X:60,Y:100},{X:100,Y:100},{X:100,Y:60}]];
            var scale = 100;
            ClipperLib.JS.ScaleUpPaths(paths, scale);
            var joinTypes = [ClipperLib.JoinType.jtSquare, ClipperLib.JoinType.jtRound, ClipperLib.JoinType.jtMiter];
            var endType = ClipperLib.EndType.etClosedPolygon;

            var joinTypesTexts = ["Square", "Round", "Miter"];
            var deltas = [-10, 0, 10];

            var co = new ClipperLib.ClipperOffset(); // constructor
            var offsetted_paths = new ClipperLib.Paths(); // empty solution

            for(i = 0; i < joinTypes.length; i++) {
                for(ii = 0; ii < deltas.length; ii++) {
                    co.Clear();
                    co.AddPaths(paths, joinTypes[i], endType);
                    co.MiterLimit = 2;
                    co.ArcTolerance = 0.25;

                    co.Execute(offsetted_paths, deltas[ii] * scale);
                    //console.log(JSON.stringify(offsetted_paths));

                    svg = '<svg style="margin-top:10px;margin-right:10px;margin-bottom:10px;background-color:#dddddd" width="160" height="160">';
                    svg += '<path stroke="black" fill="yellow" stroke-width="2" d="' + paths2string(offsetted_paths, scale) + '"/>';
                    svg += '</svg>';
                    cont.innerHTML += svg;
                }
                cont.innerHTML += "<br>" + joinTypesTexts[i] + " " + deltas[0] + ", ";
                cont.innerHTML += joinTypesTexts[i] + " " + deltas[1] + ", ";
                cont.innerHTML += joinTypesTexts[i] + " " + deltas[2] + "<hr>";
            }
        }

        // Converts Paths to SVG path string
        // and scales down the coordinates
        function paths2string (paths, scale) {
            var svgpath = "", i, j;
            if (!scale) scale = 1;
            for(i = 0; i < paths.length; i++) {
                for(j = 0; j < paths[i].length; j++){
                    if (!j) svgpath += "M";
                    else svgpath += "L";
                    svgpath += (paths[i][j].X / scale) + ", " + (paths[i][j].Y / scale);
                }
                svgpath += "Z";
            }
            if (svgpath=="") svgpath = "M0,0";
            return svgpath;
        }
    </script>
</head>
<body onload="draw()">
<h2>Javascript Clipper Library / Offsetting polygons / SVG example</h2>
This page shows an example of offsetting polygons and drawing them using SVG.
<div id="svgcontainer"></div>
</body>
</html>

当“和”是在SVG上绘制后的结果时,偏移的多边形如下所示:

 B8.2 完整的代码示例:使用画布绘制偏移的多边形

<html>
<head>
    <title>Javascript Clipper Library / Offsetting polygons / Canvas example</title>
    <script src="clipper.js"></script>
    <style>
        h3{ margin-bottom:2px}
        body,th,td,input,legend,fieldset,p,b,button,select,textarea {
            font-size: 14px;
            font-family: Arial, Helvetica, sans-serif;
        }
    </style>
    <script>
        function draw() {
            var canvas, ctx, i2, ii, i, j, x, y;
            var paths = [[{X:30,Y:30},{X:130,Y:30},{X:130,Y:130},{X:30,Y:130}],
                [{X:60,Y:60},{X:60,Y:100},{X:100,Y:100},{X:100,Y:60}]];
            var scale = 100;
            ClipperLib.JS.ScaleUpPaths(paths, scale);
            var joinTypes = [ClipperLib.JoinType.jtSquare, ClipperLib.JoinType.jtRound, ClipperLib.JoinType.jtMiter];
            var endType = ClipperLib.EndType.etClosedPolygon;
            var joinTypesTexts = ["Square", "Round", "Miter"];
            var deltas = [-10, 0, 10];

            var co = new ClipperLib.ClipperOffset(); // constructor
            co.AddPaths(paths, true); // we add paths only once
                                      // true means closed paths (polygons)
            var offsetted_paths = new ClipperLib.Paths(); // empty solution

            for(i = 0; i < joinTypes.length; i++) {
                for(ii = 0; ii < deltas.length; ii++) {

                    co.Clear();
                    co.AddPaths(paths, joinTypes[i], endType);
                    co.MiterLimit = 2;
                    co.ArcTolerance = 0.25;

                    co.Execute(offsetted_paths, deltas[ii] * scale);
                    //console.log(JSON.stringify(offsetted_paths));

                    canvas = document.getElementById("canvas" + i + "." + ii);
                    canvas.width = canvas.height = 160;
                    ctx = canvas.getContext("2d");
                    ctx.fillStyle = "red";
                    ctx.strokeStyle = "black";
                    ctx.lineWidth = 2;
                    ctx.beginPath();
                    for(i2 = 0; i2 < offsetted_paths.length; i2++) {
                        for(j = 0; j < offsetted_paths[i2].length; j++) {
                            x = offsetted_paths[i2][j].X / scale;
                            y = offsetted_paths[i2][j].Y / scale;
                            if (!j) ctx.moveTo(x, y);
                            else ctx.lineTo(x, y);
                        }
                        ctx.closePath();
                    }
                    ctx.fill();
                    ctx.stroke();
                }
                document.getElementById('desc' + i).innerHTML = joinTypesTexts[i] + " " + deltas[0] + ", " + joinTypesTexts[i] + " " + deltas[1] + ", " + joinTypesTexts[i] + " " + deltas[2];
            }
        }
    </script>
    <style>
        canvas { background-color:#dddddd; margin:0px; margin-right:10px; margin-bottom:10px; }
    </style>
</head>
<body onload="draw()">
<h2>Javascript Clipper Library / Offsetting polygons / Canvas example</h2>
This page shows an example of offsetting polygons and drawing them on html5 canvas as bitmap images.
<br><br>
<canvas id="canvas0.0"></canvas>
<canvas id="canvas0.1"></canvas>
<canvas id="canvas0.2"></canvas>
<div id="desc0"></div>
<hr>
<canvas id="canvas1.0"></canvas>
<canvas id="canvas1.1"></canvas>
<canvas id="canvas1.2"></canvas>
<div id="desc1"></div>
<hr>
<canvas id="canvas2.0"></canvas>
<canvas id="canvas2.1"></canvas>
<canvas id="canvas2.2"></canvas>
<div id="desc2"></div>
<hr>
</body>
</html>


 C:对ExPolygons和PolyTree进行布尔运算

有时您需要知道哪个孔属于哪个轮廓。例如,就是这种情况。在用于3D程序的三角剖分中。使用Polygons结构是不可能的。答案是ExPolygons或PolyTree结构。

ExPolygon是一种“扩展”多边形结构,它将外部多边形轮廓与所有包含的多边形孔封装在一起。请阅读有关ExPolygons和PolyTree的布尔运算的更多信息。


 D:将Web Worker与Clipper一起使用

Clipper可以处理非常复杂的路径,并且页面更新可能需要几秒钟。在这些情况下,使用Web Worker避免浏览器挂起可能是明智的。请阅读有关将Web Workers与Clipper一起使用的更多信息。


 Lightening Paths

在许多情况下,多边形和折线的顶点过多,这会导致工程图过载。这是例如。当位图追溯到矢量图形,制图时以及使用Clipper通过偏移多边形时,为true joinType jtRound

为了删除不必要的顶点,我们提供了一种方法ClipperLib.Lighten()。它采用垂直归约算法;如果与当前三点序列的起点和终点之间的线的垂直距离等于或小于一定公差(距离),则遍历所有顶点并移除三个连续顶点的中间顶点。

要减少顶点,请使用:

var tolerance = 0.1;
polygons = ClipperLib.JS.Lighten(polygons, tolerance * scale);

tolerance上面是负的或正Number或零。如果为负,则返回原始路径的克隆。越高tolerance,将删除更多的顶点。

注意!约简算法不是最适合较大公差值的算法,因为它非常局部地(按三个顶点的顺序)确定边界线,并且不会在添加可以提高原始保真度的位置添加新顶点。顺便说一句,它很快。如果保真度tolerance较低,则保真度是理想的。0.1-0.2单位。


 计算多边形的面积

要获取多边形的面积,Javascript Clipper库提供AreaOfPolygon()并提供以下AreaOfPolygons()功能:

var area = ClipperLib.JS.AreaOfPolygon(polygon);

要获得多边形的面积,请使用以下命令:

var area = ClipperLib.JS.AreaOfPolygons(polygons);

 计算多边形的周长

为了获取多边形的周长,Javascript Clipper库提供PerimeterOfPath()并提供以下PerimeterOfPaths()功能:

var polygonal_perimeter = ClipperLib.JS.PerimeterOfPath(path, true, 1);

要获得多边形的周长,请使用以下命令:

var polygonal_perimeter = ClipperLib.JS.PerimeterOfPaths(paths, true, 1);

上面的两个示例计算了多边形的周长,这意味着该周长是从第一个点到第一个点进行测量的,而不管最后一个点是否与第一个点相同。如果要测量线的周长,请使用以下方法:

var line_perimeter = ClipperLib.JS.PerimeterOfPath(path, false, 1);

要获得多边形的周长,请使用以下命令:

var line_perimeter = ClipperLib.JS.PerimeterOfPaths(paths, false, 1);

 

posted @ 2019-11-25 16:13  .why  阅读(4912)  评论(0编辑  收藏  举报
Live2D