11// Copyright (c) Six Labors.
22// Licensed under the Six Labors Split License.
33
4- using System . Numerics ;
54using SixLabors . PolygonClipper ;
65
76using PCPolygon = SixLabors . PolygonClipper . Polygon ;
@@ -14,329 +13,6 @@ namespace SixLabors.ImageSharp.Drawing.PolygonGeometry;
1413/// </summary>
1514internal 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