Skip to content

Commit e1dc3f4

Browse files
committed
Added wrapper for ClipperOffset
1 parent 423bf06 commit e1dc3f4

2 files changed

Lines changed: 145 additions & 81 deletions

File tree

src/ImageSharp.Drawing/Shapes/OutlinePathExtensions.cs

Lines changed: 14 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.Shapes.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.
@@ -77,17 +74,10 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
7774
return path.GenerateOutline(width, jointStyle: jointStyle);
7875
}
7976

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

85-
var offset = new ClipperOffset()
86-
{
87-
MiterLimit = MiterOffsetDelta
88-
};
89-
90-
var buffer = new List<IntPoint>(3);
79+
var offset = new ClipperOffset(MiterOffsetDelta);
80+
var buffer = new List<PointF>();
9181
foreach (ISimplePath p in paths)
9282
{
9383
bool online = !startOff;
@@ -116,13 +106,13 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
116106
float t = targetLength / distToNext;
117107

118108
Vector2 point = (currentPoint * (1 - t)) + (targetPoint * t);
119-
buffer.Add(currentPoint.ToPoint());
120-
buffer.Add(point.ToPoint());
109+
buffer.Add(currentPoint);
110+
buffer.Add(point);
121111

122112
// we now inset a line joining
123113
if (online)
124114
{
125-
offset.AddPath(buffer, style, patternSectionCap);
115+
offset.AddPath(new ReadOnlySpan<PointF>(buffer.ToArray()), jointStyle, patternSectionCapStyle);
126116
}
127117

128118
online = !online;
@@ -137,7 +127,7 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
137127
}
138128
else if (distToNext <= targetLength)
139129
{
140-
buffer.Add(currentPoint.ToPoint());
130+
buffer.Add(currentPoint);
141131
currentPoint = targetPoint;
142132
i++;
143133
targetLength -= distToNext;
@@ -148,16 +138,16 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
148138
{
149139
if (p.IsClosed)
150140
{
151-
buffer.Add(points[0].ToPoint());
141+
buffer.Add(points[0]);
152142
}
153143
else
154144
{
155-
buffer.Add(points[points.Length - 1].ToPoint());
145+
buffer.Add(points[points.Length - 1]);
156146
}
157147

158148
if (online)
159149
{
160-
offset.AddPath(buffer, style, patternSectionCap);
150+
offset.AddPath(new ReadOnlySpan<PointF>(buffer.ToArray()), jointStyle, patternSectionCapStyle);
161151
}
162152

163153
online = !online;
@@ -168,7 +158,7 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
168158
}
169159
}
170160

171-
return ExecuteOutliner(width, offset);
161+
return offset.Execute(width);
172162
}
173163

174164
/// <summary>
@@ -189,67 +179,10 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
189179
/// <returns>A new path representing the outline.</returns>
190180
public static IPath GenerateOutline(this IPath path, float width, JointStyle jointStyle = JointStyle.Square, EndCapStyle endCapStyle = EndCapStyle.Square)
191181
{
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-
}
182+
var offset = new ClipperOffset(MiterOffsetDelta);
183+
offset.AddPath(path, jointStyle, endCapStyle);
229184

