Skip to content

Commit 44fa989

Browse files
Pass layer bounds and refactor rasterizer spans
1 parent 7be0001 commit 44fa989

4 files changed

Lines changed: 108 additions & 136 deletions

File tree

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

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -222,17 +222,18 @@ public static CompositionCommand CreateBeginLayer(Rectangle layerBounds, Graphic
222222
/// <summary>
223223
/// Creates an end-layer composition command.
224224
/// </summary>
225+
/// <param name="layerBounds">The absolute bounds of the layer being closed.</param>
225226
/// <returns>The end-layer command.</returns>
226-
public static CompositionCommand CreateEndLayer()
227+
public static CompositionCommand CreateEndLayer(Rectangle layerBounds)
227228
=> new(
228229
CompositionCommandKind.EndLayer,
229230
0,
230231
EmptyPath.ClosedPath,
231232
SentinelBrush,
232233
SentinelGraphicsOptions,
233234
default,
234-
default,
235-
default,
235+
layerBounds,
236+
layerBounds,
236237
default,
237238
null,
238239
Matrix4x4.Identity,
@@ -273,35 +274,40 @@ internal void Prepare()
273274
bounds = new RectangleF(bounds.X + 0.5F, bounds.Y + 0.5F, bounds.Width, bounds.Height);
274275
}
275276

276-
// Coverage is generated in path-local interest coordinates.
277-
Rectangle interest = Rectangle.FromLTRB(
277+
Rectangle localInterest = Rectangle.FromLTRB(
278278
(int)MathF.Floor(bounds.Left),
279279
(int)MathF.Floor(bounds.Top),
280-
(int)MathF.Ceiling(bounds.Right),
280+
(int)MathF.Ceiling(bounds.Right) + 1,
281281
(int)MathF.Ceiling(bounds.Bottom));
282282

283+
Rectangle absoluteInterest = new(
284+
localInterest.X + this.DestinationOffset.X,
285+
localInterest.Y + this.DestinationOffset.Y,
286+
localInterest.Width,
287+
localInterest.Height);
288+
283289
this.RasterizerOptions = new RasterizerOptions(
284-
interest,
290+
absoluteInterest,
285291
old.IntersectionRule,
286292
old.RasterizationMode,
287293
old.SamplingOrigin,
288294
old.AntialiasThreshold);
289295

290296
this.BrushBounds = new Rectangle(
291-
interest.X + this.DestinationOffset.X,
292-
interest.Y + this.DestinationOffset.Y,
293-
interest.Width,
294-
interest.Height);
297+
absoluteInterest.X,
298+
absoluteInterest.Y,
299+
absoluteInterest.Width,
300+
absoluteInterest.Height);
295301

296302
RasterizerOptions updated = this.RasterizerOptions;
297303
this.DefinitionKey = ComputeCoverageDefinitionKey(preparedPath, in updated);
298304

299305
// Move the interest rect into absolute destination space, then clip it back to the command target.
300306
Rectangle commandDestination = new(
301-
this.DestinationOffset.X + interest.X,
302-
this.DestinationOffset.Y + interest.Y,
303-
interest.Width,
304-
interest.Height);
307+
absoluteInterest.X,
308+
absoluteInterest.Y,
309+
absoluteInterest.Width,
310+
absoluteInterest.Height);
305311

306312
Rectangle clippedDestination = Rectangle.Intersect(this.TargetBounds, commandDestination);
307313
if (clippedDestination.Width <= 0 || clippedDestination.Height <= 0)

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

Lines changed: 21 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,12 @@ private static void ThrowInterestBoundsTooLarge()
132132
RectangleF translatedBounds = geometry.Info.Bounds;
133133
translatedBounds.Offset(translateX + samplingOffsetX, translateY + samplingOffsetY);
134134

135+
// The retained clipper ignores segments at the maximum X edge,
136+
// so extend the right bound by one pixel to keep closing vertical edges available.
135137
Rectangle geometryBounds = Rectangle.FromLTRB(
136138
(int)MathF.Floor(translatedBounds.Left),
137139
(int)MathF.Floor(translatedBounds.Top),
138-
(int)MathF.Ceiling(translatedBounds.Right),
140+
(int)MathF.Ceiling(translatedBounds.Right) + 1,
139141
(int)MathF.Ceiling(translatedBounds.Bottom));
140142

