Skip to content

Commit 0849d85

Browse files
Merge pull request #145 from obayomy/master
Added wrapper for ClipperOffset algorithm to fix internal exception
2 parents a1ca308 + ddffd2f commit 0849d85

File tree

7 files changed

+189
-112
lines changed

7 files changed

+189
-112
lines changed

src/ImageSharp.Drawing/Shapes/ClipPathExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public static class ClipPathExtensions
1717
/// <param name="shape">The shape.</param>
1818
/// <param name="holes">The holes.</param>
1919
/// <returns>Returns a new shape with the holes clipped out of the shape.</returns>
20+
/// <exception cref="ClipperException">Open paths have been disabled.</exception>
2021
public static IPath Clip(this IPath shape, IEnumerable<IPath> holes)
2122
{
2223
var clipper = new Clipper();
@@ -35,6 +36,7 @@ public static IPath Clip(this IPath shape, IEnumerable<IPath> holes)
3536
/// <param name="shape">The shape.</param>
3637
/// <param name="holes">The holes.</param>
3738
/// <returns>Returns a new shape with the holes clipped out of the shape.</returns>
39+
/// <exception cref="ClipperException">Open paths have been disabled.</exception>
3840
public static IPath Clip(this IPath shape, params IPath[] holes)
3941
=> shape.Clip((IEnumerable<IPath>)holes);
4042
}

