Skip to content

Commit 2f0a7f6

Browse files
Move stroking to the GPU
1 parent b04a0ec commit 2f0a7f6

69 files changed

Lines changed: 1516 additions & 671 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Numerics;
5+
6+
namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
7+
8+
/// <summary>
9+
/// Splits a path into dash segments without performing stroke expansion.
10+
/// Each "on" dash segment is returned as an open sub-path.
11+
/// </summary>
12+
internal static class DashPathSplitter
13+
{
14+
/// <summary>
15+
/// Splits the given path into dash segments based on the provided pattern.
16+
/// Returns a composite path containing only the "on" segments as open sub-paths.
17+
/// </summary>
18+
/// <param name="path">The centerline path to split.</param>
19+
/// <param name="strokeWidth">The stroke width (pattern elements are multiples of this).</param>
20+
/// <param name="pattern">The dash pattern. Each element is a multiple of <paramref name="strokeWidth"/>.</param>
21+
/// <returns>A path containing the "on" dash segments.</returns>
22+
public static IPath SplitDashes(IPath path, float strokeWidth, ReadOnlySpan<float> pattern)
23+
{
24+
if (pattern.Length < 2)
25+
{
26+
return path;
27+
}
28+
29+
const float eps = 1e-6f;
30+
31+
float patternLength = 0f;
32+
for (int i = 0; i < pattern.Length; i++)
33+
{
34+
patternLength += MathF.Abs(pattern[i]) * strokeWidth;
35+
}
36+
37+
if (patternLength <= eps)
38+
{
39+
return path;
40+
}
41+
42+
IEnumerable<ISimplePath> simplePaths = path.Flatten();
43+
List<IPath> segments = [];
44+
List<PointF> buffer = new(64);
45+
46+
foreach (ISimplePath p in simplePaths)
47+
{
48+
bool online = true;
49+
int patternPos = 0;
50+
float targetLength = pattern[patternPos] * strokeWidth;
51+
52+
ReadOnlySpan<PointF> pts = p.Points.Span;
53+
if (pts.Length < 2)
54+
{
55+
continue;
56+
}
57+
58+
int edgeCount = p.IsClosed ? pts.Length : pts.Length - 1;
59+
int ei = 0;
60+
Vector2 current = pts[0];
61+
62+
while (ei < edgeCount)
63+
{
64+
int nextIndex = p.IsClosed ? (ei + 1) % pts.Length : ei + 1;
65+
Vector2 next = pts[nextIndex];
66+
float segLen = Vector2.Distance(current, next);
67+
68+
if (segLen <= eps)
69+
{
70+
current = next;
71+
ei++;
72+
continue;
73+
}
74+
75+
if (segLen + eps < targetLength)
76+
{
77+
if (online)
78+
{
79+
buffer.Add(current);
80+
}
81+
82+
current = next;
83+
ei++;
84+
targetLength -= segLen;
85+
continue;
86+
}
87+
88+
if (MathF.Abs(segLen - targetLength) <= eps)
89+
{
90+
if (online)
91+
{
92+
buffer.Add(current);
93+
buffer.Add(next);
94+
FlushBuffer(buffer, segments);
95+
}
96+
97+
buffer.Clear();
98+
online = !online;
99+
current = next;
100+
ei++;
101+
patternPos = (patternPos + 1) % pattern.Length;
102+
targetLength = pattern[patternPos] * strokeWidth;
103+
continue;
104+
}
105+
106+
float t = targetLength / segLen;
107+
Vector2 split = current + (t * (next - current));
108+
109+
if (online)
110+
{
111+
buffer.Add(current);
112+
buffer.Add(split);
113+
FlushBuffer(buffer, segments);
114+
}
115+
116+
buffer.Clear();
117+
online = !online;
118+
current = split;
119+
patternPos = (patternPos + 1) % pattern.Length;
120+
targetLength = pattern[patternPos] * strokeWidth;
121+
}
122+
123+
if (buffer.Count > 0)
124+
{
125+
if (online)
126+
{
127+
buffer.Add(current);
128+
FlushBuffer(buffer, segments);
129+
}
130+
131+
buffer.Clear();
132+
}
133+
}
134+
135+
if (segments.Count == 0)
136+
{
137+
return Path.Empty;
138+
}
139+
140+
if (segments.Count == 1)
141+
{
142+
return segments[0];
143+
}
144+
145+
return new ComplexPolygon(segments);
146+
}
147+
148+
private static void FlushBuffer(List<PointF> buffer, List<IPath> segments)
149+
{
150+
if (buffer.Count >= 2 && buffer[0] != buffer[^1])
151+
{
152+
segments.Add(new Path(new LinearLineSegment([.. buffer])));
153+
}
154+
}
155+
}

0 commit comments

Comments
 (0)