// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; using System.Collections.Generic; using System.Numerics; namespace SixLabors.ImageSharp.Drawing { /// /// Represents retained linearized geometry that can be consumed directly by drawing backends. /// /// /// /// A instance stores contour-local point data plus the metadata required to /// interpret those points as a sequence of final linear segments. /// /// /// Closed contours do not duplicate their first point at the end of the stored point run. Closure is represented /// by , and the closing segment is derived by . /// /// /// The retained storage model is: /// /// /// stores the concatenated point data for every contour. /// maps each contour to its point run and derived segment range. /// exposes geometry-wide metadata such as bounds and total segment count. /// /// public sealed class LinearGeometry { private readonly LinearContour[] contours; private readonly PointF[] points; /// /// Initializes a new instance of the class. /// /// The geometry metadata. /// The contour metadata. /// The point storage. public LinearGeometry(LinearGeometryInfo info, IReadOnlyList contours, IReadOnlyList points) { Guard.NotNull(contours, nameof(contours)); Guard.NotNull(points, nameof(points)); this.Info = info; this.contours = contours as LinearContour[] ?? [.. contours]; this.points = points as PointF[] ?? [.. points]; this.Contours = this.contours; this.Points = this.points; } /// /// Gets geometry-wide metadata for this retained result. /// public LinearGeometryInfo Info { get; } /// /// Gets the contour metadata describing how is partitioned. /// /// /// Each entry defines one contour's point run and the corresponding segment range in the derived segment stream. /// public IReadOnlyList Contours { get; } /// /// Gets the retained point storage for all contours in this geometry. /// /// /// Points are stored per contour in contour order. A closed contour does not repeat its first point at the end /// of its stored point run. /// public IReadOnlyList Points { get; } internal ReadOnlySpan GetContours() => this.contours; internal ReadOnlySpan GetContourPoints(in LinearContour contour) => this.points.AsSpan(contour.PointStart, contour.PointCount); /// /// Creates retained geometry for one open polyline, baked under the supplied device-space . /// /// The polyline points. /// The X/Y scale at which the polyline is baked. /// The retained open polyline geometry. public static LinearGeometry CreateOpenPolyline(PointF[] points, Vector2 scale) { Guard.NotNull(points, nameof(points)); Guard.MustBeGreaterThanOrEqualTo(points.Length, 2, nameof(points)); PointF[] retained; if (scale == Vector2.One) { retained = points; } else { retained = new PointF[points.Length]; for (int i = 0; i < points.Length; i++) { retained[i] = new PointF(points[i].X * scale.X, points[i].Y * scale.Y); } } RectangleF bounds = GetPointBounds(retained); int segmentCount = retained.Length - 1; int nonHorizontalBoundary = 0; int nonHorizontalCenter = 0; for (int i = 0; i < segmentCount; i++) { PointF start = retained[i]; PointF end = retained[i + 1]; if ((int)MathF.Floor(start.Y) != (int)MathF.Floor(end.Y)) { nonHorizontalBoundary++; } if ((int)MathF.Floor(start.Y + 0.5F) != (int)MathF.Floor(end.Y + 0.5F)) { nonHorizontalCenter++; } } return new LinearGeometry( new LinearGeometryInfo { Bounds = bounds, ContourCount = 1, PointCount = retained.Length, SegmentCount = segmentCount, NonHorizontalSegmentCountPixelBoundary = nonHorizontalBoundary, NonHorizontalSegmentCountPixelCenter = nonHorizontalCenter }, [new LinearContour { PointStart = 0, PointCount = retained.Length, SegmentStart = 0, SegmentCount = segmentCount, IsClosed = false } ], retained); } /// /// Creates retained geometry for one open polyline. /// /// The polyline points. /// The retained open polyline geometry. public static LinearGeometry CreateOpenPolyline(PointF[] points) => CreateOpenPolyline(points, Vector2.One); /// /// Gets an enumerator for the derived linear segments represented by and . /// /// /// A zero-allocation enumerator that yields the final linear segments in contour order. /// public SegmentEnumerator GetSegments() => new(this); private static RectangleF GetPointBounds(PointF[] points) { 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); } return RectangleF.FromLTRB(minX, minY, maxX, maxY); } } }