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