Skip to content

Commit eab9402

Browse files
Implement retained stroke rasterization and stroke commands
1 parent 4ebf33d commit eab9402

20 files changed

+2172
-4079
lines changed

ImageSharp.Drawing.sln

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebGPUWindowDemo", "samples
346346
EndProject
347347
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Drawing.WebGPU.ShaderGen", "src\ImageSharp.Drawing.WebGPU.ShaderGen\ImageSharp.Drawing.WebGPU.ShaderGen.csproj", "{C7606104-5D58-4670-912C-3F336606B02D}"
348348
EndProject
349-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp", "..\ImageSharp\src\ImageSharp\ImageSharp.csproj", "{FE71764E-E7BD-B378-8A4C-86929A28AD25}"
350-
EndProject
351349
Global
352350
GlobalSection(SolutionConfigurationPlatforms) = preSolution
353351
Debug|Any CPU = Debug|Any CPU
@@ -454,18 +452,6 @@ Global
454452
{C7606104-5D58-4670-912C-3F336606B02D}.Release|x64.Build.0 = Release|Any CPU
455453
{C7606104-5D58-4670-912C-3F336606B02D}.Release|x86.ActiveCfg = Release|Any CPU
456454
{C7606104-5D58-4670-912C-3F336606B02D}.Release|x86.Build.0 = Release|Any CPU
457-
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
458-
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|Any CPU.Build.0 = Debug|Any CPU
459-
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|x64.ActiveCfg = Debug|Any CPU
460-
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|x64.Build.0 = Debug|Any CPU
461-
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|x86.ActiveCfg = Debug|Any CPU
462-
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Debug|x86.Build.0 = Debug|Any CPU
463-
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|Any CPU.ActiveCfg = Release|Any CPU
464-
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|Any CPU.Build.0 = Release|Any CPU
465-
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|x64.ActiveCfg = Release|Any CPU
466-
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|x64.Build.0 = Release|Any CPU
467-
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|x86.ActiveCfg = Release|Any CPU
468-
{FE71764E-E7BD-B378-8A4C-86929A28AD25}.Release|x86.Build.0 = Release|Any CPU
469455
EndGlobalSection
470456
GlobalSection(SolutionProperties) = preSolution
471457
HideSolutionNode = FALSE
@@ -497,7 +483,6 @@ Global
497483
{061582C2-658F-40AE-A978-7D74A4EB2C0A} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
498484
{2541FDCD-78AC-40DB-B5E3-6A715DC132BA} = {528610AC-7C0C-46E8-9A2D-D46FD92FEE29}
499485
{C7606104-5D58-4670-912C-3F336606B02D} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
500-
{FE71764E-E7BD-B378-8A4C-86929A28AD25} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
501486
EndGlobalSection
502487
GlobalSection(ExtensibilityGlobals) = postSolution
503488
SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795}
@@ -506,7 +491,6 @@ Global
506491
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{061582c2-658f-40ae-a978-7d74a4eb2c0a}*SharedItemsImports = 5
507492
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{2e33181e-6e28-4662-a801-e2e7dc206029}*SharedItemsImports = 5
508493
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{68a8cc40-6aed-4e96-b524-31b1158fdeea}*SharedItemsImports = 13
509-
..\ImageSharp\shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{fe71764e-e7bd-b378-8a4c-86929a28ad25}*SharedItemsImports = 5
510494
EndGlobalSection
511495
GlobalSection(Performance) = preSolution
512496
HasPerformanceSessions = true