141143
Rectangle clippedBounds = Rectangle.Intersect(geometryBounds, options.Interest);
@@ -534,8 +536,6 @@ private readonly void EmitRowCoverage<TRowHandler>(
534536
int spanStart = 0;
535537
int spanEnd = 0;
536538
float spanCoverage = 0F;
537-
int runStart = -1;
538-
int runEnd = -1;
539539
int minWord = minTouchedColumn / WordBitCount;
540540
int maxWord = maxTouchedColumn / WordBitCount;
541541

@@ -565,8 +565,7 @@ private readonly void EmitRowCoverage<TRowHandler>(
565565
{
566566
if (coverage <= 0F)
567567
{
568-
WriteSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd);
569-
EmitRun(ref rowHandler, destinationY, destinationLeft, scanline, ref runStart, ref runEnd);
568+
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanStart, spanEnd, spanCoverage);
570569
spanStart = x + 1;
571570
spanEnd = spanStart;
572571
spanCoverage = 0F;
@@ -577,7 +576,7 @@ private readonly void EmitRowCoverage<TRowHandler>(
577576
}
578577
else
579578
{
580-
WriteSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd);
579+
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanStart, spanEnd, spanCoverage);
581580
spanStart = x;
582581
spanEnd = x + 1;
583582
spanCoverage = coverage;
@@ -589,7 +588,7 @@ private readonly void EmitRowCoverage<TRowHandler>(
589588
// non-zero coverage and must be emitted as its own run.
590589
if (cover == 0)
591590
{
592-
WriteSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd);
591+
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanStart, spanEnd, spanCoverage);
593592
spanStart = x;
594593
spanEnd = x + 1;
595594
spanCoverage = coverage;
@@ -601,7 +600,7 @@ private readonly void EmitRowCoverage<TRowHandler>(
601600
{
602601
// Even-odd can map non-zero winding to zero coverage.
603602
// Treat this as a hard run break so we don't bridge holes.
604-
WriteSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd);
603+
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanStart, spanEnd, spanCoverage);
605604
spanStart = x;
606605
spanEnd = x + 1;
607606
spanCoverage = coverage;
@@ -614,16 +613,16 @@ private readonly void EmitRowCoverage<TRowHandler>(
614613
}
615614
else
616615
{
617-
WriteSpan(scanline, spanStart, x, spanCoverage, ref runStart, ref runEnd);
616+
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanStart, x, spanCoverage);
618617
spanStart = x;
619618
spanEnd = x + 1;
620619
spanCoverage = coverage;
621620
}
622621
}
623622
else
624623
{
625-
WriteSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd);
626-
WriteSpan(scanline, spanEnd, x, gapCoverage, ref runStart, ref runEnd);
624+
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanStart, spanEnd, spanCoverage);
625+
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanEnd, x, gapCoverage);
627626
spanStart = x;
628627
spanEnd = x + 1;
629628
spanCoverage = coverage;
@@ -635,15 +634,12 @@ private readonly void EmitRowCoverage<TRowHandler>(
635634
}
636635
}
637636

638-
// Flush tail run and any remaining constant-cover tail after the last touched cell.
639-
WriteSpan(scanline, spanStart, spanEnd, spanCoverage, ref runStart, ref runEnd);
637+
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanStart, spanEnd, spanCoverage);
640638

641639
if (cover != 0 && spanEnd < this.width)
642640
{
643-
WriteSpan(scanline, spanEnd, this.width, this.AreaToCoverage(cover << AreaToCoverageShift), ref runStart, ref runEnd);
641+
EmitSpan(ref rowHandler, destinationY, destinationLeft, scanline, spanEnd, this.width, this.AreaToCoverage(cover << AreaToCoverageShift));
644642
}
645-
646-
EmitRun(ref rowHandler, destinationY, destinationLeft, scanline, ref runStart, ref runEnd);
647643
}
648644

649645
/// <summary>
@@ -691,57 +687,27 @@ private readonly float AreaToCoverage(int area)
691687
}
692688

