From e84e9bbd1b357b6c1eaf65c8d1af855fadba4b7d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 24 Apr 2026 21:23:35 +1000 Subject: [PATCH 1/2] Remove InnerJoin and InnerMiterLimit support --- README.md | 1 - src/PolygonClipper/InnerJoin.cs | 30 -------- src/PolygonClipper/PolygonStroker.cs | 71 +------------------ src/PolygonClipper/StrokeOptions.cs | 18 +---- .../PolygonStrokerTests.cs | 3 +- 5 files changed, 5 insertions(+), 118 deletions(-) delete mode 100644 src/PolygonClipper/InnerJoin.cs diff --git a/README.md b/README.md index fbe434e..2e59dfd 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,6 @@ StrokeOptions options = new() LineJoin = LineJoin.Round, LineCap = LineCap.Round, MiterLimit = 4, - InnerMiterLimit = 1.01, ArcDetailScale = 1 }; diff --git a/src/PolygonClipper/InnerJoin.cs b/src/PolygonClipper/InnerJoin.cs deleted file mode 100644 index 0785f01..0000000 --- a/src/PolygonClipper/InnerJoin.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.PolygonClipper; - -/// -/// Specifies join styles for sharp interior angles. -/// -public enum InnerJoin -{ - /// - /// Use an interior miter join. - /// - Miter = 0, - - /// - /// Use an interior jag join. - /// - Jag = 1, - - /// - /// Use an interior round join. - /// - Round = 2, - - /// - /// Use an interior bevel join. - /// - Bevel = 3 -} diff --git a/src/PolygonClipper/PolygonStroker.cs b/src/PolygonClipper/PolygonStroker.cs index f210c35..8066bf0 100644 --- a/src/PolygonClipper/PolygonStroker.cs +++ b/src/PolygonClipper/PolygonStroker.cs @@ -79,10 +79,8 @@ public PolygonStroker(StrokeOptions options) ArgumentNullException.ThrowIfNull(options); this.NormalizeOutput = options.NormalizeOutput; this.LineJoin = options.LineJoin; - this.InnerJoin = options.InnerJoin; this.LineCap = options.LineCap; this.MiterLimit = options.MiterLimit; - this.InnerMiterLimit = options.InnerMiterLimit; this.ArcDetailScale = options.ArcDetailScale; } @@ -131,10 +129,8 @@ public StrokeOptionsKey(StrokeOptions options) { this.NormalizeOutput = options.NormalizeOutput; this.LineJoin = options.LineJoin; - this.InnerJoin = options.InnerJoin; this.LineCap = options.LineCap; this.MiterLimit = options.MiterLimit; - this.InnerMiterLimit = options.InnerMiterLimit; this.ArcDetailScale = options.ArcDetailScale; } @@ -142,23 +138,17 @@ public StrokeOptionsKey(StrokeOptions options) public LineJoin LineJoin { get; } - public InnerJoin InnerJoin { get; } - public LineCap LineCap { get; } public double MiterLimit { get; } - public double InnerMiterLimit { get; } - public double ArcDetailScale { get; } public bool Equals(StrokeOptionsKey other) => this.NormalizeOutput == other.NormalizeOutput && this.LineJoin == other.LineJoin && - this.InnerJoin == other.InnerJoin && this.LineCap == other.LineCap && this.MiterLimit == other.MiterLimit && - this.InnerMiterLimit == other.InnerMiterLimit && this.ArcDetailScale == other.ArcDetailScale; public override bool Equals(object? obj) => obj is StrokeOptionsKey other && this.Equals(other); @@ -167,10 +157,8 @@ public override int GetHashCode() => HashCode.Combine( this.NormalizeOutput, this.LineJoin, - this.InnerJoin, this.LineCap, this.MiterLimit, - this.InnerMiterLimit, this.ArcDetailScale); } @@ -266,11 +254,6 @@ public Polygon Stroke(Polygon polygon, double width) /// public double MiterLimit { get; } - /// - /// Gets the inner miter limit used to clamp joins on acute interior angles. - /// - public double InnerMiterLimit { get; } - /// /// Gets the tessellation detail scale used for round joins and round caps. /// Higher values produce more vertices and smoother curves. @@ -287,11 +270,6 @@ public Polygon Stroke(Polygon polygon, double width) /// public LineCap LineCap { get; } - /// - /// Gets the join style used for sharp interior angles. - /// - public InnerJoin InnerJoin { get; } - /// /// Gets a value indicating whether generated contours should be normalized by resolving /// self-intersections and overlaps. @@ -1122,53 +1100,8 @@ private void CalcJoin(ref StrokeVertexDistance v0, ref StrokeVertexDistance v1, double cp = Vertex.Cross(segNext, segForward); if (Math.Abs(cp) > double.Epsilon && (cp > 0D) == (strokeWidth > 0D)) { - double limit = Math.Min(len1, len2) / widthAbs; - if (limit < this.InnerMiterLimit) - { - limit = this.InnerMiterLimit; - } - - switch (this.InnerJoin) - { - default: - // Bevel-like fallback for inner corners. - this.AddPoint(v1.X + dx1, v1.Y - dy1); - this.AddPoint(v1.X + dx2, v1.Y - dy2); - break; - - case InnerJoin.Miter: - this.CalcMiter(ref v0, ref v1, ref v2, dx1, dy1, dx2, dy2, LineJoin.MiterRevert, limit, 0D); - break; - - case InnerJoin.Jag: - case InnerJoin.Round: - // If offsets are close enough, miter produces cleaner inner-corner output. - Vertex offset1 = new(dx1, dy1); - Vertex offset2 = new(dx2, dy2); - double offsetDeltaSquared = Vertex.DistanceSquared(offset1, offset2); - if (offsetDeltaSquared < len1 * len1 && offsetDeltaSquared < len2 * len2) - { - this.CalcMiter(ref v0, ref v1, ref v2, dx1, dy1, dx2, dy2, LineJoin.MiterRevert, limit, 0D); - } - else if (this.InnerJoin == InnerJoin.Jag) - { - // Jagged inner join inserts center vertex to preserve cusp. - this.AddPoint(v1.X + dx1, v1.Y - dy1); - this.AddPoint(v1.X, v1.Y); - this.AddPoint(v1.X + dx2, v1.Y - dy2); - } - else - { - // Rounded inner join bridges via arc passing through the corner. - this.AddPoint(v1.X + dx1, v1.Y - dy1); - this.AddPoint(v1.X, v1.Y); - this.CalcArc(v1.X, v1.Y, dx2, -dy2, dx1, -dy1); - this.AddPoint(v1.X, v1.Y); - this.AddPoint(v1.X + dx2, v1.Y - dy2); - } - - break; - } + this.AddPoint(v1.X + dx1, v1.Y - dy1); + this.AddPoint(v1.X + dx2, v1.Y - dy2); } else { diff --git a/src/PolygonClipper/StrokeOptions.cs b/src/PolygonClipper/StrokeOptions.cs index f9e5eb3..5034af8 100644 --- a/src/PolygonClipper/StrokeOptions.cs +++ b/src/PolygonClipper/StrokeOptions.cs @@ -23,11 +23,6 @@ public sealed class StrokeOptions : IEquatable /// public double MiterLimit { get; set; } = 4D; - /// - /// Gets or sets the inner miter limit used to clamp joins on acute interior angles. - /// - public double InnerMiterLimit { get; set; } = 1.01D; - /// /// Gets or sets the tessellation detail scale for round joins and round caps. /// Higher values produce more vertices (smoother curves, more work). @@ -45,11 +40,6 @@ public sealed class StrokeOptions : IEquatable /// public LineCap LineCap { get; set; } = LineCap.Butt; - /// - /// Gets or sets the join style used for sharp interior angles. - /// - public InnerJoin InnerJoin { get; set; } = InnerJoin.Miter; - /// public override bool Equals(object? obj) => this.Equals(obj as StrokeOptions); @@ -58,20 +48,16 @@ public bool Equals(StrokeOptions? other) => other is not null && this.NormalizeOutput == other.NormalizeOutput && this.MiterLimit == other.MiterLimit && - this.InnerMiterLimit == other.InnerMiterLimit && this.ArcDetailScale == other.ArcDetailScale && this.LineJoin == other.LineJoin && - this.LineCap == other.LineCap && - this.InnerJoin == other.InnerJoin; + this.LineCap == other.LineCap; /// public override int GetHashCode() => HashCode.Combine( this.NormalizeOutput, this.MiterLimit, - this.InnerMiterLimit, this.ArcDetailScale, this.LineJoin, - this.LineCap, - this.InnerJoin); + this.LineCap); } diff --git a/tests/PolygonClipper.Tests/PolygonStrokerTests.cs b/tests/PolygonClipper.Tests/PolygonStrokerTests.cs index 64d6172..01437f1 100644 --- a/tests/PolygonClipper.Tests/PolygonStrokerTests.cs +++ b/tests/PolygonClipper.Tests/PolygonStrokerTests.cs @@ -68,8 +68,7 @@ public void Stroke_OpenPolylineWithThreeVertices_IsNotForcedClosed() StrokeOptions options = new() { LineCap = LineCap.Butt, - LineJoin = LineJoin.Miter, - InnerJoin = InnerJoin.Miter + LineJoin = LineJoin.Miter }; Polygon open = From 517a35dde252953e581002d0985468673dc3e254 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 24 Apr 2026 21:44:30 +1000 Subject: [PATCH 2/2] Default should be mitre --- src/PolygonClipper/PolygonStroker.cs | 12 ++++++++++-- tests/PolygonClipper.Tests/PolygonStrokerTests.cs | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/PolygonClipper/PolygonStroker.cs b/src/PolygonClipper/PolygonStroker.cs index 8066bf0..8b57061 100644 --- a/src/PolygonClipper/PolygonStroker.cs +++ b/src/PolygonClipper/PolygonStroker.cs @@ -36,6 +36,9 @@ public sealed class PolygonStroker private const double Pi = Math.PI; private const double PiMul2 = Math.PI * 2D; + // The inner miter limit used to clamp joins on acute interior angles. + private const double InnerMiterLimit = 1.01D; + // Keep at most 2 warm instances per option-set (one active shape and one spare) // to reduce churn without retaining many rarely reused configurations. private const int MaxPooledStrokersPerOptions = 2; @@ -1100,8 +1103,13 @@ private void CalcJoin(ref StrokeVertexDistance v0, ref StrokeVertexDistance v1, double cp = Vertex.Cross(segNext, segForward); if (Math.Abs(cp) > double.Epsilon && (cp > 0D) == (strokeWidth > 0D)) { - this.AddPoint(v1.X + dx1, v1.Y - dy1); - this.AddPoint(v1.X + dx2, v1.Y - dy2); + double limit = Math.Min(len1, len2) / widthAbs; + if (limit < InnerMiterLimit) + { + limit = InnerMiterLimit; + } + + this.CalcMiter(ref v0, ref v1, ref v2, dx1, dy1, dx2, dy2, LineJoin.MiterRevert, limit, 0D); } else { diff --git a/tests/PolygonClipper.Tests/PolygonStrokerTests.cs b/tests/PolygonClipper.Tests/PolygonStrokerTests.cs index 01437f1..c4d8ce3 100644 --- a/tests/PolygonClipper.Tests/PolygonStrokerTests.cs +++ b/tests/PolygonClipper.Tests/PolygonStrokerTests.cs @@ -137,7 +137,7 @@ public void ProcessPolygonAndClip_FigureNinePath_ProducesValidStroke() AssertStrokeCoversInputCenterline(input, actual, samplesPerSegment: 3); } - [Theory (Skip = "For profiling only.")] + [Theory(Skip = "For profiling only.")] [InlineData(101)] [InlineData(301)] [InlineData(1001)]