230-
return new ComplexPolygon(polygons.ToArray());
185+
return offset.Execute(width);
231186
}
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-
};
254187
}
255188
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using ClipperLib;
8+
9+
namespace SixLabors.ImageSharp.Drawing.Shapes.PolygonClipper
10+
{
11+
/// <summary>
12+
/// Wrapper for clipper offset
13+
/// </summary>
14+
public class ClipperOffset
15+
{
16+
private const float ScalingFactor = 1000.0f;
17+
18+
private readonly ClipperLib.ClipperOffset innerClipperOffest;
19+
private readonly object syncRoot = new object();
20+
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="ClipperOffset"/> class.
23+
/// </summary>
24+
/// <param name="meterLimit">meter limit</param>
25+
/// <param name="arcTolerance">arc tolerance</param>
26+
public ClipperOffset(double meterLimit = 2, double arcTolerance = 0.25) => this.innerClipperOffest = new ClipperLib.ClipperOffset(meterLimit, arcTolerance);
27+
28+
/// <summary>
29+
/// Calcualte Offset
30+
/// </summary>
31+
/// <param name="width">Width</param>
32+
/// <returns>path offset</returns>
33+
/// <exception cref="ClipperException">Execute: </exception>
34+
public ComplexPolygon Execute(float width)
35+
{
36+
var tree = new List<List<IntPoint>>();
37+
this.innerClipperOffest.Execute(ref tree, width * ScalingFactor / 2);
38+
var polygons = new List<Polygon>();
39+
foreach (List<IntPoint> pt in tree)
40+
{
41+
PointF[] points = pt.Select(p => new PointF(p.X / ScalingFactor, p.Y / ScalingFactor)).ToArray();
42+
polygons.Add(new Polygon(new LinearLineSegment(points)));
43+
}
44+
45+
return new ComplexPolygon(polygons.ToArray());
46+
}
47+
48+
/// <summary>
49+
/// Adds the path points
50+
/// </summary>
51+
/// <param name="pathPoints">The path points</param>
52+
/// <param name="jointStyle">Joint Style</param>
53+
/// <param name="endCapStyle">Endcap Style</param>
54+
public void AddPath(ReadOnlySpan<PointF> pathPoints, JointStyle jointStyle, EndCapStyle endCapStyle) =>
55+
this.AddPath(pathPoints, jointStyle, this.Convert(endCapStyle));
56+
57+
/// <summary>
58+
/// Adds the path.
59+
/// </summary>
60+
/// <param name="path">The path.</param>
61+
/// <param name="jointStyle">Joint Style</param>
62+
/// <param name="endCapStyle">Endcap Style</param>
63+
public void AddPath(IPath path, JointStyle jointStyle, EndCapStyle endCapStyle)
64+
{
65+
if (path is null)
66+
{
67+
throw new ArgumentNullException(nameof(path));
68+
}
69+
70+
foreach (ISimplePath p in path.Flatten())
71+
{
72+
this.AddPath(p, jointStyle, endCapStyle);
73+
}
74+
}
75+
76+
/// <summary>
77+
/// Adds the path.
78+
/// </summary>
79+
/// <param name="path">The path.</param>
80+
/// <param name="jointStyle">Joint Style</param>
81+
/// <param name="endCapStyle">Endcap Style</param>
82+
private void AddPath(ISimplePath path, JointStyle jointStyle, EndCapStyle endCapStyle)
83+
{
84+
ReadOnlySpan<PointF> vectors = path.Points.Span;
85+
EndType type = path.IsClosed ? EndType.etClosedLine : this.Convert(endCapStyle);
86+
this.AddPath(vectors, jointStyle, type);
87+
}
88+
89+
/// <summary>
90+
/// Adds the path.
91+
/// </summary>
92+
/// <param name="pathPoints">The path points</param>
93+
/// <param name="jointStyle">Joint Style</param>
94+
/// <param name="endCapStyle">Endcap Style</param>
95+
/// <exception cref="ClipperException">AddPath: Open paths have been disabled.</exception>
96+
private void AddPath(ReadOnlySpan<PointF> pathPoints, JointStyle jointStyle, EndType endCapStyle)
97+
{
98+
var points = new List<IntPoint>();
99+
foreach (PointF v in pathPoints)
100+
{
101+
if (float.IsNaN(v.X) || float.IsNaN(v.Y))
102+
{
103+
throw new ClipperException("Invalid Point");
104+
}
105+
106+
points.Add(new IntPoint(v.X * ScalingFactor, v.Y * ScalingFactor));
107+
}
108+
109+
lock (this.syncRoot)
110+
{
111+
this.innerClipperOffest.AddPath(points, this.Convert(jointStyle), endCapStyle);
112+
}
113+
}
114+
115+
private JoinType Convert(JointStyle style)
116+
=> style switch
117+
{
118+
JointStyle.Round => JoinType.jtRound,
119+
JointStyle.Miter => JoinType.jtMiter,
120+
_ => JoinType.jtSquare,
121+
};
122+
123+
private EndType Convert(EndCapStyle style)
124+
=> style switch
125+
{
126+
EndCapStyle.Round => EndType.etOpenRound,
127+
EndCapStyle.Square => EndType.etOpenSquare,
128+
_ => EndType.etOpenButt,
129+
};
130+
}
131+
}

0 commit comments

Comments
 (0)