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