// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; using System.Numerics; namespace SixLabors.ImageSharp.Drawing { /// /// Convenience methods that can be applied to shapes and paths. /// public static partial class PathExtensions { /// /// Creates a path rotated by the specified radians around its center. /// /// The path to rotate. /// The radians to rotate the path. /// A with a rotate transform applied. public static IPathCollection Rotate(this IPathCollection path, float radians) => path.Transform(new Matrix4x4(Matrix3x2.CreateRotation(radians, RectangleF.Center(path.Bounds)))); /// /// Creates a path rotated by the specified degrees around its center. /// /// The path to rotate. /// The degree to rotate the path. /// A with a rotate transform applied. public static IPathCollection RotateDegree(this IPathCollection shape, float degree) => shape.Rotate(GeometryUtilities.DegreeToRadian(degree)); /// /// Creates a path translated by the supplied position /// /// The path to translate. /// The translation position. /// A with a translate transform applied. public static IPathCollection Translate(this IPathCollection path, PointF position) => path.Transform(Matrix4x4.CreateTranslation(position.X, position.Y, 0)); /// /// Creates a path translated by the supplied position /// /// The path to translate. /// The amount to translate along the X axis. /// The amount to translate along the Y axis. /// A with a translate transform applied. public static IPathCollection Translate(this IPathCollection path, float x, float y) => path.Translate(new PointF(x, y)); /// /// Creates a path translated by the supplied position /// /// The path to translate. /// The amount to scale along the X axis. /// The amount to scale along the Y axis. /// A with a translate transform applied. public static IPathCollection Scale(this IPathCollection path, float scaleX, float scaleY) => path.Transform(Matrix4x4.CreateScale(scaleX, scaleY, 1, new Vector3(RectangleF.Center(path.Bounds), 0))); /// /// Creates a path translated by the supplied position /// /// The path to translate. /// The amount to scale along both the x and y axis. /// A with a translate transform applied. public static IPathCollection Scale(this IPathCollection path, float scale) => path.Transform(Matrix4x4.CreateScale(scale, scale, 1, new Vector3(RectangleF.Center(path.Bounds), 0))); /// /// Creates a path rotated by the specified radians around its center. /// /// The path to rotate. /// The radians to rotate the path. /// A with a rotate transform applied. public static IPath Rotate(this IPath path, float radians) => path.Transform(new Matrix4x4(Matrix3x2.CreateRotation(radians, RectangleF.Center(path.Bounds)))); /// /// Creates a path rotated by the specified degrees around its center. /// /// The path to rotate. /// The degree to rotate the path. /// A with a rotate transform applied. public static IPath RotateDegree(this IPath shape, float degree) => shape.Rotate(GeometryUtilities.DegreeToRadian(degree)); /// /// Creates a path translated by the supplied position /// /// The path to translate. /// The translation position. /// A with a translate transform applied. public static IPath Translate(this IPath path, PointF position) => path.Transform(Matrix4x4.CreateTranslation(position.X, position.Y, 0)); /// /// Creates a path translated by the supplied position /// /// The path to translate. /// The amount to translate along the X axis. /// The amount to translate along the Y axis. /// A with a translate transform applied. public static IPath Translate(this IPath path, float x, float y) => path.Translate(new Vector2(x, y)); /// /// Creates a path translated by the supplied position /// /// The path to translate. /// The amount to scale along the X axis. /// The amount to scale along the Y axis. /// A with a translate transform applied. public static IPath Scale(this IPath path, float scaleX, float scaleY) => path.Transform(Matrix4x4.CreateScale(scaleX, scaleY, 1, new Vector3(RectangleF.Center(path.Bounds), 0))); /// /// Creates a path translated by the supplied position /// /// The path to translate. /// The amount to scale along both the x and y axis. /// A with a translate transform applied. public static IPath Scale(this IPath path, float scale) => path.Transform(Matrix4x4.CreateScale(scale, scale, 1, new Vector3(RectangleF.Center(path.Bounds), 0))); /// /// Calculates the approximate length of the path as though each segment were unrolled into a line. /// /// The path to compute the length for. /// /// The representing the unrolled length. /// For closed paths, the length includes an implicit closing segment. /// public static float ComputeLength(this IPath path) { float dist = 0; foreach (ISimplePath s in path.Flatten()) { ReadOnlySpan points = s.Points.Span; if (points.Length < 2) { // Only a single point continue; } for (int i = 1; i < points.Length; i++) { dist += Vector2.Distance(points[i - 1], points[i]); } if (s.IsClosed) { dist += Vector2.Distance(points[0], points[^1]); } } return dist; } /// /// Calculates the total area of all paths in the specified collection. /// /// A collection of paths for which to compute the combined area. Cannot be null. /// /// The total area, in square units, enclosed by all paths in the collection. /// public static float ComputeArea(this IPathCollection paths) { float area = 0; foreach (IPath path in paths) { area += path.ComputeArea(); } return area; } /// /// Calculates the total area enclosed by the specified path. /// /// /// This method sums the areas of all subpaths within the path. Subpaths with fewer than three /// points are ignored, as they do not form a closed region. The result is always non-negative, regardless of the /// winding direction of the subpaths. /// /// /// The path for which to compute the enclosed area. Must contain at least one subpath with three or more points to /// contribute to the area calculation. /// /// /// The total area, in square units, enclosed by all subpaths of the path. Returns 0 if the path does not contain /// any subpaths with at least three points. /// public static float ComputeArea(this IPath path) { float area = 0; foreach (ISimplePath s in path.Flatten()) { ReadOnlySpan points = s.Points.Span; if (points.Length < 3) { // Not enough points to form an area continue; } float subArea = 0; for (int i = 0; i < points.Length; i++) { PointF p1 = points[i]; PointF p2 = points[(i + 1) % points.Length]; subArea += (p1.X * p2.Y) - (p2.X * p1.Y); } area += MathF.Abs(subArea) * .5F; } return area; } } }