693689
/// <summary>
694-
/// Writes one non-zero coverage segment into the scanline and expands the active run.
695-
/// </summary>
696-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
697-
private static void WriteSpan(
698-
Span<float> scanline,
699-
int start,
700-
int end,
701-
float coverage,
702-
ref int runStart,
703-
ref int runEnd)
704-
{
705-
if (coverage <= 0F || end <= start)
706-
{
707-
return;
708-
}
709-
710-
if (runStart < 0)
711-
{
712-
runStart = start;
713-
runEnd = end;
714-
}
715-
else if (end > runEnd)
716-
{
717-
runEnd = end;
718-
}
719-
720-
scanline[(start - runStart)..(end - runStart)].Fill(coverage);
721-
}
722-
723-
/// <summary>
724-
/// Emits the currently accumulated non-zero run, if any.
690+
/// Emits one constant-coverage span directly to the row handler.
725691
/// </summary>
726692
[MethodImpl(MethodImplOptions.AggressiveInlining)]
727-
private static void EmitRun<TRowHandler>(
693+
private static void EmitSpan<TRowHandler>(
728694
ref TRowHandler rowHandler,
729695
int destinationY,
730696
int destinationLeft,
731697
Span<float> scanline,
732-
ref int runStart,
733-
ref int runEnd)
698+
int start,
699+
int end,
700+
float coverage)
734701
where TRowHandler : struct, IRasterizerCoverageRowHandler
735702
{
736-
if (runStart < 0)
703+
if (coverage <= 0F || end <= start)
737704
{
738705
return;
739706
}
740707

741-
rowHandler.Handle(destinationY, destinationLeft + runStart, scanline[..(runEnd - runStart)]);
742-
743-
runStart = -1;
744-
runEnd = -1;
708+
int length = end - start;
709+
scanline[..length].Fill(coverage);
710+
rowHandler.Handle(destinationY, destinationLeft + start, scanline[..length]);
745711
}
746712

747713
/// <summary>

src/ImageSharp.Drawing/Processing/Backends/FlushScene.RetainedTypes.cs

