Skip to content

Commit e5ada72

Browse files
Use PolygonClipper
1 parent 6b42ee3 commit e5ada72

28 files changed

+533
-5700
lines changed

ImageSharp.Drawing.sln

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
337337
.github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml
338338
EndProjectSection
339339
EndProject
340+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PolygonClipper", "..\..\SixLabors\PolygonClipper\src\PolygonClipper\PolygonClipper.csproj", "{5ED54794-99BF-5E50-A861-0BAAAC794E44}"
341+
EndProject
340342
Global
341343
GlobalSection(SolutionConfigurationPlatforms) = preSolution
342344
Debug|Any CPU = Debug|Any CPU
@@ -359,6 +361,10 @@ Global
359361
{5493F024-0A3F-420C-AC2D-05B77A36025B}.Debug|Any CPU.Build.0 = Debug|Any CPU
360362
{5493F024-0A3F-420C-AC2D-05B77A36025B}.Release|Any CPU.ActiveCfg = Release|Any CPU
361363
{5493F024-0A3F-420C-AC2D-05B77A36025B}.Release|Any CPU.Build.0 = Release|Any CPU
364+
{5ED54794-99BF-5E50-A861-0BAAAC794E44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
365+
{5ED54794-99BF-5E50-A861-0BAAAC794E44}.Debug|Any CPU.Build.0 = Debug|Any CPU
366+
{5ED54794-99BF-5E50-A861-0BAAAC794E44}.Release|Any CPU.ActiveCfg = Release|Any CPU
367+
{5ED54794-99BF-5E50-A861-0BAAAC794E44}.Release|Any CPU.Build.0 = Release|Any CPU
362368
EndGlobalSection
363369
GlobalSection(SolutionProperties) = preSolution
364370
HideSolutionNode = FALSE
@@ -386,12 +392,14 @@ Global
386392
{68A8CC40-6AED-4E96-B524-31B1158FDEEA} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
387393
{5493F024-0A3F-420C-AC2D-05B77A36025B} = {528610AC-7C0C-46E8-9A2D-D46FD92FEE29}
388394
{23859314-5693-4E6C-BE5C-80A433439D2A} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D}
395+
{5ED54794-99BF-5E50-A861-0BAAAC794E44} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
389396
EndGlobalSection
390397
GlobalSection(ExtensibilityGlobals) = postSolution
391398
SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795}
392399
EndGlobalSection
393400
GlobalSection(SharedMSBuildProjectFiles) = preSolution
394401
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{2e33181e-6e28-4662-a801-e2e7dc206029}*SharedItemsImports = 5
402+
..\..\SixLabors\PolygonClipper\shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{5ed54794-99bf-5e50-a861-0baaac794e44}*SharedItemsImports = 5
395403
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{68a8cc40-6aed-4e96-b524-31b1158fdeea}*SharedItemsImports = 13
396404
EndGlobalSection
397405
GlobalSection(Performance) = preSolution

src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,8 @@
4747
<PackageReference Include="SixLabors.Fonts" Version="3.0.0-alpha.0.3" />
4848
<PackageReference Include="SixLabors.ImageSharp" Version="4.0.0-alpha.0.49" />
4949
</ItemGroup>
50+
<ItemGroup>
51+
<ProjectReference Include="..\..\..\..\SixLabors\PolygonClipper\src\PolygonClipper\PolygonClipper.csproj" />
52+
</ItemGroup>
5053
<Import Project="..\..\shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems" Label="Shared" />
5154
</Project>

src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawPathProcessor.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,20 @@ public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuratio
4848
// The global transform is applied in the FillPathProcessor.
4949
IPath outline = this.Pen.GeneratePath(this.Path.Transform(Matrix3x2.CreateTranslation(0.5F, 0.5F)));
5050

51-
return new FillPathProcessor(this.Options, this.Pen.StrokeFill, outline)
51+
DrawingOptions effectiveOptions = this.Options;
52+
53+
// Non-normalized stroked output can contain overlaps/self-intersections.
54+
// Rasterizing these contours with non-zero winding matches the intended stroke semantics.
55+
if (!this.Pen.StrokeOptions.NormalizeOutput &&
56+
this.Options.ShapeOptions.IntersectionRule != IntersectionRule.NonZero)
57+
{
58+
ShapeOptions shapeOptions = this.Options.ShapeOptions.DeepClone();
59+
shapeOptions.IntersectionRule = IntersectionRule.NonZero;
60+
61+
effectiveOptions = new DrawingOptions(this.Options.GraphicsOptions, shapeOptions, this.Options.Transform);
62+
}
63+
64+
return new FillPathProcessor(effectiveOptions, this.Pen.StrokeFill, outline)
5265
.CreatePixelSpecificProcessor(configuration, source, sourceRectangle);
5366
}
5467
}

