Skip to content

Commit e87286e

Browse files
Port Clipper to use Vector2
1 parent cee21d9 commit e87286e

20 files changed

Lines changed: 4876 additions & 138 deletions

src/ImageSharp.Drawing/Processing/Processors/Text/RichTextGlyphRenderer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ private Buffer2D<float> Render(IPath path)
536536
0,
537537
size.Height,
538538
subpixelCount,
539-
IntersectionRule.Nonzero,
539+
IntersectionRule.NonZero,
540540
this.memoryAllocator);
541541

542542
try

src/ImageSharp.Drawing/Processing/ShapeOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ public ShapeOptions()
2020
/// <summary>
2121
/// Gets or sets a value indicating whether antialiasing should be applied.
2222
/// <para/>
23-
/// Defaults to <see cref="IntersectionRule.OddEven"/>.
23+
/// Defaults to <see cref="IntersectionRule.EvenOdd"/>.
2424
/// </summary>
25-
public IntersectionRule IntersectionRule { get; set; } = IntersectionRule.OddEven;
25+
public IntersectionRule IntersectionRule { get; set; } = IntersectionRule.EvenOdd;
2626

2727
/// <inheritdoc/>
2828
public ShapeOptions DeepClone() => new(this);

src/ImageSharp.Drawing/Shapes/EndCapStyle.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,15 @@ public enum EndCapStyle
2222
/// The outlines ends squared off passed the end of the path.
2323
/// </summary>
2424
Square = 2,
25+
26+
/// <summary>
27+
/// The outline is treated as a polygon.
28+
/// </summary>
29+
Polygon = 3,
30+
31+
/// <summary>
32+
/// The outlines ends are joined and the path treated as a polyline
33+
/// </summary>
34+
Joined = 4
2535
}
2636
}

