Skip to content

Commit cc01945

Browse files
Improve coverage a little
1 parent 0110ef5 commit cc01945

File tree

5 files changed

+375
-367
lines changed

5 files changed

+375
-367
lines changed

src/ImageSharp.Drawing/PolygonGeometry/StrokedShapeGenerator.cs

Lines changed: 0 additions & 324 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.Numerics;
54
using SixLabors.PolygonClipper;
65

76
using PCPolygon = SixLabors.PolygonClipper.Polygon;
@@ -14,329 +13,6 @@ namespace SixLabors.ImageSharp.Drawing.PolygonGeometry;
1413
/// </summary>
1514
internal static class StrokedShapeGenerator
1615
{
17-
/// <summary>
18-
/// Strokes a path and returns retained linear geometry for the merged outline.
19-
/// </summary>
20-
/// <param name="path">The source path. It is flattened using the current flattening settings.</param>
21-
/// <param name="width">The stroke width in the caller's coordinate space.</param>
22-
/// <param name="options">The stroke geometry options.</param>
23-
/// <returns>The stroked outline as retained linear geometry.</returns>
24-
public static LinearGeometry GenerateStrokedGeometry(IPath path, float width, StrokeOptions options)
25-
{
26-
PCPolygon rings = [];
27-
28-
foreach (ISimplePath sp in path.Flatten())
29-
{
30-
ReadOnlySpan<PointF> span = sp.Points.Span;
31-
if (span.Length < 2)
32-
{
33-
continue;
34-
}
35-
36-
Contour ring = new(span.Length);
37-
for (int i = 0; i < span.Length; i++)
38-
{
39-
PointF p = span[i];
40-
ring.Add(new Vertex(p.X, p.Y));
41-
}
42-
43-
if (sp.IsClosed)
44-
{
45-
ring.Add(ring[0]);
46-
}
47-
48-
rings.Add(ring);
49-
}
50-
51-
if (rings.Count == 0)
52-
{
53-
return new LinearGeometry(
54-
new LinearGeometryInfo
55-
{
56-
Bounds = RectangleF.Empty,
57-
ContourCount = 0,
58-
PointCount = 0,
59-
SegmentCount = 0,
60-
NonHorizontalSegmentCountPixelBoundary = 0,
61-
NonHorizontalSegmentCountPixelCenter = 0
62-
},
63-
[],
64-
[]);
65-
}
66-
67-
PCPolygon result = PolygonStroker.Stroke(rings, width, CreateStrokeOptions(options));
68-
if (result.Count == 0)
69-
{
70-
return new LinearGeometry(
71-
new LinearGeometryInfo
72-
{
73-
Bounds = RectangleF.Empty,
74-
ContourCount = 0,
75-
PointCount = 0,
76-
SegmentCount = 0,
77-
NonHorizontalSegmentCountPixelBoundary = 0,
78-
NonHorizontalSegmentCountPixelCenter = 0
79-
},
80-
[],
81-
[]);
82-
}
83-
84-
int pointCount = 0;
85-
for (int i = 0; i < result.Count; i++)
86-
{
87-
pointCount += result[i].Count;
88-
}
89-
90-
PointF[] points = new PointF[pointCount];
91-
LinearContour[] contours = new LinearContour[result.Count];
92-
int pointStart = 0;
93-
int outputContourIndex = 0;
94-
int nonHorizontalBoundary = 0;
95-
int nonHorizontalCenter = 0;
96-
float minX = float.MaxValue;
97-
float minY = float.MaxValue;
98-
float maxX = float.MinValue;
99-
float maxY = float.MinValue;
100-
101-
for (int i = 0; i < result.Count; i++)
102-
{
103-
Contour contour = result[i];
104-
int contourPointCount = contour.Count;
105-
106-
Vertex v0 = contour[0];
107-
PointF p0 = new((float)v0.X, (float)v0.Y);
108-
points[pointStart] = p0;
109-
minX = MathF.Min(minX, p0.X);
110-
minY = MathF.Min(minY, p0.Y);
111-
maxX = MathF.Max(maxX, p0.X);
112-
maxY = MathF.Max(maxY, p0.Y);
113-
114-
for (int j = 1; j < contourPointCount; j++)
115-
{
116-
Vertex vertex = contour[j];
117-
PointF p = new((float)vertex.X, (float)vertex.Y);
118-
points[pointStart + j] = p;
119-
minX = MathF.Min(minX, p.X);
120-
minY = MathF.Min(minY, p.Y);
121-
maxX = MathF.Max(maxX, p.X);
122-
maxY = MathF.Max(maxY, p.Y);
123-
124-
float prevY = points[pointStart + j - 1].Y;
125-
if ((int)MathF.Floor(prevY) != (int)MathF.Floor(p.Y))
126-
{
127-
nonHorizontalBoundary++;
128-
}
129-
130-
if ((int)MathF.Floor(prevY + 0.5F) != (int)MathF.Floor(p.Y + 0.5F))
131-
{
132-
nonHorizontalCenter++;
133-
}
134-
}
135-
136-
float lastY = points[pointStart + contourPointCount - 1].Y;
137-
float firstY = points[pointStart].Y;
138-
if ((int)MathF.Floor(lastY) != (int)MathF.Floor(firstY))
139-
{
140-
nonHorizontalBoundary++;
141-
}
142-
143-
if ((int)MathF.Floor(lastY + 0.5F) != (int)MathF.Floor(firstY + 0.5F))
144-
{
145-
nonHorizontalCenter++;
146-
}
147-
148-
contours[outputContourIndex++] = new LinearContour
149-
{
150-
PointStart = pointStart,
151-
PointCount = contourPointCount,
152-
SegmentStart = pointStart,
153-
SegmentCount = contourPointCount,
154-
IsClosed = true
155-
};
156-
157-
pointStart += contourPointCount;
158-
}
159-
160-
if (outputContourIndex != contours.Length)
161-
{
162-
Array.Resize(ref contours, outputContourIndex);
163-
}
164-
165-
return new LinearGeometry(
166-
new LinearGeometryInfo
167-
{
168-
Bounds = RectangleF.FromLTRB(minX, minY, maxX, maxY),
169-
ContourCount = contours.Length,
170-
PointCount = points.Length,
171-
SegmentCount = pointCount,
172-
NonHorizontalSegmentCountPixelBoundary = nonHorizontalBoundary,
173-
NonHorizontalSegmentCountPixelCenter = nonHorizontalCenter
174-
},
175-
contours,
176-
points);
177-
}
178-
179-
/// <summary>
180-
/// Strokes a path and returns a merged outline with the specified projective transform
181-
/// applied to each output point during geometry construction.
182-
/// </summary>
183-
public static LinearGeometry GenerateStrokedGeometry(IPath path, float width, StrokeOptions options, Matrix4x4 transform)
184-
{
185-
PCPolygon rings = [];
186-
187-
foreach (ISimplePath sp in path.Flatten())
188-
{
189-
ReadOnlySpan<PointF> span = sp.Points.Span;
190-
if (span.Length < 2)
191-
{
192-
continue;
193-
}
194-
195-
Contour ring = new(span.Length);
196-
for (int i = 0; i < span.Length; i++)
197-
{
198-
PointF p = span[i];
199-
ring.Add(new Vertex(p.X, p.Y));
200-
}
201-
202-
if (sp.IsClosed)
203-
{
204-
ring.Add(ring[0]);
205-
}
206-
207-
rings.Add(ring);
208-
}
209-
210-
if (rings.Count == 0)
211-
{
212-
return new LinearGeometry(
213-
new LinearGeometryInfo
214-
{
215-
Bounds = RectangleF.Empty,
216-
ContourCount = 0,
217-
PointCount = 0,
218-
SegmentCount = 0,
219-
NonHorizontalSegmentCountPixelBoundary = 0,
220-
NonHorizontalSegmentCountPixelCenter = 0
221-
},
222-
[],
223-
[]);
224-
}
225-
226-
PCPolygon result = PolygonStroker.Stroke(rings, width, CreateStrokeOptions(options));
227-
if (result.Count == 0)
228-
{
229-
return new LinearGeometry(
230-
new LinearGeometryInfo
231-
{
232-
Bounds = RectangleF.Empty,
233-
ContourCount = 0,
234-
PointCount = 0,
235-
SegmentCount = 0,
236-
NonHorizontalSegmentCountPixelBoundary = 0,
237-
NonHorizontalSegmentCountPixelCenter = 0
238-
},
239-
[],
240-
[]);
241-
}
242-
243-
// First pass: count points only (no transform).
244-
int pointCount = 0;
245-
for (int i = 0; i < result.Count; i++)
246-
{
247-
pointCount += result[i].Count;
248-
}
249-
250-
PointF[] points = new PointF[pointCount];
251-
LinearContour[] contours = new LinearContour[result.Count];
252-
int pointStart = 0;
253-
int outputContourIndex = 0;
254-
int nonHorizontalBoundary = 0;
255-
int nonHorizontalCenter = 0;
256-
float minX = float.MaxValue;
257-
float minY = float.MaxValue;
258-
float maxX = float.MinValue;
259-
float maxY = float.MinValue;
260-
261-
// Second pass: transform, write, bounds, and non-horizontal counts in one loop.
262-
for (int i = 0; i < result.Count; i++)
263-
{
264-
Contour contour = result[i];
265-
int contourPointCount = contour.Count;
266-
267-
Vertex v0 = contour[0];
268-
PointF p0 = PointF.Transform(new PointF((float)v0.X, (float)v0.Y), transform);
269-
points[pointStart] = p0;
270-
minX = MathF.Min(minX, p0.X);
271-
minY = MathF.Min(minY, p0.Y);
272-
maxX = MathF.Max(maxX, p0.X);
273-
maxY = MathF.Max(maxY, p0.Y);
274-
275-
for (int j = 1; j < contourPointCount; j++)
276-
{
277-
Vertex vertex = contour[j];
278-
PointF p = PointF.Transform(new PointF((float)vertex.X, (float)vertex.Y), transform);
279-
points[pointStart + j] = p;
280-
minX = MathF.Min(minX, p.X);
281-
minY = MathF.Min(minY, p.Y);
282-
maxX = MathF.Max(maxX, p.X);
283-
maxY = MathF.Max(maxY, p.Y);
284-
285-
float prevY = points[pointStart + j - 1].Y;
286-
if ((int)MathF.Floor(prevY) != (int)MathF.Floor(p.Y))
287-
{
288-
nonHorizontalBoundary++;
289-
}
290-
291-
if ((int)MathF.Floor(prevY + 0.5F) != (int)MathF.Floor(p.Y + 0.5F))
292-
{
293-
nonHorizontalCenter++;
294-
}
295-
}
296-
297-
float lastY = points[pointStart + contourPointCount - 1].Y;
298-
float firstY = points[pointStart].Y;
299-
if ((int)MathF.Floor(lastY) != (int)MathF.Floor(firstY))
300-
{
301-
nonHorizontalBoundary++;
302-
}
303-
304-
if ((int)MathF.Floor(lastY + 0.5F) != (int)MathF.Floor(firstY + 0.5F))
305-
{
306-
nonHorizontalCenter++;
307-
}
308-
309-
contours[outputContourIndex++] = new LinearContour
310-
{
311-
PointStart = pointStart,
312-
PointCount = contourPointCount,
313-
SegmentStart = pointStart,
314-
SegmentCount = contourPointCount,
315-
IsClosed = true
316-
};
317-
318-
pointStart += contourPointCount;
319-
}
320-
321-
if (outputContourIndex != contours.Length)
322-
{
323-
Array.Resize(ref contours, outputContourIndex);
324-
}
325-
326-
return new LinearGeometry(
327-
new LinearGeometryInfo
328-
{
329-
Bounds = RectangleF.FromLTRB(minX, minY, maxX, maxY),
330-
ContourCount = contours.Length,
331-
PointCount = points.Length,
332-
SegmentCount = pointCount,
333-
NonHorizontalSegmentCountPixelBoundary = nonHorizontalBoundary,
334-
NonHorizontalSegmentCountPixelCenter = nonHorizontalCenter
335-
},
336-
contours,
337-
points);
338-
}
339-
34016
/// <summary>
34117
/// Strokes a path and returns a merged outline from its flattened segments.
34218
/// </summary>

0 commit comments

Comments
 (0)