// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
namespace SixLabors.ImageSharp.Drawing.Processing.Backends {
///
/// One explicit stroked open polyline command queued by the canvas batcher.
///
public readonly struct StrokePolylineCommand
{
private readonly PointF[] sourcePoints;
private readonly DrawingOptions drawingOptions;
///
/// Initializes a new instance of the struct.
///
/// The source polyline points.
/// The brush used to shade the stroke.
/// The drawing options (graphics, shape, transform) used during composition.
/// The rasterizer options used to generate coverage.
/// The absolute bounds of the logical target.
/// The absolute destination offset of the command.
/// The stroke metadata.
/// True if the command was recorded inside a layer.
public StrokePolylineCommand(
PointF[] sourcePoints,
Brush brush,
DrawingOptions drawingOptions,
in RasterizerOptions rasterizerOptions,
Rectangle targetBounds,
Point destinationOffset,
Pen pen,
bool isInsideLayer)
{
ArgumentNullException.ThrowIfNull(sourcePoints);
if (sourcePoints.Length < 2)
{
throw new ArgumentOutOfRangeException(nameof(sourcePoints), "Open stroke polylines require at least two points.");
}
this.sourcePoints = sourcePoints;
this.drawingOptions = drawingOptions;
this.Brush = brush;
this.RasterizerOptions = rasterizerOptions;
this.TargetBounds = targetBounds;
this.DestinationOffset = destinationOffset;
this.Pen = pen;
this.IsInsideLayer = isInsideLayer;
}
///
/// Gets the brush used during composition.
///
public Brush Brush { get; }
///
/// Gets the drawing options carried by the command.
///
public DrawingOptions DrawingOptions => this.drawingOptions;
///
/// Gets the graphics options used during composition.
///
public GraphicsOptions GraphicsOptions => this.drawingOptions.GraphicsOptions;
///
/// Gets the rasterizer options used to generate coverage.
///
public RasterizerOptions RasterizerOptions { get; }
///
/// Gets the absolute bounds of the logical target for this command.
///
public Rectangle TargetBounds { get; }
///
/// Gets the absolute destination offset where the local coverage should be composited.
///
public Point DestinationOffset { get; }
///
/// Gets the stroke metadata for this command.
///
public Pen Pen { get; }
///
/// Gets the source polyline points.
///
public PointF[] SourcePoints => this.sourcePoints;
///
/// Gets the command transform.
///
public Matrix4x4 Transform => this.drawingOptions.Transform;
///
/// Gets a value indicating whether the command was recorded inside a layer.
///
public bool IsInsideLayer { get; }
///
/// Computes the conservative stroked bounds of one open polyline.
///
/// The polyline points.
/// The stroke metadata.
/// The conservative stroked bounds.
public static RectangleF GetConservativeBounds(PointF[] points, Pen pen)
{
ArgumentNullException.ThrowIfNull(points);
if (points.Length == 0)
{
return RectangleF.Empty;
}
float minX = points[0].X;
float minY = points[0].Y;
float maxX = minX;
float maxY = minY;
for (int i = 1; i < points.Length; i++)
{
PointF point = points[i];
minX = MathF.Min(minX, point.X);
minY = MathF.Min(minY, point.Y);
maxX = MathF.Max(maxX, point.X);
maxY = MathF.Max(maxY, point.Y);
}
RectangleF bounds = RectangleF.FromLTRB(minX, minY, maxX, maxY);
return InflateBounds(bounds, pen);
}
private static RectangleF InflateBounds(RectangleF bounds, Pen pen)
{
float halfWidth = pen.StrokeWidth * 0.5F;
float inflate = pen.StrokeOptions.LineJoin switch
{
LineJoin.Miter or LineJoin.MiterRevert or LineJoin.MiterRound => (float)(halfWidth * Math.Max(pen.StrokeOptions.MiterLimit, 1D)),
_ => halfWidth
};
bounds.Inflate(new SizeF(inflate, inflate));
return bounds;
}
}
}