@ -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 = 1 0 0f ;
private IEnumerable < IShape > holes ;
private IEnumerable < IShape > outlines ;
private IShape [ ] holes ;
private IShape [ ] outlines ;
private IEnumerable < IPath > paths ;
/// <summary>
@ -39,7 +38,7 @@ namespace ImageSharp.Drawing.Shapes
/// </summary>
/// <param name="outlines">The outlines.</param>
/// <param name="holes">The holes.</param>
public ComplexPolygon ( IEnumerable < IShape > outlines , IEnumerable < IShape > 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 < IShape > 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 < Polygon > outlines , List < Polygon > holes )
private void ExtractOutlines ( ClipperLib . PolyNode tree , List < IShape > outlines , List < IShape > 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 < IShape > outlines , IEnumerable < IShape > holes )
/// <summary>
/// Determines if the <see cref="IShape"/>s bounding boxes overlap.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <returns></returns>
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 < Polygon > newOutlines = new List < Polygon > ( ) ;
List < Polygon > newHoles = new List < Polygon > ( ) ;
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 < IShape > newOutlines = new List < IShape > ( ) ;
List < IShape > newHoles = new List < IShape > ( ) ;
// 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 < IPath > ( ) ;
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 ;
}
}
}