|
1 | 1 | // Copyright (c) Six Labors. |
2 | 2 | // Licensed under the Six Labors Split License. |
3 | 3 |
|
4 | | -using System.Numerics; |
5 | 4 | using SixLabors.ImageSharp.Drawing.PolygonGeometry; |
6 | 5 | using SixLabors.ImageSharp.Drawing.Processing; |
7 | 6 |
|
@@ -98,144 +97,22 @@ public static IPath GenerateOutline( |
98 | 97 | return path.GenerateOutline(width, strokeOptions); |
99 | 98 | } |
100 | 99 |
|
101 | | - const float eps = 1e-6f; |
102 | | - const int maxPatternSegments = 10000; |
| 100 | + IPath dashed = path.GenerateDashes(width, pattern, startOff); |
103 | 101 |
|
104 | | - // Compute the absolute pattern length in path units to detect degenerate patterns. |
105 | | - float patternLength = 0f; |
106 | | - for (int i = 0; i < pattern.Length; i++) |
107 | | - { |
108 | | - patternLength += MathF.Abs(pattern[i]) * width; |
109 | | - } |
110 | | - |
111 | | - // Fallback to a solid outline when the dash pattern is too small to be meaningful. |
112 | | - if (patternLength <= eps) |
| 102 | + // GenerateDashes returns the original path when the pattern is degenerate |
| 103 | + // or when segmentation would exceed safety limits; stroke it as solid. |
| 104 | + if (ReferenceEquals(dashed, path)) |
113 | 105 | { |
114 | 106 | return path.GenerateOutline(width, strokeOptions); |
115 | 107 | } |
116 | 108 |
|
117 | | - IEnumerable<ISimplePath> paths = path.Flatten(); |
118 | | - |
119 | | - List<PointF[]> outlines = []; |
120 | | - List<PointF> buffer = new(64); // arbitrary initial capacity hint. |
121 | | - |
122 | | - foreach (ISimplePath p in paths) |
| 109 | + if (dashed == Path.Empty) |
123 | 110 | { |
124 | | - bool online = !startOff; |
125 | | - int patternPos = 0; |
126 | | - float targetLength = pattern[patternPos] * width; |
127 | | - |
128 | | - ReadOnlySpan<PointF> pts = p.Points.Span; |
129 | | - if (pts.Length < 2) |
130 | | - { |
131 | | - continue; |
132 | | - } |
133 | | - |
134 | | - // number of edges to traverse (no wrap for open paths) |
135 | | - int edgeCount = p.IsClosed ? pts.Length : pts.Length - 1; |
136 | | - float totalLength = 0f; |
137 | | - |
138 | | - // Compute total path length to estimate the number of dash segments to produce. |
139 | | - for (int j = 0; j < edgeCount; j++) |
140 | | - { |
141 | | - int nextIndex = p.IsClosed ? (j + 1) % pts.Length : j + 1; |
142 | | - totalLength += Vector2.Distance(pts[j], pts[nextIndex]); |
143 | | - } |
144 | | - |
145 | | - if (totalLength > eps) |
146 | | - { |
147 | | - // Avoid runaway segmentation by falling back when the dash count explodes. |
148 | | - float estimatedSegments = (totalLength / patternLength) * pattern.Length; |
149 | | - if (estimatedSegments > maxPatternSegments) |
150 | | - { |
151 | | - return path.GenerateOutline(width, strokeOptions); |
152 | | - } |
153 | | - } |
154 | | - |
155 | | - int i = 0; |
156 | | - Vector2 current = pts[0]; |
157 | | - |
158 | | - while (i < edgeCount) |
159 | | - { |
160 | | - int nextIndex = p.IsClosed ? (i + 1) % pts.Length : i + 1; |
161 | | - Vector2 next = pts[nextIndex]; |
162 | | - float segLen = Vector2.Distance(current, next); |
163 | | - |
164 | | - // Skip degenerate segments. |
165 | | - if (segLen <= eps) |
166 | | - { |
167 | | - current = next; |
168 | | - i++; |
169 | | - continue; |
170 | | - } |
171 | | - |
172 | | - // Accumulate into the current dash span when the segment is shorter than the target. |
173 | | - if (segLen + eps < targetLength) |
174 | | - { |
175 | | - buffer.Add(current); |
176 | | - current = next; |
177 | | - i++; |
178 | | - targetLength -= segLen; |
179 | | - continue; |
180 | | - } |
181 | | - |
182 | | - // Close out a dash span when the segment length matches the target length. |
183 | | - if (MathF.Abs(segLen - targetLength) <= eps) |
184 | | - { |
185 | | - buffer.Add(current); |
186 | | - buffer.Add(next); |
187 | | - |
188 | | - if (online && buffer.Count >= 2 && buffer[0] != buffer[^1]) |
189 | | - { |
190 | | - outlines.Add([.. buffer]); |
191 | | - } |
192 | | - |
193 | | - buffer.Clear(); |
194 | | - online = !online; |
195 | | - |
196 | | - current = next; |
197 | | - i++; |
198 | | - patternPos = (patternPos + 1) % pattern.Length; |
199 | | - targetLength = pattern[patternPos] * width; |
200 | | - continue; |
201 | | - } |
202 | | - |
203 | | - // Split inside this segment to end the current dash span. |
204 | | - float t = targetLength / segLen; // 0 < t < 1 here |
205 | | - Vector2 split = current + (t * (next - current)); |
206 | | - |
207 | | - buffer.Add(current); |
208 | | - buffer.Add(split); |
209 | | - |
210 | | - if (online && buffer.Count >= 2 && buffer[0] != buffer[^1]) |
211 | | - { |
212 | | - outlines.Add([.. buffer]); |
213 | | - } |
214 | | - |
215 | | - buffer.Clear(); |
216 | | - online = !online; |
217 | | - |
218 | | - current = split; // continue along the same geometric segment |
219 | | - |
220 | | - patternPos = (patternPos + 1) % pattern.Length; |
221 | | - targetLength = pattern[patternPos] * width; |
222 | | - } |
223 | | - |
224 | | - // flush tail of the last dash span, if any |
225 | | - if (buffer.Count > 0) |
226 | | - { |
227 | | - buffer.Add(current); // terminate at the true end position |
228 | | - |
229 | | - if (online && buffer.Count >= 2 && buffer[0] != buffer[^1]) |
230 | | - { |
231 | | - outlines.Add([.. buffer]); |
232 | | - } |
233 | | - |
234 | | - buffer.Clear(); |
235 | | - } |
| 111 | + return Path.Empty; |
236 | 112 | } |
237 | 113 |
|
238 | | - // Each outline span is stroked as an open polyline; the union cleans overlaps. |
239 | | - return StrokedShapeGenerator.GenerateStrokedShapes(outlines, width, strokeOptions); |
| 114 | + // Each dash segment is an open sub-path; stroke expansion and boolean merge |
| 115 | + // are handled by the generator. |
| 116 | + return StrokedShapeGenerator.GenerateStrokedShapes(dashed, width, strokeOptions); |
240 | 117 | } |
241 | 118 | } |
0 commit comments