ImageSharp/ImageSharp.Drawing/PathExtensions.cs
2026-08-03 22:31:27 +02:00

220 lines
9.9 KiB
C#

// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
namespace SixLabors.ImageSharp.Drawing {
/// <summary>
/// Convenience methods that can be applied to shapes and paths.
/// </summary>
public static partial class PathExtensions
{
/// <summary>
/// Creates a path rotated by the specified radians around its center.
/// </summary>
/// <param name="path">The path to rotate.</param>
/// <param name="radians">The radians to rotate the path.</param>
/// <returns>A <see cref="IPath"/> with a rotate transform applied.</returns>
public static IPathCollection Rotate(this IPathCollection path, float radians)
=> path.Transform(new Matrix4x4(Matrix3x2.CreateRotation(radians, RectangleF.Center(path.Bounds))));
/// <summary>
/// Creates a path rotated by the specified degrees around its center.
/// </summary>
/// <param name="shape">The path to rotate.</param>
/// <param name="degree">The degree to rotate the path.</param>
/// <returns>A <see cref="IPath"/> with a rotate transform applied.</returns>
public static IPathCollection RotateDegree(this IPathCollection shape, float degree)
=> shape.Rotate(GeometryUtilities.DegreeToRadian(degree));
/// <summary>
/// Creates a path translated by the supplied position
/// </summary>
/// <param name="path">The path to translate.</param>
/// <param name="position">The translation position.</param>
/// <returns>A <see cref="IPath"/> with a translate transform applied.</returns>
public static IPathCollection Translate(this IPathCollection path, PointF position)
=> path.Transform(Matrix4x4.CreateTranslation(position.X, position.Y, 0));
/// <summary>
/// Creates a path translated by the supplied position
/// </summary>
/// <param name="path">The path to translate.</param>
/// <param name="x">The amount to translate along the X axis.</param>
/// <param name="y">The amount to translate along the Y axis.</param>
/// <returns>A <see cref="IPath"/> with a translate transform applied.</returns>
public static IPathCollection Translate(this IPathCollection path, float x, float y)
=> path.Translate(new PointF(x, y));
/// <summary>
/// Creates a path translated by the supplied position
/// </summary>
/// <param name="path">The path to translate.</param>
/// <param name="scaleX">The amount to scale along the X axis.</param>
/// <param name="scaleY">The amount to scale along the Y axis.</param>
/// <returns>A <see cref="IPath"/> with a translate transform applied.</returns>
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)));
/// <summary>
/// Creates a path translated by the supplied position
/// </summary>
/// <param name="path">The path to translate.</param>
/// <param name="scale">The amount to scale along both the x and y axis.</param>
/// <returns>A <see cref="IPath"/> with a translate transform applied.</returns>
public static IPathCollection Scale(this IPathCollection path, float scale)
=> path.Transform(Matrix4x4.CreateScale(scale, scale, 1, new Vector3(RectangleF.Center(path.Bounds), 0)));
/// <summary>
/// Creates a path rotated by the specified radians around its center.
/// </summary>
/// <param name="path">The path to rotate.</param>
/// <param name="radians">The radians to rotate the path.</param>
/// <returns>A <see cref="IPath"/> with a rotate transform applied.</returns>
public static IPath Rotate(this IPath path, float radians)
=> path.Transform(new Matrix4x4(Matrix3x2.CreateRotation(radians, RectangleF.Center(path.Bounds))));
/// <summary>
/// Creates a path rotated by the specified degrees around its center.
/// </summary>
/// <param name="shape">The path to rotate.</param>
/// <param name="degree">The degree to rotate the path.</param>
/// <returns>A <see cref="IPath"/> with a rotate transform applied.</returns>
public static IPath RotateDegree(this IPath shape, float degree)
=> shape.Rotate(GeometryUtilities.DegreeToRadian(degree));
/// <summary>
/// Creates a path translated by the supplied position
/// </summary>
/// <param name="path">The path to translate.</param>
/// <param name="position">The translation position.</param>
/// <returns>A <see cref="IPath"/> with a translate transform applied.</returns>
public static IPath Translate(this IPath path, PointF position)
=> path.Transform(Matrix4x4.CreateTranslation(position.X, position.Y, 0));
/// <summary>
/// Creates a path translated by the supplied position
/// </summary>
/// <param name="path">The path to translate.</param>
/// <param name="x">The amount to translate along the X axis.</param>
/// <param name="y">The amount to translate along the Y axis.</param>
/// <returns>A <see cref="IPath"/> with a translate transform applied.</returns>
public static IPath Translate(this IPath path, float x, float y)
=> path.Translate(new Vector2(x, y));
/// <summary>
/// Creates a path translated by the supplied position
/// </summary>
/// <param name="path">The path to translate.</param>
/// <param name="scaleX">The amount to scale along the X axis.</param>
/// <param name="scaleY">The amount to scale along the Y axis.</param>
/// <returns>A <see cref="IPath"/> with a translate transform applied.</returns>
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)));
/// <summary>
/// Creates a path translated by the supplied position
/// </summary>
/// <param name="path">The path to translate.</param>
/// <param name="scale">The amount to scale along both the x and y axis.</param>
/// <returns>A <see cref="IPath"/> with a translate transform applied.</returns>
public static IPath Scale(this IPath path, float scale)
=> path.Transform(Matrix4x4.CreateScale(scale, scale, 1, new Vector3(RectangleF.Center(path.Bounds), 0)));
/// <summary>
/// Calculates the approximate length of the path as though each segment were unrolled into a line.
/// </summary>
/// <param name="path">The path to compute the length for.</param>
/// <returns>
/// The <see cref="float"/> representing the unrolled length.
/// For closed paths, the length includes an implicit closing segment.
/// </returns>
public static float ComputeLength(this IPath path)
{
float dist = 0;
foreach (ISimplePath s in path.Flatten())
{
ReadOnlySpan<PointF> 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;
}
/// <summary>
/// Calculates the total area of all paths in the specified collection.
/// </summary>
/// <param name="paths">A collection of paths for which to compute the combined area. Cannot be null.</param>
/// <returns>
/// The total area, in square units, enclosed by all paths in the collection.
/// </returns>
public static float ComputeArea(this IPathCollection paths)
{
float area = 0;
foreach (IPath path in paths)
{
area += path.ComputeArea();
}
return area;
}
/// <summary>
/// Calculates the total area enclosed by the specified path.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="path">
/// 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.
/// </param>
/// <returns>
/// 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.
/// </returns>
public static float ComputeArea(this IPath path)
{
float area = 0;
foreach (ISimplePath s in path.Flatten())
{
ReadOnlySpan<PointF> 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;
}
}
}