samples/WebGPUWindowDemo/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public static unsafe class Program
2828
{
2929
private const int WindowWidth = 800;
3030
private const int WindowHeight = 600;
31-
private const int BallCount = 500;
31+
private const int BallCount = 1000;
3232

3333
// Silk.NET WebGPU API and windowing handles.
3434
private static WebGPU wgpu;

src/ImageSharp.Drawing.WebGPU/WebGPUSceneEncoder.cs

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,13 @@ internal bool TryBuild(CompositionScene scene, out string? error)
273273
return false;
274274
}
275275
}
276+
else if (command is StrokePathCompositionSceneCommand strokePathCommand)
277+
{
278+
if (!this.TryAppend(strokePathCommand.Command, out error))
279+
{
280+
return false;
281+
}
282+
}
276283
else if (command is LineSegmentCompositionSceneCommand lineSegmentCommand)
277284
{
278285
if (!this.TryAppend(lineSegmentCommand.Command, out error))
@@ -294,7 +301,7 @@ internal bool TryBuild(CompositionScene scene, out string? error)
294301
}
295302

296303
/// <summary>
297-
/// Appends one prepared path- or layer-based command to the scene streams when the command kind is supported.
304+
/// Appends one prepared fill-path or layer-based command to the scene streams when the command kind is supported.
298305
/// </summary>
299306
private bool TryAppend(in CompositionCommand command, out string? error)
300307
{
@@ -314,13 +321,6 @@ private bool TryAppend(in CompositionCommand command, out string? error)
314321

315322
this.AppendTransformIfChanged(command.Transform);
316323

317-
if (command.Pen is Pen pen)
318-
{
319-
this.AppendPlainStroke(resolved, pen);
320-
error = null;
321-
return true;
322-
}
323-
324324
this.AppendPlainFill(resolved);
325325
error = null;
326326
return true;
@@ -340,6 +340,28 @@ private bool TryAppend(in CompositionCommand command, out string? error)
340340
return true;
341341
}
342342

343+
/// <summary>
344+
/// Appends one prepared stroked path command to the scene streams.
345+
/// </summary>
346+
private bool TryAppend(in StrokePathCommand command, out string? error)
347+
{
348+
if (!TryResolveCommand(command, out ResolvedPathCommand resolved))
349+
{
350+
error = null;
351+
return true;
352+
}
353+
354+
if (!this.TryRegisterVisibleFill(resolved.Brush, resolved.RasterizerOptions, out error))
355+
{
356+
return false;
357+
}
358+
359+
this.AppendTransformIfChanged(command.Transform);
360+
this.AppendPlainStroke(resolved, command.Pen);
361+
error = null;
362+
return true;
363+
}
364+
343365
/// <summary>
344366
/// Appends one prepared explicit line-segment stroke to the scene streams.
345367
/// </summary>
@@ -933,6 +955,18 @@ private static bool TryResolveCommand(in CompositionCommand command, out Resolve
933955
return true;
934956
}
935957