Lines changed: 64 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -11,70 +11,6 @@ namespace SixLabors.ImageSharp.Drawing.Processing.Backends;
1111
/// </summary>
1212
internal sealed partial class FlushScene
1313
{
14-
/// <summary>
15-
/// Holds one retained scene item.
16-
/// </summary>
17-
internal struct SceneItem : IDisposable
18-
{
19-
private object? renderer;
20-
21-
/// <summary>
22-
/// Initializes a new instance of the <see cref="SceneItem"/> struct.
23-
/// </summary>
24-
/// <param name="commandIndex">The source command index.</param>
25-
/// <param name="command">The retained composition command.</param>
26-
/// <param name="rasterizable">The retained rasterizable geometry.</param>
27-
public SceneItem(int commandIndex, CompositionCommand command, DefaultRasterizer.RasterizableGeometry rasterizable)
28-
{
29-
this.CommandIndex = commandIndex;
30-
this.Command = command;
31-
this.Rasterizable = rasterizable;
32-
}
33-
34-
/// <summary>
35-
/// Gets the source command index.
36-
/// </summary>
37-
public int CommandIndex { get; }
38-
39-
/// <summary>
40-
/// Gets the retained composition command.
41-
/// </summary>
42-
public CompositionCommand Command { get; }
43-
44-
/// <summary>
45-
/// Gets the retained rasterizable geometry.
46-
/// </summary>
47-
public DefaultRasterizer.RasterizableGeometry Rasterizable { get; }
48-
49-
/// <summary>
50-
/// Gets the memoized renderer for this scene item, creating it on first use.
51-
/// </summary>
52-
/// <typeparam name="TPixel">The pixel format.</typeparam>
53-
/// <param name="configuration">The active processing configuration.</param>
54-
/// <param name="canvasWidth">The destination canvas width.</param>
55-
/// <returns>The memoized renderer for the scene item.</returns>
56-
public BrushRenderer<TPixel> GetRenderer<TPixel>(Configuration configuration, int canvasWidth)
57-
where TPixel : unmanaged, IPixel<TPixel>
58-
{
59-
if (this.renderer is BrushRenderer<TPixel> typed)
60-
{
61-
return typed;
62-
}
63-
64-
typed = this.Command.Brush.CreateRenderer<TPixel>(
65-
configuration,
66-
this.Command.GraphicsOptions,
67-
canvasWidth,
68-
this.Command.BrushBounds);
69-
70-
this.renderer = typed;
71-
return typed;
72-
}
73-
74-
/// <inheritdoc />
75-
public readonly void Dispose() => this.Rasterizable.Dispose();
76-
}
77-
7814
/// <summary>
7915
/// Holds one retained row operation.
8016
/// </summary>
@@ -337,4 +273,68 @@ public SceneOperationBlock(MemoryAllocator allocator)
337273
/// </summary>
338274
public void Dispose() => this.owner.Dispose();
339275
}
276+
277+
/// <summary>
278+
/// Holds one retained scene item.
279+
/// </summary>
280+
internal sealed class SceneItem : IDisposable
281+
{
282+
private object? renderer;
283+
284+
/// <summary>
285+
/// Initializes a new instance of the <see cref="SceneItem"/> class.
286+
/// </summary>
287+
/// <param name="commandIndex">The source command index.</param>
288+
/// <param name="command">The retained composition command.</param>
289+
/// <param name="rasterizable">The retained rasterizable geometry.</param>
290+
public SceneItem(int commandIndex, CompositionCommand command, DefaultRasterizer.RasterizableGeometry rasterizable)
291+
{
292+
this.CommandIndex = commandIndex;
293+
this.Command = command;
294+
this.Rasterizable = rasterizable;
295+
}
296+
297+
/// <summary>
298+
/// Gets the source command index.
299+
/// </summary>
300+
public int CommandIndex { get; }
301+
302+
/// <summary>
303+
/// Gets the retained composition command.
304+
/// </summary>
305+
public CompositionCommand Command { get; }
306+
307+
/// <summary>
308+
/// Gets the retained rasterizable geometry.
309+
/// </summary>
310+
public DefaultRasterizer.RasterizableGeometry Rasterizable { get; }
311+
312+
/// <summary>
313+
/// Gets the memoized renderer for this scene item, creating it on first use.
314+
/// </summary>
315+
/// <typeparam name="TPixel">The pixel format.</typeparam>
316+
/// <param name="configuration">The active processing configuration.</param>
317+
/// <param name="canvasWidth">The destination canvas width.</param>
318+
/// <returns>The memoized renderer for the scene item.</returns>
319+
public BrushRenderer<TPixel> GetRenderer<TPixel>(Configuration configuration, int canvasWidth)
320+
where TPixel : unmanaged, IPixel<TPixel>
321+
{
322+
if (this.renderer is BrushRenderer<TPixel> typed)
323+
{
324+
return typed;
325+
}
326+
327+
typed = this.Command.Brush.CreateRenderer<TPixel>(
328+
configuration,
329+
this.Command.GraphicsOptions,
330+
canvasWidth,
331+
this.Command.BrushBounds);
332+
333+
this.renderer = typed;
334+
return typed;
335+
}
336+
337+
/// <inheritdoc />
338+
public void Dispose() => this.Rasterizable.Dispose();
339+
}
340340
}

src/ImageSharp.Drawing/Processing/DrawingCanvas{TPixel}.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ public void Restore()
304304
DrawingCanvasState popped = this.savedStates.Pop();
305305
if (popped.IsLayer)
306306
{
307-
this.batcher.AddComposition(CompositionCommand.CreateEndLayer());
307+
this.batcher.AddComposition(CompositionCommand.CreateEndLayer(popped.TargetBounds));
308308
}
309309
}
310310

@@ -1197,7 +1197,7 @@ private void RestoreToCore(int saveCount)
11971197
if (popped.IsLayer)
11981198
{
11991199
// Restore and Dispose unwind layers through the same command stream path.
1200-
this.batcher.AddComposition(CompositionCommand.CreateEndLayer());
1200+
this.batcher.AddComposition(CompositionCommand.CreateEndLayer(popped.TargetBounds));
12011201
}
12021202
}
12031203
}

0 commit comments

Comments
 (0)