diff --git a/src/ImageSharp/Drawing/Paths/InternalPath.cs b/src/ImageSharp/Drawing/Paths/InternalPath.cs
index b347022534..b71ba62a88 100644
--- a/src/ImageSharp/Drawing/Paths/InternalPath.cs
+++ b/src/ImageSharp/Drawing/Paths/InternalPath.cs
@@ -60,10 +60,29 @@ namespace ImageSharp.Drawing.Paths
/// The segments.
/// if set to true [is closed path].
internal InternalPath(ILineSegment[] segments, bool isClosedPath)
+ : this(Simplify(segments), isClosedPath)
{
- Guard.NotNull(segments, nameof(segments));
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The segment.
+ /// if set to true [is closed path].
+ internal InternalPath(ILineSegment segment, bool isClosedPath)
+ :this(segment.AsSimpleLinearPath(), isClosedPath)
+ {
+ }
+
- this.points = this.Simplify(segments);
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The points.
+ /// if set to true [is closed path].
+ internal InternalPath(Vector2[] points, bool isClosedPath)
+ {
+ this.points = points;
this.closedPath = isClosedPath;
float minX = this.points.Min(x => x.X);
@@ -192,7 +211,7 @@ namespace ImageSharp.Drawing.Paths
///
/// The .
///
- private Vector2[] Simplify(ILineSegment[] segments)
+ private static Vector2[] Simplify(ILineSegment[] segments)
{
List simplified = new List();
foreach (ILineSegment seg in segments)
diff --git a/src/ImageSharp/Drawing/Shapes/ComplexPolygon.cs b/src/ImageSharp/Drawing/Shapes/ComplexPolygon.cs
index d0edb76aa5..7bd459f6aa 100644
--- a/src/ImageSharp/Drawing/Shapes/ComplexPolygon.cs
+++ b/src/ImageSharp/Drawing/Shapes/ComplexPolygon.cs
@@ -7,7 +7,6 @@ namespace ImageSharp.Drawing.Shapes
{
using System.Collections;
using System.Collections.Generic;
- using System.Linq;
using System.Numerics;
using Paths;
@@ -20,8 +19,8 @@ namespace ImageSharp.Drawing.Shapes
public sealed class ComplexPolygon : IShape
{
private const float ClipperScaleFactor = 100f;
- private IEnumerable holes;
- private IEnumerable outlines;
+ private IShape[] holes;
+ private IShape[] outlines;
private IEnumerable paths;
///
@@ -39,7 +38,7 @@ namespace ImageSharp.Drawing.Shapes
///
/// The outlines.
/// The holes.
- public ComplexPolygon(IEnumerable outlines, IEnumerable holes)
+ public ComplexPolygon(IShape[] outlines, IShape[] holes)
{
Guard.NotNull(outlines, nameof(outlines));
Guard.MustBeGreaterThanOrEqualTo(outlines.Count(), 1, nameof(outlines));
@@ -76,7 +75,7 @@ namespace ImageSharp.Drawing.Shapes
// othersie we will start returning the distanct to the nearest shape
var dist = this.outlines.Select(o => o.Distance(point)).OrderBy(p => p).First();
- if (dist <= 0)
+ if (dist <= 0 && this.holes != null)
{
// inside poly
foreach (var hole in this.holes)
@@ -138,20 +137,32 @@ namespace ImageSharp.Drawing.Shapes
}
}
- private void AddPoints(ClipperLib.Clipper clipper, IEnumerable shapes, ClipperLib.PolyType polyType)
+ private void AddPoints(ClipperLib.Clipper clipper, IShape[] shapes, bool[] shouldInclude, ClipperLib.PolyType polyType)
{
- foreach (var shape in shapes)
+ for(var i =0; i< shapes.Length; i++)
{
- this.AddPoints(clipper, shape, polyType);
+ if (shouldInclude[i])
+ {
+ this.AddPoints(clipper, shapes[i], polyType);
+ }
}
}
- private void ExtractOutlines(ClipperLib.PolyNode tree, List outlines, List holes)
+
+ private void ExtractOutlines(ClipperLib.PolyNode tree, List outlines, List holes)
{
if (tree.Contour.Any())
{
// convert the Clipper Contour from scaled ints back down to the origional size (this is going to be lossy but not significantly)
- var polygon = new Polygon(new LinearLineSegment(tree.Contour.Select(x => new Vector2(x.X / ClipperScaleFactor, x.Y / ClipperScaleFactor)).ToArray()));
+ var pointCount = tree.Contour.Count;
+ var vectors = new Vector2[pointCount];
+ for(var i=0; i< pointCount; i++)
+ {
+ var p = tree.Contour[i];
+ vectors[i] = new Vector2(p.X, p.Y) / ClipperScaleFactor;
+ }
+
+ var polygon = new Polygon(new LinearLineSegment(vectors));
if (tree.IsHole)
{
@@ -169,28 +180,113 @@ namespace ImageSharp.Drawing.Shapes
}
}
- private void FixAndSetShapes(IEnumerable outlines, IEnumerable holes)
+ ///
+ /// Determines if the s bounding boxes overlap.
+ ///
+ /// The source.
+ /// The target.
+ ///
+ private bool OverlappingBoundingBoxes(IShape source, IShape target)
{
- var clipper = new ClipperLib.Clipper();
+ return source.Bounds.Intersects(target.Bounds);
+ }
- // add the outlines and the holes to clipper, scaling up from the float source to the int based system clipper uses
- this.AddPoints(clipper, outlines, ClipperLib.PolyType.ptSubject);
- this.AddPoints(clipper, holes, ClipperLib.PolyType.ptClip);
- var tree = new ClipperLib.PolyTree();
- clipper.Execute(ClipperLib.ClipType.ctDifference, tree);
+ private void FixAndSetShapes(IShape[] outlines, IShape[] holes)
+ {
+ // if any outline doesn't overlap another shape then we don't have to bother with sending them through clipper
+ // as sending then though clipper will turn them into generic polygons and loose thier shape specific optimisations
- List newOutlines = new List();
- List newHoles = new List();
+ int outlineLength = outlines.Length;
+ int holesLength = holes.Length;
+ bool[] overlappingOutlines = new bool[outlineLength];
+ bool[] overlappingHoles = new bool[holesLength];
+ bool anyOutlinesOverlapping = false;
+ bool anyHolesOverlapping = false;
+
+ for (int i = 0; i < outlineLength; i++)
+ {
+ for (int j = i + 1; j < outlineLength; j++)
+ {
+ //skip the bounds check if they are already tested
+ if (overlappingOutlines[i] == false || overlappingOutlines[j] == false)
+ {
+ if (OverlappingBoundingBoxes(outlines[i], outlines[j]))
+ {
+ overlappingOutlines[i] = true;
+ overlappingOutlines[j] = true;
+ anyOutlinesOverlapping = true;
+ }
+ }
+ }
- // convert the 'tree' back to paths
- this.ExtractOutlines(tree, newOutlines, newHoles);
+ for (int k = 0; k < holesLength; k++)
+ {
+ if (overlappingOutlines[i] == false || overlappingHoles[k] == false)
+ {
+ if (OverlappingBoundingBoxes(outlines[i], holes[k]))
+ {
+ overlappingOutlines[i] = true;
+ overlappingHoles[k] = true;
+ anyOutlinesOverlapping = true;
+ anyHolesOverlapping = true;
+ }
+ }
+ }
+ }
- this.outlines = newOutlines;
- this.holes = newHoles;
+ if (anyOutlinesOverlapping)
+ {
+ var clipper = new ClipperLib.Clipper();
- // extract the final list of paths out of the new polygons we just converted down to.
- this.paths = newOutlines.Union(newHoles).ToArray();
+ // add the outlines and the holes to clipper, scaling up from the float source to the int based system clipper uses
+ this.AddPoints(clipper, outlines, overlappingOutlines, ClipperLib.PolyType.ptSubject);
+ if (anyHolesOverlapping)
+ {
+ this.AddPoints(clipper, holes, overlappingHoles, ClipperLib.PolyType.ptClip);
+ }
+
+ var tree = new ClipperLib.PolyTree();
+ clipper.Execute(ClipperLib.ClipType.ctDifference, tree);
+
+ List newOutlines = new List();
+ List newHoles = new List();
+
+ // convert the 'tree' back to shapes
+ this.ExtractOutlines(tree, newOutlines, newHoles);
+
+ // add the origional outlines that where not overlapping
+ for (int i = 0; i < outlineLength - 1; i++)
+ {
+ if (!overlappingOutlines[i])
+ {
+ newOutlines.Add(outlines[i]);
+ }
+ }
+
+ this.outlines = newOutlines.ToArray();
+ if (newHoles.Count > 0)
+ {
+ this.holes = newHoles.ToArray();
+ }
+ }else
+ {
+ this.outlines = outlines;
+ }
+
+ var paths = new List();
+ foreach (var o in this.outlines)
+ {
+ paths.AddRange(o);
+ }
+ if (this.holes != null)
+ {
+ foreach (var o in this.holes)
+ {
+ paths.AddRange(o);
+ }
+ }
+ this.paths = paths;
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Drawing/Shapes/Polygon.cs b/src/ImageSharp/Drawing/Shapes/Polygon.cs
index 9d9626d4ee..3621a9e2c0 100644
--- a/src/ImageSharp/Drawing/Shapes/Polygon.cs
+++ b/src/ImageSharp/Drawing/Shapes/Polygon.cs
@@ -29,6 +29,16 @@ namespace ImageSharp.Drawing.Shapes
this.pathCollection = new[] { this };
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The segment.
+ public Polygon(ILineSegment segment)
+ {
+ this.innerPath = new InternalPath(segment, true);
+ this.pathCollection = new[] { this };
+ }
+
///
/// Gets the bounding box of this shape.
///
diff --git a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs
index 1f6708bf81..cdcb5ebe43 100644
--- a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs
@@ -66,6 +66,55 @@ namespace ImageSharp.Tests.Drawing
}
}
+ [Fact]
+ public void ImageShouldBeOverlayedByPolygonOutlineNoOverlapping()
+ {
+ string path = CreateOutputDirectory("Drawing", "LineComplexPolygon");
+ var simplePath = new LinearPolygon(
+ new Vector2(10, 10),
+ new Vector2(200, 150),
+ new Vector2(50, 300));
+
+ var hole1 = new LinearPolygon(
+ new Vector2(207, 25),
+ new Vector2(263, 25),
+ new Vector2(235, 57));
+
+ var image = new Image(500, 500);
+
+ using (FileStream output = File.OpenWrite($"{path}/SimpleVanishHole.png"))
+ {
+ image
+ .BackgroundColor(Color.Blue)
+ .DrawPolygon(Color.HotPink, 5, new ComplexPolygon(simplePath, hole1))
+ .Save(output);
+ }
+
+ using (var sourcePixels = image.Lock())
+ {
+ Assert.Equal(Color.HotPink, sourcePixels[10, 10]);
+
+ Assert.Equal(Color.HotPink, sourcePixels[200, 150]);
+
+ Assert.Equal(Color.HotPink, sourcePixels[50, 300]);
+
+
+ //Assert.Equal(Color.HotPink, sourcePixels[37, 85]);
+
+ //Assert.Equal(Color.HotPink, sourcePixels[93, 85]);
+
+ //Assert.Equal(Color.HotPink, sourcePixels[65, 137]);
+
+ Assert.Equal(Color.Blue, sourcePixels[2, 2]);
+
+ //inside hole
+ Assert.Equal(Color.Blue, sourcePixels[57, 99]);
+
+ //inside shape
+ Assert.Equal(Color.Blue, sourcePixels[100, 192]);
+ }
+ }
+
[Fact]
public void ImageShouldBeOverlayedByPolygonOutlineOverlapping()