// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using System;
namespace SixLabors.ImageSharp.Drawing.Processing.Backends {
///
/// CPU backend that executes path coverage rasterization and brush composition directly against a CPU region.
///
public sealed partial class DefaultDrawingBackend
{
///
/// Adapts rasterizer coverage callbacks into brush application against the active band target.
///
/// The pixel format.
private readonly struct FillCoverageRowHandler : IRasterizerCoverageRowHandler
where TPixel : unmanaged, IPixel
{
private readonly BrushRenderer renderer;
private readonly BandTarget target;
private readonly BrushWorkspace brushWorkspace;
///
/// Initializes a new instance of the struct.
///
/// The brush renderer that will consume emitted coverage spans.
/// The active band target being rendered.
/// The worker-local brush workspace.
public FillCoverageRowHandler(
BrushRenderer renderer,
BandTarget target,
BrushWorkspace brushWorkspace)
{
this.renderer = renderer;
this.target = target;
this.brushWorkspace = brushWorkspace;
}
///
/// Applies one emitted coverage span to the active destination band.
///
/// The absolute destination row.
/// The absolute start column of the coverage span.
/// The emitted coverage values.
public void Handle(int y, int startX, Span coverage)
{
int localY = y - this.target.AbsoluteTop;
if ((uint)localY >= (uint)this.target.Region.Height)
{
return;
}
int clipStartX = Math.Max(startX, this.target.AbsoluteLeft);
int clipEndX = Math.Min(startX + coverage.Length, this.target.AbsoluteLeft + this.target.Region.Width);
if (clipEndX <= clipStartX)
{
return;
}
// The rasterizer emits absolute coordinates; clip them once here so the brush
// renderer can operate against a tight destination span with no extra bounds work.
int coverageOffset = clipStartX - startX;
int clippedLength = clipEndX - clipStartX;
Span destinationRow = this.target.Region
.DangerousGetRowSpan(localY)
.Slice(clipStartX - this.target.AbsoluteLeft, clippedLength);
this.renderer.Apply(destinationRow, coverage.Slice(coverageOffset, clippedLength), clipStartX, y, this.brushWorkspace);
}
}
///
/// Represents one active composition target for a retained row.
///
/// The pixel format.
private sealed class BandTarget : IDisposable
where TPixel : unmanaged, IPixel
{
private readonly Buffer2D? owner;
///
/// Initializes a new instance of the class over an existing region.
///
/// The destination region.
/// The absolute X origin of the region.
/// The absolute Y origin of the region.
/// The graphics options used when this target is later composited.
public BandTarget(Buffer2DRegion region, int absoluteLeft, int absoluteTop, GraphicsOptions? graphicsOptions)
{
this.Region = region;
this.AbsoluteLeft = absoluteLeft;
this.AbsoluteTop = absoluteTop;
this.GraphicsOptions = graphicsOptions;
}
///
/// Initializes a new instance of the class over an owned temporary buffer.
///
/// The owned buffer backing the target.
/// The absolute bounds represented by the target.
/// The graphics options used when this target is later composited.
public BandTarget(Buffer2D owner, Rectangle bounds, GraphicsOptions? graphicsOptions)
{
this.owner = owner;
this.Region = owner.GetRegion();
this.AbsoluteLeft = bounds.X;
this.AbsoluteTop = bounds.Y;
this.GraphicsOptions = graphicsOptions;
}
///
/// Gets the writable pixel region for the target.
///
public Buffer2DRegion Region { get; }
///
/// Gets the absolute X origin of .
///
public int AbsoluteLeft { get; }
///
/// Gets the absolute Y origin of .
///
public int AbsoluteTop { get; }
///
/// Gets the graphics options associated with the target when it is used as a layer.
///
public GraphicsOptions? GraphicsOptions { get; }
///
/// Releases the owned temporary buffer when the target represents a layer.
///
public void Dispose() => this.owner?.Dispose();
}
///
/// Holds the reusable worker-local scratch used while executing retained scene rows.
///
/// The pixel format.
private sealed class WorkerState : IDisposable
where TPixel : unmanaged, IPixel
{
private readonly MemoryAllocator allocator;
private DefaultRasterizer.WorkerScratch? scratch;
///
/// Initializes a new instance of the class.
///
/// The memory allocator used for scratch growth.
/// The destination width used to size the brush workspace.
/// The maximum retained layer depth required by the scene.
public WorkerState(
MemoryAllocator allocator,
int destinationWidth,
int layerDepth)
{
this.allocator = allocator;
this.BrushWorkspace = new BrushWorkspace(allocator, destinationWidth);
this.TargetStack = new BandTarget[layerDepth];
}
///
/// Gets the reusable brush workspace for the worker.
///
public BrushWorkspace BrushWorkspace { get; }
///
/// Gets the reusable composition target stack for the worker.
///
public BandTarget[] TargetStack { get; }
///
/// Returns a reusable raster scratch instance sized for the requested width.
///
/// The minimum scanline width required by the current row.
/// A scratch instance that can execute the row.
public DefaultRasterizer.WorkerScratch GetOrCreateScratch(int requiredWidth)
{
DefaultRasterizer.WorkerScratch? current = this.scratch;
if (current is not null && current.CanReuse(requiredWidth))
{
return current;
}
current?.Dispose();
this.scratch = DefaultRasterizer.CreateWorkerScratch(this.allocator, requiredWidth);
return this.scratch;
}
///
/// Releases the worker-local scratch and brush workspace.
///
public void Dispose()
{
this.scratch?.Dispose();
this.BrushWorkspace.Dispose();
}
}
}
}