// 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(); } } } }