src/ImageSharp.Drawing/Shapes/IntersectionRule.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@
44
namespace SixLabors.ImageSharp.Drawing
55
{
66
/// <summary>
7-
/// Rule for calulating intersection points,
7+
/// Rule for calulating intersection points.
88
/// </summary>
99
public enum IntersectionRule
1010
{
1111
/// <summary>
12-
/// Use odd/even intersection rules, self intersection will cause holes.
12+
/// Only odd numbered sub-regions are filled.
1313
/// </summary>
14-
OddEven = 0,
14+
EvenOdd = 0,
1515

1616
/// <summary>
17-
/// Nonzero rule treats intersecting holes as inside the path thus being ignored by intersections.
17+
/// Only non-zero sub-regions are filled.
1818
/// </summary>
19-
Nonzero = 1
19+
NonZero = 1
2020
}
2121
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Numerics;
6+
7+
namespace SixLabors.ImageSharp.Drawing.Shapes.PolygonClipper
8+
{
9+
internal struct BoundsF
10+
{
11+
public float Left;
12+
public float Top;
13+
public float Right;
14+
public float Bottom;
15+
16+
public BoundsF(float l, float t, float r, float b)
17+
{
18+
Guard.MustBeGreaterThanOrEqualTo(r, l, nameof(r));
19+
Guard.MustBeGreaterThanOrEqualTo(b, t, nameof(r));
20+
21+
this.Left = l;
22+
this.Top = t;
23+
this.Right = r;
24+
this.Bottom = b;
25+
}
26+
27+
public BoundsF(BoundsF bounds)
28+
{
29+
this.Left = bounds.Left;
30+
this.Top = bounds.Top;
31+
this.Right = bounds.Right;
32+
this.Bottom = bounds.Bottom;
33+
}
34+
35+
public BoundsF(bool isValid)
36+
{
37+
if (isValid)
38+
{
39+
this.Left = 0;
40+
this.Top = 0;
41+
this.Right = 0;
42+
this.Bottom = 0;
43+
}
44+
else
45+
{
46+
this.Left = float.MaxValue;
47+
this.Top = float.MaxValue;
48+
this.Right = -float.MaxValue;
49+
this.Bottom = -float.MaxValue;
50+
}
51+
}
52+
53+
public float Width
54+
{
55+
get => this.Right - this.Left;
56+
set => this.Right = this.Left + value;
57+
}
58+
59+
public float Height
60+
{
61+
get => this.Bottom - this.Top;
62+
set => this.Bottom = this.Top + value;
63+
}
64+
65+
public bool IsEmpty()
66+
=> this.Bottom <= this.Top || this.Right <= this.Left;
67+
68+
public Vector2 MidPoint()
69+
=> new Vector2(this.Left + this.Right, this.Top + this.Bottom) * .5F;
70+
71+
public bool Contains(Vector2 pt)
72+
=> pt.X > this.Left
73+
&& pt.X < this.Right
74+
&& pt.Y > this.Top && pt.Y < this.Bottom;
75+
76+
public bool Contains(BoundsF bounds)
77+
=> bounds.Left >= this.Left
78+
&& bounds.Right <= this.Right
79+
&& bounds.Top >= this.Top
80+
&& bounds.Bottom <= this.Bottom;
81+
82+
public bool Intersects(BoundsF bounds)
83+
=> (Math.Max(this.Left, bounds.Left) < Math.Min(this.Right, bounds.Right))
84+
&& (Math.Max(this.Top, bounds.Top) < Math.Min(this.Bottom, bounds.Bottom));
85+
86+
public PathF AsPath()
87+
=> new(4)
88+
{
89+
new PointF(this.Left, this.Top),
90+
new PointF(this.Right, this.Top),
91+
new PointF(this.Right, this.Bottom),
92+
new PointF(this.Left, this.Bottom)
93+
};
94+
}
95+
}

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

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Numerics;
7-
using Clipper2Lib;
7+
using SixLabors.ImageSharp.Drawing.Shapes.PolygonClipper;
88

99
namespace SixLabors.ImageSharp.Drawing.PolygonClipper
1010
{
@@ -13,14 +13,15 @@ namespace SixLabors.ImageSharp.Drawing.PolygonClipper
1313
/// </summary>
1414
internal class Clipper
1515
{
16-
private const float ScalingFactor = 1000.0f;
17-
private readonly object syncRoot = new();
18-
private readonly Clipper64 innerClipper;
16+
// To make the floating point polygons compatable with clipper we have to scale them.
17+
private const float ScalingFactor = 1000F;
18+
private readonly Shapes.PolygonClipper.PolygonClipper polygonClipper;
1919

2020
/// <summary>
2121
/// Initializes a new instance of the <see cref="Clipper"/> class.
2222
/// </summary>
23-
public Clipper() => this.innerClipper = new Clipper64();
23+
public Clipper()
24+
=> this.polygonClipper = new Shapes.PolygonClipper.PolygonClipper();
2425

2526
/// <summary>
2627
/// Initializes a new instance of the <see cref="Clipper" /> class.
@@ -38,29 +39,22 @@ public Clipper(params ClippablePath[] shapes)
3839
/// <exception cref="ClipperException">GenerateClippedShapes: Open paths have been disabled.</exception>
3940
public IPath[] GenerateClippedShapes()
4041
{
41-
Paths64 closedPaths = new();
42-
Paths64 openPaths = new();
42+
PathsF closedPaths = new();
43+
PathsF openPaths = new();
4344

44-
// TODO: Why are we locking?
45-
lock (this.syncRoot)
46-
{
47-
this.innerClipper.Execute(ClipType.Difference, FillRule.EvenOdd, closedPaths, openPaths);
48-
}
45+
this.polygonClipper.Execute(ClipType.Difference, FillRule.EvenOdd, closedPaths, openPaths);
4946

5047
var shapes = new IPath[closedPaths.Count + openPaths.Count];
48+
const float scale = 1F / ScalingFactor;
5149

5250
for (int i = 0; i < closedPaths.Count; i++)
5351
{
5452
var points = new PointF[closedPaths[i].Count];
5553

5654
for (int j = 0; j < closedPaths[i].Count; j++)
5755
{
58-
Point64 p = closedPaths[i][j];
59-
60-
// to make the floating point polygons compatable with clipper we had
61-
// to scale them up to make them ints but still retain some level of precision
62-
// thus we have to scale them back down
63-
points[j] = new Vector2(p.X / ScalingFactor, p.Y / ScalingFactor);
56+
Vector2 v = closedPaths[i][j];
57+
points[j] = v * scale;
6458
}
6559

6660
shapes[i] = new Polygon(new LinearLineSegment(points));
@@ -72,12 +66,8 @@ public IPath[] GenerateClippedShapes()
7266

7367
for (int j = 0; j < closedPaths[i].Count; j++)
7468
{
75-
Point64 p = closedPaths[i][j];
76-
77-
// to make the floating point polygons compatable with clipper we had
78-
// to scale them up to make them ints but still retain some level of precision
79-
// thus we have to scale them back down
80-
points[j] = new Vector2(p.X / ScalingFactor, p.Y / ScalingFactor);
69+
Vector2 v = closedPaths[i][j];
70+
points[j] = v * scale;
8171
}
8272

8373
shapes[i] = new Path(new LinearLineSegment(points));
@@ -144,20 +134,14 @@ public void AddPath(IPath path, ClippingType clippingType)
144134
internal void AddPath(ISimplePath path, ClippingType clippingType)
145135
{
146136
ReadOnlySpan<PointF> vectors = path.Points.Span;
147-
Path64 points = new(vectors.Length);
137+
PathF points = new(vectors.Length);
148138
for (int i = 0; i < vectors.Length; i++)
149139
{
150-
PointF v = vectors[i];
151-
points.Add(new Point64(v.X * ScalingFactor, v.Y * ScalingFactor));
140+
Vector2 v = vectors[i];
141+
points.Add(v * ScalingFactor);
152142
}
153143

154-
PathType type = clippingType == ClippingType.Clip ? PathType.Clip : PathType.Subject;
155-
156-
// TODO: Why are we locking?
157-
lock (this.syncRoot)
158-
{
159-
this.innerClipper.AddPath(points, type, !path.IsClosed);
160-
}
144+
this.polygonClipper.AddPath(points, clippingType, !path.IsClosed);
161145
}
162146
}
163147
}

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

Lines changed: 28 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
using System;
55
using System.Linq;
6-
using Clipper2Lib;
6+
using System.Numerics;
7+
using SixLabors.ImageSharp.Drawing.Shapes.PolygonClipper;
78

89
namespace SixLabors.ImageSharp.Drawing.PolygonClipper
910
{
@@ -12,43 +13,40 @@ namespace SixLabors.ImageSharp.Drawing.PolygonClipper
1213
/// </summary>
1314
internal class ClipperOffset
1415
{
15-
private const float ScalingFactor = 1000.0f;
16-
17-
private readonly Clipper2Lib.ClipperOffset innerClipperOffest;
18-
private readonly object syncRoot = new();
16+
// To make the floating point polygons compatable with clipper we have to scale them.
17+
private const float ScalingFactor = 1000F;
18+
private readonly PolygonOffsetter polygonClipperOffset;
1919

2020
/// <summary>
2121
/// Initializes a new instance of the <see cref="ClipperOffset"/> class.
2222
/// </summary>
2323
/// <param name="meterLimit">meter limit</param>
2424
/// <param name="arcTolerance">arc tolerance</param>
2525
public ClipperOffset(double meterLimit = 2, double arcTolerance = 0.25)
26-
=> this.innerClipperOffest = new Clipper2Lib.ClipperOffset(meterLimit, arcTolerance);
26+
=> this.polygonClipperOffset = new((float)meterLimit, (float)arcTolerance);
2727

2828
/// <summary>
29-
/// Calcualte Offset
29+
/// Calculates an offset polygon based on the given path and width.
3030
/// </summary>
3131
/// <param name="width">Width</param>
3232
/// <returns>path offset</returns>
33-
/// <exception cref="ClipperException">Calculate: Couldn't caculate Offset</exception>
33+
/// <exception cref="ClipperException">Calculate: Couldn't calculate the cffset.</exception>
3434
public ComplexPolygon Execute(float width)
3535
{
36-
Paths64 tree = new();
37-
lock (this.syncRoot)
38-
{
39-
this.innerClipperOffest.Execute(width * ScalingFactor, tree);
40-
}
36+
PathsF solution = new();
37+
this.polygonClipperOffset.Execute(width * ScalingFactor, solution);
4138

42-
var polygons = new Polygon[tree.Count];
43-
for (int i = 0; i < tree.Count; i++)
39+
var polygons = new Polygon[solution.Count];
40+
const float scale = 1F / ScalingFactor;
41+
for (int i = 0; i < solution.Count; i++)
4442
{
45-
Path64 pt = tree[i];
43+
PathF pt = solution[i];
4644

47-
PointF[] points = pt.Select(p => new PointF(p.X / ScalingFactor, p.Y / ScalingFactor)).ToArray();
45+
PointF[] points = pt.Select(p => (PointF)(p * scale)).ToArray();
4846
polygons[i] = new Polygon(new LinearLineSegment(points));
4947
}
5048

51-
return new ComplexPolygon(polygons.ToArray());
49+
return new ComplexPolygon(polygons);
5250
}
5351

5452
/// <summary>
@@ -58,8 +56,17 @@ public ComplexPolygon Execute(float width)
5856
/// <param name="jointStyle">Joint Style</param>
5957
/// <param name="endCapStyle">Endcap Style</param>
6058
/// <exception cref="ClipperException">AddPath: Invalid Path</exception>
61-
public void AddPath(ReadOnlySpan<PointF> pathPoints, JointStyle jointStyle, EndCapStyle endCapStyle) =>
62-
this.AddPath(pathPoints, jointStyle, Convert(endCapStyle));
59+
public void AddPath(ReadOnlySpan<PointF> pathPoints, JointStyle jointStyle, EndCapStyle endCapStyle)
60+
{
61+
PathF points = new(pathPoints.Length);
62+
for (int i = 0; i < pathPoints.Length; i++)
63+
{
64+
Vector2 v = pathPoints[i];
65+
points.Add(v * ScalingFactor);
66+
}
67+
68+
this.polygonClipperOffset.AddPath(points, jointStyle, endCapStyle);
69+
}
6370

6471
/// <summary>
6572
/// Adds the path.
@@ -87,46 +94,7 @@ public void AddPath(IPath path, JointStyle jointStyle, EndCapStyle endCapStyle)
8794
private void AddPath(ISimplePath path, JointStyle jointStyle, EndCapStyle endCapStyle)
8895
{
8996
ReadOnlySpan<PointF> vectors = path.Points.Span;
90-
EndType type = path.IsClosed ? EndType.Joined : Convert(endCapStyle);
91-
this.AddPath(vectors, jointStyle, type);
97+
this.AddPath(vectors, jointStyle, path.IsClosed ? EndCapStyle.Joined : endCapStyle);
9298
}
93-
94-
/// <summary>
95-
/// Adds the path.
96-
/// </summary>
97-
/// <param name="pathPoints">The path points</param>
98-
/// <param name="jointStyle">Joint Style</param>
99-
/// <param name="endCapStyle">Endcap Style</param>
100-
/// <exception cref="ClipperException">AddPath: Invalid Path</exception>
101-
private void AddPath(ReadOnlySpan<PointF> pathPoints, JointStyle jointStyle, EndType endCapStyle)
102-
{
103-
Path64 points = new();
104-
foreach (PointF v in pathPoints)
105-
{
106-
points.Add(new Point64(v.X * ScalingFactor, v.Y * ScalingFactor));
107-
}
108-
109-
// TODO: Why are we locking?
110-
lock (this.syncRoot)
111-
{
112-
this.innerClipperOffest.AddPath(points, Convert(jointStyle), endCapStyle);
113-
}
114-
}
115-
116-
private static JoinType Convert(JointStyle style)
117-
=> style switch
118-
{
119-
JointStyle.Round => JoinType.Round,
120-
JointStyle.Miter => JoinType.Miter,
121-
_ => JoinType.Square,
122-
};
123-
124-
private static EndType Convert(EndCapStyle style)
125-
=> style switch
126-
{
127-
EndCapStyle.Round => EndType.Round,
128-
EndCapStyle.Square => EndType.Square,
129-
_ => EndType.Butt,
130-
};
13199
}
132100
}

0 commit comments

Comments
 (0)