ImageSharp/SixLabors.Fonts/ClipQuad.cs
2026-08-03 22:31:27 +02:00

86 lines
3.6 KiB
C#

// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
namespace SixLabors.Fonts {
/// <summary>
/// Represents a rectangular clipping region as a convex quadrilateral.
/// Allows for transformation by rotation, skew, or non-uniform scaling,
/// resulting in non-axis-aligned edges.
/// </summary>
public readonly struct ClipQuad
{
/// <summary>
/// Initializes a new instance of the <see cref="ClipQuad"/> struct.
/// </summary>
/// <param name="topLeft">The top-left corner of the quadrilateral.</param>
/// <param name="topRight">The top-right corner of the quadrilateral.</param>
/// <param name="bottomRight">The bottom-right corner of the quadrilateral.</param>
/// <param name="bottomLeft">The bottom-left corner of the quadrilateral.</param>
public ClipQuad(Vector2 topLeft, Vector2 topRight, Vector2 bottomRight, Vector2 bottomLeft)
{
this.TopLeft = topLeft;
this.TopRight = topRight;
this.BottomRight = bottomRight;
this.BottomLeft = bottomLeft;
}
/// <summary>
/// Gets the top-left corner of the quadrilateral.
/// </summary>
public Vector2 TopLeft { get; }
/// <summary>
/// Gets the top-right corner of the quadrilateral.
/// </summary>
public Vector2 TopRight { get; }
/// <summary>
/// Gets the bottom-right corner of the quadrilateral.
/// </summary>
public Vector2 BottomRight { get; }
/// <summary>
/// Gets the bottom-left corner of the quadrilateral.
/// </summary>
public Vector2 BottomLeft { get; }
/// <summary>
/// Creates a <see cref="ClipQuad"/> from an axis-aligned <see cref="Bounds"/> and an optional transform.
/// </summary>
/// <param name="bounds">The bounds representing the untransformed rectangular area.</param>
/// <param name="transform">An optional transform to apply. If omitted, no transform is applied.</param>
/// <returns>A <see cref="ClipQuad"/> representing the transformed rectangle.</returns>
internal static ClipQuad FromBounds(in Bounds bounds, in Matrix3x2 transform)
{
Vector2 tl = Vector2.Transform(bounds.Min, transform);
Vector2 tr = Vector2.Transform(new Vector2(bounds.Max.X, bounds.Min.Y), transform);
Vector2 br = Vector2.Transform(bounds.Max, transform);
Vector2 bl = Vector2.Transform(new Vector2(bounds.Min.X, bounds.Max.Y), transform);
return new ClipQuad(tl, tr, br, bl);
}
/// <summary>
/// Determines whether the quadrilateral is axis-aligned within a small tolerance.
/// </summary>
/// <param name="tolerance">The tolerance for comparing parallel edges, typically a small epsilon.</param>
/// <returns>
/// <see langword="true"/> if opposite edges are parallel and of equal length; otherwise, <see langword="false"/>.
/// </returns>
public bool IsAxisAligned(float tolerance = 1E-4F)
{
Vector2 top = this.TopRight - this.TopLeft;
Vector2 bottom = this.BottomRight - this.BottomLeft;
Vector2 left = this.BottomLeft - this.TopLeft;
Vector2 right = this.BottomRight - this.TopRight;
bool horizontalParallel = MathF.Abs(Vector2.Dot(Vector2.Normalize(top), Vector2.Normalize(bottom)) - 1F) < tolerance;
bool verticalParallel = MathF.Abs(Vector2.Dot(Vector2.Normalize(left), Vector2.Normalize(right)) - 1F) < tolerance;
return horizontalParallel && verticalParallel;
}
}
}