// 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 two-point line-segment command queued by the canvas batcher. /// public readonly struct StrokeLineSegmentCommand { private readonly PointF sourceStart; private readonly PointF sourceEnd; private readonly DrawingOptions drawingOptions; /// /// Initializes a new instance of the struct. /// /// The source line start point. /// The source line end point. /// 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 StrokeLineSegmentCommand( PointF sourceStart, PointF sourceEnd, Brush brush, DrawingOptions drawingOptions, in RasterizerOptions rasterizerOptions, Rectangle targetBounds, Point destinationOffset, Pen pen, bool isInsideLayer) { this.sourceStart = sourceStart; this.sourceEnd = sourceEnd; 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 line start point. /// public PointF SourceStart => this.sourceStart; /// /// Gets the source line end point. /// public PointF SourceEnd => this.sourceEnd; /// /// 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 two-point line segment. /// /// The line start point. /// The line end point. /// The stroke metadata. /// The conservative stroked bounds. public static RectangleF GetConservativeBounds(PointF start, PointF end, Pen pen) { float left = MathF.Min(start.X, end.X); float top = MathF.Min(start.Y, end.Y); float right = MathF.Max(start.X, end.X); float bottom = MathF.Max(start.Y, end.Y); RectangleF bounds = RectangleF.FromLTRB(left, top, right, bottom); 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; } } }