src/ImageSharp.Drawing/Shapes/Clipper/clipper.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// <auto-generated />
1+
// <auto-generated />
22
// not really auto generated but is a code based dependency from https://sourceforge.net/projects/polyclipping/
33
// only update is to make everything internal
44
/*******************************************************************************
@@ -54,6 +54,7 @@
5454

5555
using System;
5656
using System.Collections.Generic;
57+
using ClipperException = SixLabors.ImageSharp.Drawing.PolygonClipper.ClipperException;
5758
//using System.Text; //for Int128.AsString() & StringBuilder
5859
//using System.IO; //debugging with streamReader & StreamWriter
5960
//using System.Windows.Forms; //debugging to clipboard
@@ -4986,11 +4987,6 @@ internal void DoRound(int j, int k)
49864987
}
49874988
//------------------------------------------------------------------------------
49884989
}
4989-
4990-
class ClipperException : Exception
4991-
{
4992-
public ClipperException(string description) : base(description) { }
4993-
}
49944990
//------------------------------------------------------------------------------
49954991

4996-
} //end ClipperLib namespace
4992+
} //end ClipperLib namespace

src/ImageSharp.Drawing/Shapes/OutlinePathExtensions.cs

Lines changed: 21 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33

44
using System;
55
using System.Collections.Generic;
6-
using System.Linq;
76
using System.Numerics;
8-
using System.Runtime.InteropServices;
9-
using ClipperLib;
7+
using SixLabors.ImageSharp.Drawing.PolygonClipper;
108

119
namespace SixLabors.ImageSharp.Drawing
1210
{
@@ -16,7 +14,6 @@ namespace SixLabors.ImageSharp.Drawing
1614
public static class OutlinePathExtensions
1715
{
1816
private const double MiterOffsetDelta = 20;
19-
private const float ScalingFactor = 1000.0f;
2017

2118
/// <summary>
2219
/// Generates a outline of the path with alternating on and off segments based on the pattern.
@@ -25,6 +22,7 @@ public static class OutlinePathExtensions
2522
/// <param name="width">The final width outline</param>
2623
/// <param name="pattern">The pattern made of multiples of the width.</param>
2724
/// <returns>A new path representing the outline.</returns>
25+
/// <exception cref="ClipperException">Couldn't calculate offset.</exception>
2826
public static IPath GenerateOutline(this IPath path, float width, float[] pattern)
2927
=> path.GenerateOutline(width, new ReadOnlySpan<float>(pattern));
3028

@@ -35,6 +33,7 @@ public static IPath GenerateOutline(this IPath path, float width, float[] patter
3533
/// <param name="width">The final width outline</param>
3634
/// <param name="pattern">The pattern made of multiples of the width.</param>
3735
/// <returns>A new path representing the outline.</returns>
36+
/// <exception cref="ClipperException">Couldn't calculate offset.</exception>
3837
public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<float> pattern)
3938
=> path.GenerateOutline(width, pattern, false);
4039

@@ -46,6 +45,7 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
4645
/// <param name="pattern">The pattern made of multiples of the width.</param>
4746
/// <param name="startOff">Weather the first item in the pattern is on or off.</param>
4847
/// <returns>A new path representing the outline.</returns>
48+
/// <exception cref="ClipperException">Couldn't calculate offset.</exception>
4949
public static IPath GenerateOutline(this IPath path, float width, float[] pattern, bool startOff)
5050
=> path.GenerateOutline(width, new ReadOnlySpan<float>(pattern), startOff);
5151

@@ -57,6 +57,7 @@ public static IPath GenerateOutline(this IPath path, float width, float[] patter
5757
/// <param name="pattern">The pattern made of multiples of the width.</param>
5858
/// <param name="startOff">Weather the first item in the pattern is on or off.</param>
5959
/// <returns>A new path representing the outline.</returns>
60+
/// <exception cref="ClipperException">Couldn't calculate offset.</exception>
6061
public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<float> pattern, bool startOff)
6162
=> GenerateOutline(path, width, pattern, startOff, JointStyle.Square, EndCapStyle.Butt);
6263

@@ -70,24 +71,18 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
7071
/// <param name="jointStyle">The style to render the joints.</param>
7172
/// <param name="patternSectionCapStyle">The style to render between sections of the specified pattern.</param>
7273
/// <returns>A new path representing the outline.</returns>
74+
/// <exception cref="ClipperException">Couldn't calculate offset.</exception>
7375
public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<float> pattern, bool startOff, JointStyle jointStyle = JointStyle.Square, EndCapStyle patternSectionCapStyle = EndCapStyle.Butt)
7476
{
7577
if (pattern.Length < 2)
7678
{
7779
return path.GenerateOutline(width, jointStyle: jointStyle);
7880
}
7981

80-
JoinType style = Convert(jointStyle);
81-
EndType patternSectionCap = Convert(patternSectionCapStyle);
82-
8382
IEnumerable<ISimplePath> paths = path.Flatten();
8483

85-
var offset = new ClipperOffset()
86-
{
87-
MiterLimit = MiterOffsetDelta
88-
};
89-
90-
var buffer = new List<IntPoint>(3);
84+
var offset = new ClipperOffset(MiterOffsetDelta);
85+
var buffer = new List<PointF>();
9186
foreach (ISimplePath p in paths)
9287
{
9388
bool online = !startOff;
@@ -116,13 +111,13 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
116111
float t = targetLength / distToNext;
117112

118113
Vector2 point = (currentPoint * (1 - t)) + (targetPoint * t);
119-
buffer.Add(currentPoint.ToPoint());
120-
buffer.Add(point.ToPoint());
114+
buffer.Add(currentPoint);
115+
buffer.Add(point);
121116

122117
// we now inset a line joining
123118
if (online)
124119
{
125-
offset.AddPath(buffer, style, patternSectionCap);
120+
offset.AddPath(new ReadOnlySpan<PointF>(buffer.ToArray()), jointStyle, patternSectionCapStyle);
126121
}
127122

128123
online = !online;
@@ -137,7 +132,7 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
137132
}
138133
else if (distToNext <= targetLength)
139134
{
140-
buffer.Add(currentPoint.ToPoint());
135+
buffer.Add(currentPoint);
141136
currentPoint = targetPoint;
142137
i++;
143138
targetLength -= distToNext;
@@ -148,16 +143,16 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
148143
{
149144
if (p.IsClosed)
150145
{
151-
buffer.Add(points[0].ToPoint());
146+
buffer.Add(points[0]);
152147
}
153148
else
154149
{
155-
buffer.Add(points[points.Length - 1].ToPoint());
150+
buffer.Add(points[points.Length - 1]);
156151
}
157152

158153
if (online)
159154
{
160-
offset.AddPath(buffer, style, patternSectionCap);
155+
offset.AddPath(new ReadOnlySpan<PointF>(buffer.ToArray()), jointStyle, patternSectionCapStyle);
161156
}
162157

163158
online = !online;
@@ -168,7 +163,7 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
168163
}
169164
}
170165

171-
return ExecuteOutliner(width, offset);
166+
return offset.Execute(width);
172167
}
173168

174169
/// <summary>
@@ -177,6 +172,7 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
177172
/// <param name="path">the path to outline</param>
178173
/// <param name="width">The final width outline</param>
179174
/// <returns>A new path representing the outline.</returns>
175+
/// <exception cref="ClipperException">Couldn't calculate offset.</exception>
180176
public static IPath GenerateOutline(this IPath path, float width) => GenerateOutline(path, width, JointStyle.Square, EndCapStyle.Butt);
181177

182178
/// <summary>
@@ -187,69 +183,13 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
187183
/// <param name="jointStyle">The style to render the joints.</param>
188184
/// <param name="endCapStyle">The style to render the end caps of open paths (ignored on closed paths).</param>
189185
/// <returns>A new path representing the outline.</returns>
186+
/// <exception cref="ClipperException">Couldn't calculate offset.</exception>
190187
public static IPath GenerateOutline(this IPath path, float width, JointStyle jointStyle = JointStyle.Square, EndCapStyle endCapStyle = EndCapStyle.Square)
191188
{
192-
var offset = new ClipperOffset()
193-
{
194-
MiterLimit = MiterOffsetDelta
195-
};
196-
197-
JoinType style = Convert(jointStyle);
198-
EndType openEndCapStyle = Convert(endCapStyle);
199-
200-
// Pattern can be applied to the path by cutting it into segments
201-
IEnumerable<ISimplePath> paths = path.Flatten();
202-
foreach (ISimplePath p in paths)
203-
{
204-
ReadOnlySpan<Vector2> vectors = MemoryMarshal.Cast<PointF, Vector2>(p.Points.Span);
205-
var points = new List<IntPoint>(vectors.Length);
206-
foreach (Vector2 v in vectors)
207-
{
208-
points.Add(new IntPoint(v.X * ScalingFactor, v.Y * ScalingFactor));
209-
}
210-
211-
EndType type = p.IsClosed ? EndType.etClosedLine : openEndCapStyle;
212-
213-
offset.AddPath(points, style, type);
214-
}
215-
216-
return ExecuteOutliner(width, offset);
217-
}
218-
219-
private static ComplexPolygon ExecuteOutliner(float width, ClipperOffset offset)
220-
{
221-
var tree = new List<List<IntPoint>>();
222-
offset.Execute(ref tree, width * ScalingFactor / 2);
223-
var polygons = new List<Polygon>();
224-
foreach (List<IntPoint> pt in tree)
225-
{
226-
PointF[] points = pt.Select(p => new PointF(p.X / ScalingFactor, p.Y / ScalingFactor)).ToArray();
227-
polygons.Add(new Polygon(new LinearLineSegment(points)));
228-
}
189+
var offset = new ClipperOffset(MiterOffsetDelta);
190+
offset.AddPath(path, jointStyle, endCapStyle);
229191

230-
return new ComplexPolygon(polygons.ToArray());
192+
return offset.Execute(width);
231193
}
232-
233-
private static IntPoint ToPoint(this PointF vector)
234-
=> new IntPoint(vector.X * ScalingFactor, vector.Y * ScalingFactor);
235-
236-
private static IntPoint ToPoint(this Vector2 vector)
237-
=> new IntPoint(vector.X * ScalingFactor, vector.Y * ScalingFactor);
238-
239-
private static JoinType Convert(JointStyle style)
240-
=> style switch
241-
{
242-
JointStyle.Round => JoinType.jtRound,
243-
JointStyle.Miter => JoinType.jtMiter,
244-
_ => JoinType.jtSquare,
245-
};
246-
247-
private static EndType Convert(EndCapStyle style)
248-
=> style switch
249-
{
250-
EndCapStyle.Round => EndType.etOpenRound,
251-
EndCapStyle.Square => EndType.etOpenSquare,
252-
_ => EndType.etOpenButt,
253-
};
254194
}
255195
}

src/ImageSharp.Drawing/Shapes/PolygonClipper/Clipper.cs

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
@@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Drawing.PolygonClipper
1111
/// <summary>
1212
/// Library to clip polygons.
1313
/// </summary>
14-
public class Clipper
14+
internal class Clipper
1515
{
1616
private const float ScalingFactor = 1000.0f;
1717

@@ -42,6 +42,7 @@ public Clipper(params ClippablePath[] shapes)
4242
/// <returns>
4343
/// Returns the <see cref="IPath" /> array containing the converted polygons.
4444
/// </returns>
45+
/// <exception cref="ClipperException">GenerateClippedShapes: Open paths have been disabled.</exception>
4546
public IPath[] GenerateClippedShapes()
4647
{
4748
var results = new List<PolyNode>();
@@ -79,12 +80,10 @@ public IPath[] GenerateClippedShapes()
7980
/// Adds the paths.
8081
/// </summary>
8182
/// <param name="paths">The paths.</param>
83+
/// <exception cref="ClipperException">Open paths have been disabled.</exception>
8284
public void AddPaths(ClippablePath[] paths)
8385
{
84-
if (paths == null)
85-
{
86-
throw new ArgumentNullException(nameof(paths));
87-
}
86+
Guard.NotNull(paths, nameof(paths));
8887

8988
for (int i = 0; i < paths.Length; i++)
9089
{
@@ -99,12 +98,10 @@ public void AddPaths(ClippablePath[] paths)
9998
/// </summary>
10099
/// <param name="paths">The paths.</param>
101100
/// <param name="clippingType">The clipping type.</param>
101+
/// <exception cref="ClipperException">Open paths have been disabled.</exception>
102102
public void AddPaths(IEnumerable<IPath> paths, ClippingType clippingType)
103103
{
104-
if (paths is null)
105-
{
106-
throw new ArgumentNullException(nameof(paths));
107-
}
104+
Guard.NotNull(paths, nameof(paths));
108105

109106
foreach (IPath p in paths)
110107
{
@@ -117,12 +114,10 @@ public void AddPaths(IEnumerable<IPath> paths, ClippingType clippingType)
117114
/// </summary>
118115
/// <param name="path">The path.</param>
119116
/// <param name="clippingType">The clipping type.</param>
117+
/// <exception cref="ClipperException">Open paths have been disabled.</exception>
120118
public void AddPath(IPath path, ClippingType clippingType)
121119
{
122-
if (path is null)
123-
{
124-
throw new ArgumentNullException(nameof(path));
125-
}
120+
Guard.NotNull(path, nameof(path));
126121

127122
foreach (ISimplePath p in path.Flatten())
128123
{
@@ -135,7 +130,7 @@ public void AddPath(IPath path, ClippingType clippingType)
135130
/// </summary>
136131
/// <param name="path">The path.</param>
137132
/// <param name="clippingType">Type of the poly.</param>
138-
/// <exception cref="ClipperException">AddPath: Open paths have been disabled.</exception>
133+
/// <exception cref="ClipperException">Open paths have been disabled.</exception>
139134
internal void AddPath(ISimplePath path, ClippingType clippingType)
140135
{
141136
ReadOnlySpan<PointF> vectors = path.Points.Span;
@@ -153,4 +148,4 @@ internal void AddPath(ISimplePath path, ClippingType clippingType)
153148
}
154149
}
155150
}
156-
}
151+
}
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
55

66
namespace SixLabors.ImageSharp.Drawing.PolygonClipper
77
{
88
/// <summary>
9-
/// Clipper Exception
9+
/// The exception that is thrown when an error occurs clipping a polygon.
1010
/// </summary>
11-
/// <seealso cref="System.Exception" />
12-
internal class ClipperException : Exception
11+
public class ClipperException : Exception
1312
{
1413
/// <summary>
1514
/// Initializes a new instance of the <see cref="ClipperException"/> class.
1615
/// </summary>
17-
/// <param name="description">The description.</param>
18-
public ClipperException(string description)
19-
: base(description)
16+
/// <param name="message">The message that describes the error.</param>
17+
public ClipperException(string message)
18+
: base(message)
2019
{
2120
}
2221
}
23-
}
22+
}

0 commit comments

Comments
 (0)