src/ImageSharp.Drawing/Processing/StrokeOptions.cs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,32 @@ namespace SixLabors.ImageSharp.Drawing.Processing;
88
/// </summary>
99
public sealed class StrokeOptions : IEquatable<StrokeOptions?>
1010
{
11+
/// <summary>
12+
/// Gets or sets a value indicating whether stroked contours should be normalized by
13+
/// resolving self-intersections and overlaps before returning.
14+
/// </summary>
15+
/// <remarks>
16+
/// Defaults to <see langword="false"/> for maximum throughput.
17+
/// When disabled, callers should rasterize with a non-zero winding fill rule.
18+
/// </remarks>
19+
public bool NormalizeOutput { get; set; }
20+
1121
/// <summary>
1222
/// Gets or sets the miter limit used to clamp outer miter joins.
1323
/// </summary>
14-
public double MiterLimit { get; set; } = 4;
24+
public double MiterLimit { get; set; } = 4D;
1525

1626
/// <summary>
1727
/// Gets or sets the inner miter limit used to clamp joins on acute interior angles.
1828
/// </summary>
19-
public double InnerMiterLimit { get; set; } = 1.01;
29+
public double InnerMiterLimit { get; set; } = 1.01D;
2030

2131
/// <summary>
22-
/// Gets or sets the arc approximation scale used for round joins and caps.
32+
/// Gets or sets the tessellation detail scale for round joins and round caps.
33+
/// Higher values produce more vertices (smoother curves, more work).
34+
/// Lower values produce fewer vertices.
2335
/// </summary>
24-
public double ApproximationScale { get; set; } = 1.0;
36+
public double ArcDetailScale { get; set; } = 1D;
2537

2638
/// <summary>
2739
/// Gets or sets the outer line join style used for stroking corners.
@@ -44,19 +56,21 @@ public sealed class StrokeOptions : IEquatable<StrokeOptions?>
4456
/// <inheritdoc/>
4557
public bool Equals(StrokeOptions? other)
4658
=> other is not null &&
47-
this.MiterLimit == other.MiterLimit &&
48-
this.InnerMiterLimit == other.InnerMiterLimit &&
49-
this.ApproximationScale == other.ApproximationScale &&
50-
this.LineJoin == other.LineJoin &&
51-
this.LineCap == other.LineCap &&
52-
this.InnerJoin == other.InnerJoin;
59+
this.NormalizeOutput == other.NormalizeOutput &&
60+
this.MiterLimit == other.MiterLimit &&
61+
this.InnerMiterLimit == other.InnerMiterLimit &&
62+
this.ArcDetailScale == other.ArcDetailScale &&
63+
this.LineJoin == other.LineJoin &&
64+
this.LineCap == other.LineCap &&
65+
this.InnerJoin == other.InnerJoin;
5366

5467
/// <inheritdoc/>
5568
public override int GetHashCode()
5669
=> HashCode.Combine(
70+
this.NormalizeOutput,
5771
this.MiterLimit,
5872
this.InnerMiterLimit,
59-
this.ApproximationScale,
73+
this.ArcDetailScale,
6074
this.LineJoin,
6175
this.LineCap,
6276
this.InnerJoin);

src/ImageSharp.Drawing/Shapes/ClipPathExtensions.cs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ namespace SixLabors.ImageSharp.Drawing;
1111
/// </summary>
1212
public static class ClipPathExtensions
1313
{
14+
private static readonly ShapeOptions DefaultOptions = new();
15+
1416
/// <summary>
1517
/// Clips the specified subject path with the provided clipping paths.
1618
/// </summary>
1719
/// <param name="subjectPath">The subject path.</param>
1820
/// <param name="clipPaths">The clipping paths.</param>
1921
/// <returns>The clipped <see cref="IPath"/>.</returns>
2022
public static IPath Clip(this IPath subjectPath, params IPath[] clipPaths)
21-
=> subjectPath.Clip((IEnumerable<IPath>)clipPaths);
23+
=> subjectPath.Clip(DefaultOptions, clipPaths);
2224

2325
/// <summary>
2426
/// Clips the specified subject path with the provided clipping paths.
@@ -31,7 +33,7 @@ public static IPath Clip(
3133
this IPath subjectPath,
3234
ShapeOptions options,
3335
params IPath[] clipPaths)
34-
=> subjectPath.Clip(options, (IEnumerable<IPath>)clipPaths);
36+
=> ClippedShapeGenerator.GenerateClippedShapes(options.BooleanOperation, subjectPath, clipPaths);
3537

3638
/// <summary>
3739
/// Clips the specified subject path with the provided clipping paths.
@@ -40,7 +42,7 @@ public static IPath Clip(
4042
/// <param name="clipPaths">The clipping paths.</param>
4143
/// <returns>The clipped <see cref="IPath"/>.</returns>
4244
public static IPath Clip(this IPath subjectPath, IEnumerable<IPath> clipPaths)
43-
=> subjectPath.Clip(new ShapeOptions(), clipPaths);
45+
=> subjectPath.Clip(DefaultOptions, clipPaths);
4446

4547
/// <summary>
4648
/// Clips the specified subject path with the provided clipping paths.
@@ -53,14 +55,5 @@ public static IPath Clip(
5355
this IPath subjectPath,
5456
ShapeOptions options,
5557
IEnumerable<IPath> clipPaths)
56-
{
57-
ClippedShapeGenerator clipper = new(options.IntersectionRule);
58-
59-
clipper.AddPath(subjectPath, ClippingType.Subject);
60-
clipper.AddPaths(clipPaths, ClippingType.Clip);
61-
62-
IPath[] result = clipper.GenerateClippedShapes(options.BooleanOperation);
63-
64-
return new ComplexPolygon(result);
65-
}
58+
=> ClippedShapeGenerator.GenerateClippedShapes(options.BooleanOperation, subjectPath, clipPaths);
6659
}

src/ImageSharp.Drawing/Shapes/ComplexPolygon.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4-
using System.Buffers;
54
using System.Diagnostics.CodeAnalysis;
65
using System.Numerics;
76

@@ -34,7 +33,7 @@ public ComplexPolygon(PointF[] contour, PointF[] hole)
3433
/// </summary>
3534
/// <param name="paths">The paths.</param>
3635
public ComplexPolygon(IEnumerable<IPath> paths)
37-
: this(paths.ToArray())
36+
: this([.. paths])
3837
{
3938
}
4039

src/ImageSharp.Drawing/Shapes/OutlinePathExtensions.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@ namespace SixLabors.ImageSharp.Drawing;
1212
/// </summary>
1313
public static class OutlinePathExtensions
1414
{
15+
private static readonly StrokeOptions DefaultOptions = new();
16+
1517
/// <summary>
1618
/// Generates an outline of the path.
1719
/// </summary>
1820
/// <param name="path">The path to outline</param>
1921
/// <param name="width">The outline width.</param>
2022
/// <returns>A new <see cref="IPath"/> representing the outline.</returns>
2123
public static IPath GenerateOutline(this IPath path, float width)
22-
=> GenerateOutline(path, width, new StrokeOptions());
24+
=> GenerateOutline(path, width, DefaultOptions);
2325

2426
/// <summary>
2527
/// Generates an outline of the path.
@@ -35,8 +37,7 @@ public static IPath GenerateOutline(this IPath path, float width, StrokeOptions
3537
return Path.Empty;
3638
}
3739

38-
StrokedShapeGenerator generator = new(strokeOptions);
39-
return new ComplexPolygon(generator.GenerateStrokedShapes(path, width));
40+
return StrokedShapeGenerator.GenerateStrokedShapes(path, width, strokeOptions);
4041
}
4142

4243
/// <summary>
@@ -69,7 +70,7 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<f
6970
/// <param name="startOff">Whether the first item in the pattern is on or off.</param>
7071
/// <returns>A new <see cref="IPath"/> representing the outline.</returns>
7172
public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan<float> pattern, bool startOff)
72-
=> GenerateOutline(path, width, pattern, startOff, new StrokeOptions());
73+
=> GenerateOutline(path, width, pattern, startOff, DefaultOptions);
7374

7475
/// <summary>
7576
/// Generates an outline of the path with alternating on and off segments based on the pattern.
@@ -235,7 +236,6 @@ public static IPath GenerateOutline(
235236
}
236237

237238
// Each outline span is stroked as an open polyline; the union cleans overlaps.
238-
StrokedShapeGenerator generator = new(strokeOptions);
239-
return new ComplexPolygon(generator.GenerateStrokedShapes(outlines, width));
239+
return StrokedShapeGenerator.GenerateStrokedShapes(outlines, width, strokeOptions);
240240
}
241241
}

0 commit comments

Comments
 (0)