958+
private static bool TryResolveCommand(in StrokePathCommand command, out ResolvedPathCommand resolved)
959+
{
960+
resolved = new ResolvedPathCommand(
961+
command.SourcePath,
962+
command.Brush,
963+
command.GraphicsOptions,
964+
command.RasterizerOptions,
965+
command.DestinationOffset,
966+
command.Brush is ImageBrush ? command.RasterizerOptions.Interest : default);
967+
return true;
968+
}
969+
936970
private static bool TryResolveCommand(in StrokeLineSegmentCommand command, out ResolvedLineSegmentCommand resolved)
937971
{
938972
resolved = new ResolvedLineSegmentCommand(
@@ -1173,10 +1207,6 @@ private static int EncodeStrokePath(
11731207
for (int i = 0; i < geometry.Contours.Count; i++)
11741208
{
11751209
LinearContour contour = geometry.Contours[i];
1176-
if (contour.SegmentCount == 0)
1177-
{
1178-
continue;
1179-
}
11801210

11811211
int markerWordCount = contour.IsClosed ? 2 : 4;
11821212
Span<uint> contourData = pathData.GetAppendSpan(2 + (contour.SegmentCount * 2) + markerWordCount);
@@ -1454,10 +1484,6 @@ private static int EstimateStrokeLineCount(LinearGeometry geometry, Pen pen)
14541484
for (int i = 0; i < geometry.Contours.Count; i++)
14551485
{
14561486
LinearContour contour = geometry.Contours[i];
1457-
if (contour.SegmentCount == 0)
1458-
{
1459-
continue;
1460-
}
14611487

14621488
total += contour.SegmentCount * 2;
14631489
if (contour.IsClosed)

src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,10 @@
4545
<None Include="..\..\shared-infrastructure\branding\icons\imagesharp.drawing\sixlabors.imagesharp.drawing.128.png" Pack="true" PackagePath="" />
4646
</ItemGroup>
4747
<ItemGroup>
48-
<PackageReference Include="SixLabors.Fonts" Version="3.0.0-alpha.0.31" />
49-
<!--<PackageReference Include="SixLabors.ImageSharp" Version="4.0.0-alpha.0.86" />-->
48+
<PackageReference Include="SixLabors.Fonts" Version="3.0.0-alpha.0.36" />
49+
<PackageReference Include="SixLabors.ImageSharp" Version="4.0.0-alpha.0.88" />
5050
<PackageReference Include="SixLabors.PolygonClipper" Version="1.0.0-alpha.0.52" />
5151
</ItemGroup>
52-
<ItemGroup>
53-
<ProjectReference Include="..\..\..\ImageSharp\src\ImageSharp\ImageSharp.csproj" />
54-
</ItemGroup>
55-
56-
<ItemGroup>
57-
<Using Include="SixLabors.ImageSharp" />
58-
<Using Include="SixLabors.ImageSharp.PixelFormats" />
59-
<Using Include="SixLabors.ImageSharp.Processing" />
60-
</ItemGroup>
6152

6253
<Import Project="..\..\shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems" Label="Shared" />
6354

src/ImageSharp.Drawing/LinearGeometry.cs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ namespace SixLabors.ImageSharp.Drawing;
2626
/// </remarks>
2727
public sealed class LinearGeometry
2828
{
29+
private readonly LinearContour[] contours;
30+
private readonly PointF[] points;
31+
2932
/// <summary>
3033
/// Initializes a new instance of the <see cref="LinearGeometry"/> class.
3134
/// </summary>
@@ -34,9 +37,14 @@ public sealed class LinearGeometry
3437
/// <param name="points">The point storage.</param>
3538
public LinearGeometry(LinearGeometryInfo info, IReadOnlyList<LinearContour> contours, IReadOnlyList<PointF> points)
3639
{
40+
ArgumentNullException.ThrowIfNull(contours);
41+
ArgumentNullException.ThrowIfNull(points);
42+
3743
this.Info = info;
38-
this.Contours = contours ?? throw new ArgumentNullException(nameof(contours));
39-
this.Points = points ?? throw new ArgumentNullException(nameof(points));
44+
this.contours = contours as LinearContour[] ?? [.. contours];
45+
this.points = points as PointF[] ?? [.. points];
46+
this.Contours = this.contours;
47+
this.Points = this.points;
4048
}
4149

4250
/// <summary>
@@ -61,14 +69,10 @@ public LinearGeometry(LinearGeometryInfo info, IReadOnlyList<LinearContour> cont
6169
/// </remarks>
6270
public IReadOnlyList<PointF> Points { get; }
6371

64-
/// <summary>
65-
/// Creates retained geometry for one open two-point polyline.
66-
/// </summary>
67-
/// <param name="start">The first point.</param>
68-
/// <param name="end">The second point.</param>
69-
/// <returns>The retained open polyline geometry.</returns>
70-
public static LinearGeometry CreateOpenPolyline(PointF start, PointF end)
71-
=> CreateOpenPolyline([start, end]);
72+
internal ReadOnlySpan<LinearContour> GetContours() => this.contours;
73+
74+
internal ReadOnlySpan<PointF> GetContourPoints(in LinearContour contour)
75+
=> this.points.AsSpan(contour.PointStart, contour.PointCount);
7276

7377
/// <summary>
7478
/// Creates retained geometry for one open polyline.

src/ImageSharp.Drawing/Processing/Backends/CompositionCommand.cs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
1111
public enum CompositionCommandKind : byte
1212
{
1313
/// <summary>
14-
/// A fill or stroked-path command.
14+
/// A fill-path command.
1515
/// </summary>
1616
FillLayer = 0,
1717

@@ -27,14 +27,13 @@ public enum CompositionCommandKind : byte
2727
}
2828

2929
/// <summary>
30-
/// One normalized path- or layer-based composition command queued by <see cref="DrawingCanvasBatcher{TPixel}"/>.
30+
/// One normalized fill-path or layer-based composition command queued by <see cref="DrawingCanvasBatcher{TPixel}"/>.
3131
/// </summary>
3232
/// <remarks>
33-
/// This type carries path-backed draw commands plus inline layer boundaries.
33+
/// This type carries fill-path commands plus inline layer boundaries.
3434
/// </remarks>
3535
public readonly struct CompositionCommand
3636
{
37-
private readonly Pen? pen;
3837
private readonly IPath? sourcePath;
3938
private readonly Brush? brush;
4039
private readonly Matrix4x4 transform;
@@ -50,7 +49,6 @@ private CompositionCommand(
5049
Rectangle targetBounds,
5150
Rectangle layerBounds,
5251
Point destinationOffset,
53-
Pen? pen,
5452
Matrix4x4 transform,
5553
IReadOnlyList<IPath>? clipPaths,
5654
ShapeOptions? shapeOptions)
@@ -63,7 +61,6 @@ private CompositionCommand(
6361
this.TargetBounds = targetBounds;
6462
this.LayerBounds = layerBounds;
6563
this.DestinationOffset = destinationOffset;
66-
this.pen = pen;
6764
this.transform = transform;
6865
this.clipPaths = clipPaths;
6966
this.shapeOptions = shapeOptions;
@@ -74,11 +71,6 @@ private CompositionCommand(
7471
/// </summary>
7572
public CompositionCommandKind Kind { get; }
7673

77-
/// <summary>
78-
/// Gets the stroke metadata for stroke commands.
79-
/// </summary>
80-
public readonly Pen? Pen => this.pen;
81-
8274
/// <summary>
8375
/// Gets the absolute bounds of the logical target for this command.
8476
/// </summary>
@@ -134,7 +126,7 @@ private CompositionCommand(
134126
public ShapeOptions ShapeOptions => this.shapeOptions ?? throw new InvalidOperationException("Layer commands do not carry shape options.");
135127

136128
/// <summary>
137-
/// Creates a path-backed composition command.
129+
/// Creates a fill-path composition command.
138130
/// </summary>
139131
/// <param name="path">Path in target-local coordinates.</param>
140132
/// <param name="brush">Brush used during composition.</param>
@@ -144,7 +136,6 @@ private CompositionCommand(
144136
/// <param name="transform">Transform matrix supplied with the command.</param>
145137
/// <param name="targetBounds">The absolute bounds of the logical target for this command.</param>
146138
/// <param name="destinationOffset">Absolute destination offset where coverage is composited.</param>
147-
/// <param name="pen">Optional pen for stroked-path commands.</param>
148139
/// <param name="clipPaths">Optional clip paths supplied with the command.</param>
149140
/// <returns>The composition command.</returns>
150141
public static CompositionCommand Create(
@@ -156,7 +147,6 @@ public static CompositionCommand Create(
156147
Matrix4x4 transform,
157148
Rectangle targetBounds,
158149
Point destinationOffset = default,
159-
Pen? pen = null,
160150
IReadOnlyList<IPath>? clipPaths = null)
161151
=> new(
162152
CompositionCommandKind.FillLayer,
@@ -167,7 +157,6 @@ public static CompositionCommand Create(
167157
targetBounds,
168158
default,
169159
destinationOffset,
170-
pen,
171160
transform,
172161
clipPaths,
173162
shapeOptions);
@@ -188,7 +177,6 @@ public static CompositionCommand CreateBeginLayer(Rectangle layerBounds, Graphic
188177
layerBounds,
189178
layerBounds,
190179
default,
191-
null,
192180
Matrix4x4.Identity,
193181
null,
194182
null);
@@ -209,7 +197,6 @@ public static CompositionCommand CreateEndLayer(Rectangle layerBounds, GraphicsO
209197
layerBounds,
210198
layerBounds,
211199
default,
212-
null,
213200
Matrix4x4.Identity,
214201
null,
215202
null);

src/ImageSharp.Drawing/Processing/Backends/CompositionSceneCommandBase.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,17 @@ namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
1111
public interface ICompositionSceneCommandVisitor
1212
{
1313
/// <summary>
14-
/// Visits one path- or layer-based composition command.
14+
/// Visits one fill-path or layer-based composition command.
1515
/// </summary>
1616
/// <param name="command">The command being visited.</param>
1717
public void Visit(PathCompositionSceneCommand command);
1818

19+
/// <summary>
20+
/// Visits one stroked path command.
21+
/// </summary>
22+
/// <param name="command">The command being visited.</param>
23+
public void Visit(StrokePathCompositionSceneCommand command);
24+
1925
/// <summary>
2026
/// Visits one explicit stroked line-segment command.
2127
/// </summary>
@@ -42,7 +48,7 @@ public abstract class CompositionSceneCommand
4248
}
4349

4450
/// <summary>
45-
/// Scene command wrapper for path- and layer-based composition commands.
51+
/// Scene command wrapper for fill-path and layer-based composition commands.
4652
/// </summary>
4753
public sealed class PathCompositionSceneCommand : CompositionSceneCommand
4854
{
@@ -62,6 +68,27 @@ public PathCompositionSceneCommand(in CompositionCommand command)
6268
public override void Accept(ICompositionSceneCommandVisitor visitor) => visitor.Visit(this);
6369
}
6470

71+
/// <summary>
72+
/// Scene command wrapper for stroked path commands.
73+
/// </summary>
74+
public sealed class StrokePathCompositionSceneCommand : CompositionSceneCommand
75+
{
76+
/// <summary>
77+
/// Initializes a new instance of the <see cref="StrokePathCompositionSceneCommand"/> class.
78+
/// </summary>
79+
/// <param name="command">The wrapped stroke path command.</param>
80+
public StrokePathCompositionSceneCommand(in StrokePathCommand command)
81+
=> this.Command = command;
82+
83+
/// <summary>
84+
/// Gets the wrapped stroke path command.
85+
/// </summary>
86+
public StrokePathCommand Command { get; internal set; }
87+
88+
/// <inheritdoc />
89+
public override void Accept(ICompositionSceneCommandVisitor visitor) => visitor.Visit(this);
90+
}
91+
6592
/// <summary>
6693
/// Scene command wrapper for explicit stroked line-segment commands.
6794
/// </summary>

src/ImageSharp.Drawing/Processing/Backends/DefaultRasterizer.Linearizer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ protected Linearizer(
144144
/// Executes the linearization pass and finalizes the retained row payloads.
145145
/// </summary>
146146
/// <returns><see langword="true"/> when any retained coverage was produced; otherwise <see langword="false"/>.</returns>
147-
protected bool ProcessCore()
147+
protected virtual bool ProcessCore()
148148
{
149149
RectangleF translatedBounds = this.Geometry.Info.Bounds;
150150
translatedBounds.Offset(this.TranslateX + this.SamplingOffsetX - this.MinX, this.TranslateY + this.SamplingOffsetY - this.MinY);

0 commit comments

Comments
 (0)