// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using System; using System.Collections.Generic; namespace SixLabors.ImageSharp.Drawing.Processing.Backends { /// /// Processor barrier recorded in a drawing backend timeline. /// internal sealed class ApplyBarrier { /// /// Initializes a new instance of the class. /// /// The closed path defining the processed region. /// The drawing options captured when the barrier was recorded. /// The active clip paths captured when the barrier was recorded. /// The canvas-local bounds captured when the barrier was recorded. /// The absolute target bounds captured when the barrier was recorded. /// The absolute destination offset captured when the barrier was recorded. /// Indicates whether the barrier was recorded inside a layer. /// The processor operation to run against the replay-time snapshot. internal ApplyBarrier( IPath path, DrawingOptions options, IReadOnlyList clipPaths, Rectangle canvasBounds, Rectangle targetBounds, Point destinationOffset, bool isInsideLayer, Action operation) { this.Path = path; this.Options = options; this.ClipPaths = clipPaths; this.CanvasBounds = canvasBounds; this.TargetBounds = targetBounds; this.DestinationOffset = destinationOffset; this.IsInsideLayer = isInsideLayer; this.Operation = operation; } /// /// Gets the closed path defining the processed region. /// public IPath Path { get; } /// /// Gets the drawing options captured when the barrier was recorded. /// public DrawingOptions Options { get; } /// /// Gets the active clip paths captured when the barrier was recorded. /// public IReadOnlyList ClipPaths { get; } /// /// Gets the canvas-local bounds captured when the barrier was recorded. /// public Rectangle CanvasBounds { get; } /// /// Gets the absolute target bounds captured when the barrier was recorded. /// public Rectangle TargetBounds { get; } /// /// Gets the absolute destination offset captured when the barrier was recorded. /// public Point DestinationOffset { get; } /// /// Gets a value indicating whether the barrier was recorded inside a layer. /// public bool IsInsideLayer { get; } /// /// Gets the processor operation to run against the replay-time snapshot. /// public Action Operation { get; } /// /// Creates the transient image-brush draw command that writes this barrier's processed snapshot back to the target. /// /// The pixel format. /// The active processing configuration. /// The backend used to read the replay-time target pixels. /// The target frame. /// The image resource that must stay alive while the returned command batch is rendered. /// The transient write-back command batch, or when the barrier has no target coverage. public DrawingCommandBatch? CreateWriteBackBatch( Configuration configuration, IDrawingBackend backend, ICanvasFrame target, out IDisposable? ownedResource) where TPixel : unmanaged, IPixel { RectangleF rawBounds = RectangleF.Transform(this.Path.Bounds, this.Options.Transform); Rectangle sourceRect = ToConservativeBounds(rawBounds); sourceRect = Rectangle.Intersect(this.CanvasBounds, sourceRect); if (sourceRect.Width <= 0 || sourceRect.Height <= 0) { ownedResource = null; return null; } Image sourceImage = new(configuration, sourceRect.Width, sourceRect.Height); try { backend.ReadRegion( configuration, target, sourceRect, sourceImage.Frames.RootFrame.PixelBuffer.GetRegion()); sourceImage.Mutate(this.Operation); Point brushOffset = new( sourceRect.X - (int)MathF.Floor(rawBounds.Left), sourceRect.Y - (int)MathF.Floor(rawBounds.Top)); ImageBrush brush = new(sourceImage, sourceImage.Bounds, brushOffset); GraphicsOptions graphicsOptions = this.Options.GraphicsOptions; RasterizationMode rasterizationMode = graphicsOptions.Antialias ? RasterizationMode.Antialiased : RasterizationMode.Aliased; RectangleF pathBounds = this.Path.Bounds; Rectangle interest = Rectangle.FromLTRB( (int)MathF.Floor(pathBounds.Left), (int)MathF.Floor(pathBounds.Top), (int)MathF.Ceiling(pathBounds.Right), (int)MathF.Ceiling(pathBounds.Bottom)); RasterizerOptions rasterizerOptions = new( interest, this.Options.ShapeOptions.IntersectionRule, rasterizationMode, RasterizerSamplingOrigin.PixelBoundary, graphicsOptions.AntialiasThreshold); CompositionCommand command = CompositionCommand.Create( this.Path, brush, this.Options, in rasterizerOptions, this.TargetBounds, this.DestinationOffset, this.ClipPaths, this.IsInsideLayer); ownedResource = sourceImage; CompositionSceneCommand[] commands = [new PathCompositionSceneCommand(command)]; return new DrawingCommandBatch(commands, hasLayers: false); } catch { sourceImage.Dispose(); throw; } } private static Rectangle ToConservativeBounds(RectangleF bounds) => Rectangle.FromLTRB( (int)MathF.Floor(bounds.Left), (int)MathF.Floor(bounds.Top), (int)MathF.Ceiling(bounds.Right), (int)MathF.Ceiling(bounds.Bottom)); } }