Skip to content

Commit 3b1b88d

Browse files
Handle degenerate inbound path.
1 parent 1006631 commit 3b1b88d

1 file changed

Lines changed: 66 additions & 0 deletions

File tree

src/ImageSharp.Drawing/Shapes/PolygonGeometry/PolygonStroker.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,33 @@ public double Width
118118
/// <param name="linePoints">The input points to stroke.</param>
119119
/// <param name="isClosed">Whether the input is a closed ring.</param>
120120
/// <returns>The stroked outline as a closed point array.</returns>
121+
/// <remarks>
122+
/// When a 2-point input contains identical points (degenerate case), this method generates
123+
/// a cap shape at that point: a circle for round caps or a square for square/butt caps.
124+
/// This ensures that even degenerate input produces visible output when stroked.
125+
/// </remarks>
121126
public PointF[] ProcessPath(ReadOnlySpan<PointF> linePoints, bool isClosed)
122127
{
123128
if (linePoints.Length < 2)
124129
{
125130
return [];
126131
}
127132

133+
// Special case: for 2-point inputs, check if both points are identical (degenerate case)
134+
// This avoids overhead for longer paths where the filtering logic handles near-duplicates
135+
if (linePoints.Length == 2)
136+
{
137+
PointF p0 = linePoints[0];
138+
PointF p1 = linePoints[1];
139+
140+
if (Math.Abs(p1.X - p0.X) <= Constants.Misc.VertexDistanceEpsilon &&
141+
Math.Abs(p1.Y - p0.Y) <= Constants.Misc.VertexDistanceEpsilon)
142+
{
143+
// Both points are identical - generate a point cap shape
144+
return this.GeneratePointCap(p0.X, p0.Y);
145+
}
146+
}
147+
128148
this.Reset();
129149
this.AddLinePath(linePoints);
130150

@@ -762,6 +782,52 @@ private void CalcJoin(ref VertexDistance v0, ref VertexDistance v1, ref VertexDi
762782
[MethodImpl(MethodImplOptions.AggressiveInlining)]
763783
private void AddPoint(double x, double y) => this.outVertices.Add(new PointF((float)x, (float)y));
764784

785+
/// <summary>
786+
/// Generates a cap shape for a degenerate point (when all input points are identical).
787+
/// Creates a circle for round caps or a square for square/butt caps.
788+
/// </summary>
789+
/// <param name="x">The X coordinate of the point.</param>
790+
/// <param name="y">The Y coordinate of the point.</param>
791+
/// <returns>The vertices forming the cap shape.</returns>
792+
private PointF[] GeneratePointCap(double x, double y)
793+
{
794+
if (this.LineCap == LineCap.Round)
795+
{
796+
// Generate a circle with radius = strokeWidth
797+
double da = Math.Acos(this.widthAbs / (this.widthAbs + (0.125 / this.ApproximationScale))) * 2;
798+
int n = Math.Max(4, (int)(Constants.Misc.PiMul2 / da));
799+
double angleStep = Constants.Misc.PiMul2 / n;
800+
801+
PointF[] points = new PointF[n + 1];
802+
803+
for (int i = 0; i < n; i++)
804+
{
805+
double angle = i * angleStep;
806+
points[i] = new PointF(
807+
(float)(x + (Math.Cos(angle) * this.strokeWidth)),
808+
(float)(y + (Math.Sin(angle) * this.strokeWidth)));
809+
}
810+
811+
// Close the circle
812+
points[n] = points[0];
813+
814+
return points;
815+
}
816+
else
817+
{
818+
// Generate a square cap (used for both Square and Butt caps)
819+
double w = this.strokeWidth;
820+
return
821+
[
822+
new PointF((float)(x - w), (float)(y - w)),
823+
new PointF((float)(x + w), (float)(y - w)),
824+
new PointF((float)(x + w), (float)(y + w)),
825+
new PointF((float)(x - w), (float)(y + w)),
826+
new PointF((float)(x - w), (float)(y - w)) // Close the square
827+
];
828+
}
829+
}
830+
765831
private enum Status
766832
{
767833
Initial,

0 commit comments

Comments
 (0)