|
|
|
@ -68,6 +68,11 @@ internal static class TransformUtils |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Vector2 ProjectiveTransform2D(float x, float y, Matrix4x4 matrix) |
|
|
|
{ |
|
|
|
// The w component (v4.W) resulting from the transformation can be less than 0 in certain cases,
|
|
|
|
// such as when the point is transformed behind the camera in a perspective projection.
|
|
|
|
// However, in many 2D contexts, negative w values are not meaningful and could cause issues
|
|
|
|
// like flipped or distorted projections. To avoid this, we take the max of w and epsilon to ensure
|
|
|
|
// we don't divide by a very small or negative number, effectively treating any negative w as epsilon.
|
|
|
|
const float epsilon = 0.0000001F; |
|
|
|
Vector4 v4 = Vector4.Transform(new Vector4(x, y, 0, 1F), matrix); |
|
|
|
return new Vector2(v4.X, v4.Y) / MathF.Max(v4.W, epsilon); |
|
|
|
@ -78,48 +83,22 @@ internal static class TransformUtils |
|
|
|
/// </summary>
|
|
|
|
/// <param name="degrees">The amount of rotation, in degrees.</param>
|
|
|
|
/// <param name="size">The source image size.</param>
|
|
|
|
/// <param name="transformSpace">The <see cref="TransformSpace"/> to use when creating the centered matrix.</param>
|
|
|
|
/// <returns>The <see cref="Matrix3x2"/>.</returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Matrix3x2 CreateRotationTransformMatrixDegrees(float degrees, Size size) |
|
|
|
=> CreateCenteredTransformMatrix( |
|
|
|
new Rectangle(Point.Empty, size), |
|
|
|
Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty)); |
|
|
|
public static Matrix3x2 CreateRotationTransformMatrixDegrees(float degrees, Size size, TransformSpace transformSpace) |
|
|
|
=> CreateRotationTransformMatrixRadians(GeometryUtilities.DegreeToRadian(degrees), size, transformSpace); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates a centered rotation transform matrix using the given rotation in radians and the source size.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="radians">The amount of rotation, in radians.</param>
|
|
|
|
/// <param name="size">The source image size.</param>
|
|
|
|
/// <param name="transformSpace">The <see cref="TransformSpace"/> to use when creating the centered matrix.</param>
|
|
|
|
/// <returns>The <see cref="Matrix3x2"/>.</returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Matrix3x2 CreateRotationTransformMatrixRadians(float radians, Size size) |
|
|
|
=> CreateCenteredTransformMatrix( |
|
|
|
new Rectangle(Point.Empty, size), |
|
|
|
Matrix3x2Extensions.CreateRotation(radians, PointF.Empty)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates a centered rotation bounds matrix using the given rotation in degrees and the source size.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="degrees">The amount of rotation, in degrees.</param>
|
|
|
|
/// <param name="size">The source image size.</param>
|
|
|
|
/// <returns>The <see cref="Matrix3x2"/>.</returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Matrix3x2 CreateRotationBoundsMatrixDegrees(float degrees, Size size) |
|
|
|
=> CreateCenteredBoundsMatrix( |
|
|
|
new Rectangle(Point.Empty, size), |
|
|
|
Matrix3x2Extensions.CreateRotationDegrees(degrees, PointF.Empty)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates a centered rotation bounds matrix using the given rotation in radians and the source size.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="radians">The amount of rotation, in radians.</param>
|
|
|
|
/// <param name="size">The source image size.</param>
|
|
|
|
/// <returns>The <see cref="Matrix3x2"/>.</returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Matrix3x2 CreateRotationBoundsMatrixRadians(float radians, Size size) |
|
|
|
=> CreateCenteredBoundsMatrix( |
|
|
|
new Rectangle(Point.Empty, size), |
|
|
|
Matrix3x2Extensions.CreateRotation(radians, PointF.Empty)); |
|
|
|
public static Matrix3x2 CreateRotationTransformMatrixRadians(float radians, Size size, TransformSpace transformSpace) |
|
|
|
=> CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateRotation(radians, PointF.Empty), size, transformSpace); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates a centered skew transform matrix from the give angles in degrees and the source size.
|
|
|
|
@ -127,12 +106,11 @@ internal static class TransformUtils |
|
|
|
/// <param name="degreesX">The X angle, in degrees.</param>
|
|
|
|
/// <param name="degreesY">The Y angle, in degrees.</param>
|
|
|
|
/// <param name="size">The source image size.</param>
|
|
|
|
/// <param name="transformSpace">The <see cref="TransformSpace"/> to use when creating the centered matrix.</param>
|
|
|
|
/// <returns>The <see cref="Matrix3x2"/>.</returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Matrix3x2 CreateSkewTransformMatrixDegrees(float degreesX, float degreesY, Size size) |
|
|
|
=> CreateCenteredTransformMatrix( |
|
|
|
new Rectangle(Point.Empty, size), |
|
|
|
Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty)); |
|
|
|
public static Matrix3x2 CreateSkewTransformMatrixDegrees(float degreesX, float degreesY, Size size, TransformSpace transformSpace) |
|
|
|
=> CreateSkewTransformMatrixRadians(GeometryUtilities.DegreeToRadian(degreesX), GeometryUtilities.DegreeToRadian(degreesY), size, transformSpace); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates a centered skew transform matrix from the give angles in radians and the source size.
|
|
|
|
@ -140,81 +118,37 @@ internal static class TransformUtils |
|
|
|
/// <param name="radiansX">The X angle, in radians.</param>
|
|
|
|
/// <param name="radiansY">The Y angle, in radians.</param>
|
|
|
|
/// <param name="size">The source image size.</param>
|
|
|
|
/// <param name="transformSpace">The <see cref="TransformSpace"/> to use when creating the centered matrix.</param>
|
|
|
|
/// <returns>The <see cref="Matrix3x2"/>.</returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Matrix3x2 CreateSkewTransformMatrixRadians(float radiansX, float radiansY, Size size) |
|
|
|
=> CreateCenteredTransformMatrix( |
|
|
|
new Rectangle(Point.Empty, size), |
|
|
|
Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates a centered skew bounds matrix from the give angles in degrees and the source size.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="degreesX">The X angle, in degrees.</param>
|
|
|
|
/// <param name="degreesY">The Y angle, in degrees.</param>
|
|
|
|
/// <param name="size">The source image size.</param>
|
|
|
|
/// <returns>The <see cref="Matrix3x2"/>.</returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Matrix3x2 CreateSkewBoundsMatrixDegrees(float degreesX, float degreesY, Size size) |
|
|
|
=> CreateCenteredBoundsMatrix( |
|
|
|
new Rectangle(Point.Empty, size), |
|
|
|
Matrix3x2Extensions.CreateSkewDegrees(degreesX, degreesY, PointF.Empty)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Creates a centered skew bounds matrix from the give angles in radians and the source size.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="radiansX">The X angle, in radians.</param>
|
|
|
|
/// <param name="radiansY">The Y angle, in radians.</param>
|
|
|
|
/// <param name="size">The source image size.</param>
|
|
|
|
/// <returns>The <see cref="Matrix3x2"/>.</returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Matrix3x2 CreateSkewBoundsMatrixRadians(float radiansX, float radiansY, Size size) |
|
|
|
=> CreateCenteredBoundsMatrix( |
|
|
|
new Rectangle(Point.Empty, size), |
|
|
|
Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty)); |
|
|
|
public static Matrix3x2 CreateSkewTransformMatrixRadians(float radiansX, float radiansY, Size size, TransformSpace transformSpace) |
|
|
|
=> CreateCenteredTransformMatrix(Matrix3x2Extensions.CreateSkew(radiansX, radiansY, PointF.Empty), size, transformSpace); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the centered transform matrix based upon the source rectangle.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="sourceRectangle">The source image bounds.</param>
|
|
|
|
/// <param name="matrix">The transformation matrix.</param>
|
|
|
|
/// <param name="size">The source image size.</param>
|
|
|
|
/// <param name="transformSpace">
|
|
|
|
/// The <see cref="TransformSpace"/> to use when creating the centered matrix.
|
|
|
|
/// </param>
|
|
|
|
/// <returns>The <see cref="Matrix3x2"/></returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Matrix3x2 CreateCenteredTransformMatrix(Rectangle sourceRectangle, Matrix3x2 matrix) |
|
|
|
public static Matrix3x2 CreateCenteredTransformMatrix(Matrix3x2 matrix, Size size, TransformSpace transformSpace) |
|
|
|
{ |
|
|
|
Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix); |
|
|
|
Size transformSize = GetUnboundedTransformedSize(matrix, size, transformSpace); |
|
|
|
|
|
|
|
// We invert the matrix to handle the transformation from screen to world space.
|
|
|
|
// This ensures scaling matrices are correct.
|
|
|
|
Matrix3x2.Invert(matrix, out Matrix3x2 inverted); |
|
|
|
|
|
|
|
// Centered transforms must be 0 based so we offset the bounds width and height.
|
|
|
|
Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-(destinationRectangle.Width - 1), -(destinationRectangle.Height - 1)) * .5F); |
|
|
|
Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width - 1, sourceRectangle.Height - 1) * .5F); |
|
|
|
|
|
|
|
// Translate back to world space.
|
|
|
|
Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered); |
|
|
|
// The source size is provided using the coordinate space of the source image.
|
|
|
|
// however the transform should always be applied in the pixel space.
|
|
|
|
// To account for this we offset by the size - 1 to translate to the pixel space.
|
|
|
|
float offset = transformSpace == TransformSpace.Pixel ? 1F : 0F; |
|
|
|
|
|
|
|
return centered; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the centered bounds matrix based upon the source rectangle.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="sourceRectangle">The source image bounds.</param>
|
|
|
|
/// <param name="matrix">The transformation matrix.</param>
|
|
|
|
/// <returns>The <see cref="Matrix3x2"/></returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Matrix3x2 CreateCenteredBoundsMatrix(Rectangle sourceRectangle, Matrix3x2 matrix) |
|
|
|
{ |
|
|
|
Rectangle destinationRectangle = GetTransformedBoundingRectangle(sourceRectangle, matrix); |
|
|
|
|
|
|
|
// We invert the matrix to handle the transformation from screen to world space.
|
|
|
|
// This ensures scaling matrices are correct.
|
|
|
|
Matrix3x2.Invert(matrix, out Matrix3x2 inverted); |
|
|
|
|
|
|
|
Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-destinationRectangle.Width, -destinationRectangle.Height) * .5F); |
|
|
|
Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(sourceRectangle.Width, sourceRectangle.Height) * .5F); |
|
|
|
Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(new Vector2(-(transformSize.Width - offset), -(transformSize.Height - offset)) * .5F); |
|
|
|
Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(new Vector2(size.Width - offset, size.Height - offset) * .5F); |
|
|
|
|
|
|
|
// Translate back to world space.
|
|
|
|
Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered); |
|
|
|
@ -345,52 +279,100 @@ internal static class TransformUtils |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Returns the rectangle bounds relative to the source for the given transformation matrix.
|
|
|
|
/// Returns the size relative to the source for the given transformation matrix.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="rectangle">The source rectangle.</param>
|
|
|
|
/// <param name="matrix">The transformation matrix.</param>
|
|
|
|
/// <returns>
|
|
|
|
/// The <see cref="Rectangle"/>.
|
|
|
|
/// </returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Rectangle GetTransformedBoundingRectangle(Rectangle rectangle, Matrix3x2 matrix) |
|
|
|
{ |
|
|
|
Rectangle transformed = GetTransformedRectangle(rectangle, matrix); |
|
|
|
return new Rectangle(0, 0, transformed.Width, transformed.Height); |
|
|
|
} |
|
|
|
/// <param name="size">The source size.</param>
|
|
|
|
/// <param name="transformSpace">The <see cref="TransformSpace"/> to use when calculating the size.</param>
|
|
|
|
/// <returns>The <see cref="Size"/>.</returns>
|
|
|
|
public static Size GetTransformedSize(Matrix3x2 matrix, Size size, TransformSpace transformSpace) |
|
|
|
=> GetTransformedSize(matrix, size, transformSpace, true); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Returns the rectangle relative to the source for the given transformation matrix.
|
|
|
|
/// Returns the size relative to the source for the given transformation matrix.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="rectangle">The source rectangle.</param>
|
|
|
|
/// <param name="matrix">The transformation matrix.</param>
|
|
|
|
/// <param name="size">The source size.</param>
|
|
|
|
/// <returns>
|
|
|
|
/// The <see cref="Rectangle"/>.
|
|
|
|
/// The <see cref="Size"/>.
|
|
|
|
/// </returns>
|
|
|
|
public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix3x2 matrix) |
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Size GetTransformedSize(Matrix4x4 matrix, Size size) |
|
|
|
{ |
|
|
|
if (rectangle.Equals(default) || Matrix3x2.Identity.Equals(matrix)) |
|
|
|
Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); |
|
|
|
|
|
|
|
if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) |
|
|
|
{ |
|
|
|
return rectangle; |
|
|
|
return size; |
|
|
|
} |
|
|
|
|
|
|
|
Vector2 tl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); |
|
|
|
Vector2 tr = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); |
|
|
|
Vector2 bl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); |
|
|
|
Vector2 br = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); |
|
|
|
// Check if the matrix involves only affine transformations by inspecting the relevant components.
|
|
|
|
// We want to use pixel space for calculations only if the transformation is purely 2D and does not include
|
|
|
|
// any perspective effects, non-standard scaling, or unusual translations that could distort the image.
|
|
|
|
// The conditions are as follows:
|
|
|
|
bool usePixelSpace = |
|
|
|
|
|
|
|
// 1. Ensure there's no perspective distortion:
|
|
|
|
// M34 corresponds to the perspective component. For a purely 2D affine transformation, this should be 0.
|
|
|
|
(matrix.M34 == 0) && |
|
|
|
|
|
|
|
// 2. Ensure standard affine transformation without any unusual depth or perspective scaling:
|
|
|
|
// M44 should be 1 for a standard affine transformation. If M44 is not 1, it indicates non-standard depth
|
|
|
|
// scaling or perspective, which suggests a more complex transformation.
|
|
|
|
(matrix.M44 == 1) && |
|
|
|
|
|
|
|
// 3. Ensure no unusual translation in the x-direction:
|
|
|
|
// M14 represents translation in the x-direction that might be part of a more complex transformation.
|
|
|
|
// For standard affine transformations, M14 should be 0.
|
|
|
|
(matrix.M14 == 0) && |
|
|
|
|
|
|
|
return GetBoundingRectangle(tl, tr, bl, br); |
|
|
|
// 4. Ensure no unusual translation in the y-direction:
|
|
|
|
// M24 represents translation in the y-direction that might be part of a more complex transformation.
|
|
|
|
// For standard affine transformations, M24 should be 0.
|
|
|
|
(matrix.M24 == 0); |
|
|
|
|
|
|
|
// Define an offset size to translate between pixel space and coordinate space.
|
|
|
|
// When using pixel space, apply a scaling sensitive offset to translate to discrete pixel coordinates.
|
|
|
|
// When not using pixel space, use SizeF.Empty as the offset.
|
|
|
|
|
|
|
|
// Compute scaling factors from the matrix
|
|
|
|
float scaleX = 1F / new Vector2(matrix.M11, matrix.M21).Length(); // sqrt(M11^2 + M21^2)
|
|
|
|
float scaleY = 1F / new Vector2(matrix.M12, matrix.M22).Length(); // sqrt(M12^2 + M22^2)
|
|
|
|
|
|
|
|
// Apply the offset relative to the scale
|
|
|
|
SizeF offsetSize = usePixelSpace ? new SizeF(scaleX, scaleY) : SizeF.Empty; |
|
|
|
|
|
|
|
// Subtract the offset size to translate to the appropriate space (pixel or coordinate).
|
|
|
|
if (TryGetTransformedRectangle(new RectangleF(Point.Empty, size - offsetSize), matrix, out Rectangle bounds)) |
|
|
|
{ |
|
|
|
// Add the offset size back to translate the transformed bounds to the correct space.
|
|
|
|
return Size.Ceiling(ConstrainSize(bounds) + offsetSize); |
|
|
|
} |
|
|
|
|
|
|
|
return size; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Returns the size relative to the source for the given transformation matrix.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="matrix">The transformation matrix.</param>
|
|
|
|
/// <param name="size">The source size.</param>
|
|
|
|
/// <param name="transformSpace">The <see cref="TransformSpace"/> to use when calculating the size.</param>
|
|
|
|
/// <returns>The <see cref="Size"/>.</returns>
|
|
|
|
private static Size GetUnboundedTransformedSize(Matrix3x2 matrix, Size size, TransformSpace transformSpace) |
|
|
|
=> GetTransformedSize(matrix, size, transformSpace, false); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Returns the size relative to the source for the given transformation matrix.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="matrix">The transformation matrix.</param>
|
|
|
|
/// <param name="size">The source size.</param>
|
|
|
|
/// <param name="transformSpace">The <see cref="TransformSpace"/> to use when calculating the size.</param>
|
|
|
|
/// <param name="constrain">Whether to constrain the size to ensure that the dimensions are positive.</param>
|
|
|
|
/// <returns>
|
|
|
|
/// The <see cref="Size"/>.
|
|
|
|
/// </returns>
|
|
|
|
public static Size GetTransformedSize(Size size, Matrix3x2 matrix) |
|
|
|
private static Size GetTransformedSize(Matrix3x2 matrix, Size size, TransformSpace transformSpace, bool constrain) |
|
|
|
{ |
|
|
|
Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); |
|
|
|
|
|
|
|
@ -399,9 +381,24 @@ internal static class TransformUtils |
|
|
|
return size; |
|
|
|
} |
|
|
|
|
|
|
|
Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); |
|
|
|
// Define an offset size to translate between coordinate space and pixel space.
|
|
|
|
// Compute scaling factors from the matrix
|
|
|
|
SizeF offsetSize = SizeF.Empty; |
|
|
|
if (transformSpace == TransformSpace.Pixel) |
|
|
|
{ |
|
|
|
float scaleX = 1F / new Vector2(matrix.M11, matrix.M21).Length(); // sqrt(M11^2 + M21^2)
|
|
|
|
float scaleY = 1F / new Vector2(matrix.M12, matrix.M22).Length(); // sqrt(M12^2 + M22^2)
|
|
|
|
offsetSize = new(scaleX, scaleY); |
|
|
|
} |
|
|
|
|
|
|
|
// Subtract the offset size to translate to the pixel space.
|
|
|
|
if (TryGetTransformedRectangle(new RectangleF(Point.Empty, size - offsetSize), matrix, out Rectangle bounds)) |
|
|
|
{ |
|
|
|
// Add the offset size back to translate the transformed bounds to the coordinate space.
|
|
|
|
return Size.Ceiling((constrain ? ConstrainSize(bounds) : bounds.Size) + offsetSize); |
|
|
|
} |
|
|
|
|
|
|
|
return ConstrainSize(rectangle); |
|
|
|
return size; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -409,46 +406,52 @@ internal static class TransformUtils |
|
|
|
/// </summary>
|
|
|
|
/// <param name="rectangle">The source rectangle.</param>
|
|
|
|
/// <param name="matrix">The transformation matrix.</param>
|
|
|
|
/// <param name="bounds">The resulting bounding rectangle.</param>
|
|
|
|
/// <returns>
|
|
|
|
/// The <see cref="Rectangle"/>.
|
|
|
|
/// <see langword="true"/> if the transformation was successful; otherwise, <see langword="false"/>.
|
|
|
|
/// </returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Rectangle GetTransformedRectangle(Rectangle rectangle, Matrix4x4 matrix) |
|
|
|
private static bool TryGetTransformedRectangle(RectangleF rectangle, Matrix3x2 matrix, out Rectangle bounds) |
|
|
|
{ |
|
|
|
if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix)) |
|
|
|
if (rectangle.Equals(default) || Matrix3x2.Identity.Equals(matrix)) |
|
|
|
{ |
|
|
|
return rectangle; |
|
|
|
bounds = default; |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
Vector2 tl = ProjectiveTransform2D(rectangle.Left, rectangle.Top, matrix); |
|
|
|
Vector2 tr = ProjectiveTransform2D(rectangle.Right, rectangle.Top, matrix); |
|
|
|
Vector2 bl = ProjectiveTransform2D(rectangle.Left, rectangle.Bottom, matrix); |
|
|
|
Vector2 br = ProjectiveTransform2D(rectangle.Right, rectangle.Bottom, matrix); |
|
|
|
Vector2 tl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Top), matrix); |
|
|
|
Vector2 tr = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Top), matrix); |
|
|
|
Vector2 bl = Vector2.Transform(new Vector2(rectangle.Left, rectangle.Bottom), matrix); |
|
|
|
Vector2 br = Vector2.Transform(new Vector2(rectangle.Right, rectangle.Bottom), matrix); |
|
|
|
|
|
|
|
return GetBoundingRectangle(tl, tr, bl, br); |
|
|
|
bounds = GetBoundingRectangle(tl, tr, bl, br); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Returns the size relative to the source for the given transformation matrix.
|
|
|
|
/// Returns the rectangle relative to the source for the given transformation matrix.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="size">The source size.</param>
|
|
|
|
/// <param name="rectangle">The source rectangle.</param>
|
|
|
|
/// <param name="matrix">The transformation matrix.</param>
|
|
|
|
/// <param name="bounds">The resulting bounding rectangle.</param>
|
|
|
|
/// <returns>
|
|
|
|
/// The <see cref="Size"/>.
|
|
|
|
/// <see langword="true"/> if the transformation was successful; otherwise, <see langword="false"/>.
|
|
|
|
/// </returns>
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
public static Size GetTransformedSize(Size size, Matrix4x4 matrix) |
|
|
|
private static bool TryGetTransformedRectangle(RectangleF rectangle, Matrix4x4 matrix, out Rectangle bounds) |
|
|
|
{ |
|
|
|
Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); |
|
|
|
|
|
|
|
if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) |
|
|
|
if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix)) |
|
|
|
{ |
|
|
|
return size; |
|
|
|
bounds = default; |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
Rectangle rectangle = GetTransformedRectangle(new Rectangle(Point.Empty, size), matrix); |
|
|
|
Vector2 tl = ProjectiveTransform2D(rectangle.Left, rectangle.Top, matrix); |
|
|
|
Vector2 tr = ProjectiveTransform2D(rectangle.Right, rectangle.Top, matrix); |
|
|
|
Vector2 bl = ProjectiveTransform2D(rectangle.Left, rectangle.Bottom, matrix); |
|
|
|
Vector2 br = ProjectiveTransform2D(rectangle.Right, rectangle.Bottom, matrix); |
|
|
|
|
|
|
|
return ConstrainSize(rectangle); |
|
|
|
bounds = GetBoundingRectangle(tl, tr, bl, br); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
|
|
@ -482,6 +485,11 @@ internal static class TransformUtils |
|
|
|
float right = MathF.Max(tl.X, MathF.Max(tr.X, MathF.Max(bl.X, br.X))); |
|
|
|
float bottom = MathF.Max(tl.Y, MathF.Max(tr.Y, MathF.Max(bl.Y, br.Y))); |
|
|
|
|
|
|
|
return Rectangle.Round(RectangleF.FromLTRB(left, top, right, bottom)); |
|
|
|
// Clamp the values to the nearest whole pixel.
|
|
|
|
return Rectangle.FromLTRB( |
|
|
|
(int)Math.Floor(left), |
|
|
|
(int)Math.Floor(top), |
|
|
|
(int)Math.Ceiling(right), |
|
|
|
(int)Math.Ceiling(bottom)); |
|
|
|
} |
|
|
|
} |
|
|
|
|