@ -68,6 +68,11 @@ internal static class TransformUtils
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 ProjectiveTransform2D ( float x , float y , Matrix4x4 matrix )
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 ;
const float epsilon = 0.0000001F ;
Vector4 v4 = Vector4 . Transform ( new Vector4 ( x , y , 0 , 1F ) , matrix ) ;
Vector4 v4 = Vector4 . Transform ( new Vector4 ( x , y , 0 , 1F ) , matrix ) ;
return new Vector2 ( v4 . X , v4 . Y ) / MathF . Max ( v4 . W , epsilon ) ;
return new Vector2 ( v4 . X , v4 . Y ) / MathF . Max ( v4 . W , epsilon ) ;
@ -78,48 +83,22 @@ internal static class TransformUtils
/// </summary>
/// </summary>
/// <param name="degrees">The amount of rotation, in degrees.</param>
/// <param name="degrees">The amount of rotation, in degrees.</param>
/// <param name="size">The source image size.</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>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateRotationTransformMatrixDegrees ( float degrees , Size size )
public static Matrix3x2 CreateRotationTransformMatrixDegrees ( float degrees , Size size , TransformSpace transformSpace )
= > CreateCenteredTransformMatrix (
= > CreateRotationTransformMatrixRadians ( GeometryUtilities . DegreeToRadian ( degrees ) , size , transformSpace ) ;
new Rectangle ( Point . Empty , size ) ,
Matrix3x2Extensions . CreateRotationDegrees ( degrees , PointF . Empty ) ) ;
/// <summary>
/// <summary>
/// Creates a centered rotation transform matrix using the given rotation in radians and the source size.
/// Creates a centered rotation transform matrix using the given rotation in radians and the source size.
/// </summary>
/// </summary>
/// <param name="radians">The amount of rotation, in radians.</param>
/// <param name="radians">The amount of rotation, in radians.</param>
/// <param name="size">The source image size.</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>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateRotationTransformMatrixRadians ( float radians , Size size )
public static Matrix3x2 CreateRotationTransformMatrixRadians ( float radians , Size size , TransformSpace transformSpace )
= > CreateCenteredTransformMatrix (
= > CreateCenteredTransformMatrix ( Matrix3x2Extensions . CreateRotation ( radians , PointF . Empty ) , size , transformSpace ) ;
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 ) ) ;
/// <summary>
/// <summary>
/// Creates a centered skew transform matrix from the give angles in degrees and the source size.
/// 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="degreesX">The X angle, in degrees.</param>
/// <param name="degreesY">The Y angle, in degrees.</param>
/// <param name="degreesY">The Y angle, in degrees.</param>
/// <param name="size">The source image size.</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>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateSkewTransformMatrixDegrees ( float degreesX , float degreesY , Size size )
public static Matrix3x2 CreateSkewTransformMatrixDegrees ( float degreesX , float degreesY , Size size , TransformSpace transformSpace )
= > CreateCenteredTransformMatrix (
= > CreateSkewTransformMatrixRadians ( GeometryUtilities . DegreeToRadian ( degreesX ) , GeometryUtilities . DegreeToRadian ( degreesY ) , size , transformSpace ) ;
new Rectangle ( Point . Empty , size ) ,
Matrix3x2Extensions . CreateSkewDegrees ( degreesX , degreesY , PointF . Empty ) ) ;
/// <summary>
/// <summary>
/// Creates a centered skew transform matrix from the give angles in radians and the source size.
/// 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="radiansX">The X angle, in radians.</param>
/// <param name="radiansY">The Y angle, in radians.</param>
/// <param name="radiansY">The Y angle, in radians.</param>
/// <param name="size">The source image size.</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>
/// <returns>The <see cref="Matrix3x2"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Matrix3x2 CreateSkewTransformMatrixRadians ( float radiansX , float radiansY , Size size )
public static Matrix3x2 CreateSkewTransformMatrixRadians ( float radiansX , float radiansY , Size size , TransformSpace transformSpace )
= > CreateCenteredTransformMatrix (
= > CreateCenteredTransformMatrix ( Matrix3x2Extensions . CreateSkew ( radiansX , radiansY , PointF . Empty ) , size , transformSpace ) ;
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 ) ) ;
/// <summary>
/// <summary>
/// Gets the centered transform matrix based upon the source rectangle.
/// Gets the centered transform matrix based upon the source rectangle.
/// </summary>
/// </summary>
/// <param name="sourceRectangle">The source image bounds.</param>
/// <param name="matrix">The transformation matrix.</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>
/// <returns>The <see cref="Matrix3x2"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[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.
// We invert the matrix to handle the transformation from screen to world space.
// This ensures scaling matrices are correct.
// This ensures scaling matrices are correct.
Matrix3x2 . Invert ( matrix , out Matrix3x2 inverted ) ;
Matrix3x2 . Invert ( matrix , out Matrix3x2 inverted ) ;
// Centered transforms must be 0 based so we offset the bounds width and height.
// The source size is provided using the coordinate space of the source image.
Matrix3x2 translationToTargetCenter = Matrix3x2 . CreateTranslation ( new Vector2 ( - ( destinationRectangle . Width - 1 ) , - ( destinationRectangle . Height - 1 ) ) * . 5F ) ;
// however the transform should always be applied in the pixel space.
Matrix3x2 translateToSourceCenter = Matrix3x2 . CreateTranslation ( new Vector2 ( sourceRectangle . Width - 1 , sourceRectangle . Height - 1 ) * . 5F ) ;
// To account for this we offset by the size - 1 to translate to the pixel space.
float offset = transformSpace = = TransformSpace . Pixel ? 1F : 0F ;
// Translate back to world space.
Matrix3x2 . Invert ( translationToTargetCenter * inverted * translateToSourceCenter , out Matrix3x2 centered ) ;
return centered ;
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 ) ;
/// <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 ) ;
// Translate back to world space.
// Translate back to world space.
Matrix3x2 . Invert ( translationToTargetCenter * inverted * translateToSourceCenter , out Matrix3x2 centered ) ;
Matrix3x2 . Invert ( translationToTargetCenter * inverted * translateToSourceCenter , out Matrix3x2 centered ) ;
@ -345,52 +279,100 @@ internal static class TransformUtils
}
}
/// <summary>
/// <summary>
/// Returns the rectangle bound s relative to the source for the given transformation matrix.
/// Returns the size relative to the source for the given transformation matrix.
/// </summary>
/// </summary>
/// <param name="rectangle">The source rectangle.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>
/// <param name="size">The source size.</param>
/// The <see cref="Rectangle"/>.
/// <param name="transformSpace">The <see cref="TransformSpace"/> to use when calculating the size.</param>
/// </returns>
/// <returns>The <see cref="Size"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size GetTransformedSize ( Matrix3x2 matrix , Size size , TransformSpace transformSpace )
public static Rectangle GetTransformedBoundingRectangle ( Rectangle rectangle , Matrix3x2 matrix )
= > GetTransformedSize ( matrix , size , transformSpace , true ) ;
{
Rectangle transformed = GetTransformedRectangle ( rectangle , matrix ) ;
return new Rectangle ( 0 , 0 , transformed . Width , transformed . Height ) ;
}
/// <summary>
/// <summary>
/// Returns the rectangl e relative to the source for the given transformation matrix.
/// Returns the size relative to the source for the given transformation matrix.
/// </summary>
/// </summary>
/// <param name="rectangle">The source rectangle.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <param name="size">The source size.</param>
/// <returns>
/// <returns>
/// The <see cref="Rectangl e"/>.
/// The <see cref="Siz e"/>.
/// </returns>
/// </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 siz e;
}
}
Vector2 tl = Vector2 . Transform ( new Vector2 ( rectangle . Left , rectangle . Top ) , matrix ) ;
// Check if the matrix involves only affine transformations by inspecting the relevant components.
Vector2 tr = Vector2 . Transform ( new Vector2 ( rectangle . Right , rectangle . Top ) , matrix ) ;
// We want to use pixel space for calculations only if the transformation is purely 2D and does not include
Vector2 bl = Vector2 . Transform ( new Vector2 ( rectangle . Left , rectangle . Bottom ) , matrix ) ;
// any perspective effects, non-standard scaling, or unusual translations that could distort the image.
Vector2 br = Vector2 . Transform ( new Vector2 ( rectangle . Right , rectangle . Bottom ) , matrix ) ;
// 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>
/// <summary>
/// Returns the size relative to the source for the given transformation matrix.
/// Returns the size relative to the source for the given transformation matrix.
/// </summary>
/// </summary>
/// <param name="matrix">The transformation matrix.</param>
/// <param name="size">The source size.</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="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>
/// <returns>
/// The <see cref="Size"/>.
/// The <see cref="Size"/>.
/// </returns>
/// </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!" ) ;
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 ;
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>
/// <summary>
@ -409,46 +406,52 @@ internal static class TransformUtils
/// </summary>
/// </summary>
/// <param name="rectangle">The source rectangle.</param>
/// <param name="rectangle">The source rectangle.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <param name="bounds">The resulting bounding rectangle.</param>
/// <returns>
/// <returns>
/// The <see cref="Rectangl e"/>.
/// <see langword="true"/> if the transformation was successful; otherwise, <see langword="fals e"/>.
/// </returns>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryGetTransformedRectangle ( RectangleF rectangle , Matrix3x2 matrix , out Rectangle bounds )
public static Rectangle GetTransformedRectangle ( Rectangle rectangle , Matrix4x4 matrix )
{
{
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 tl = Vector2 . Transform ( new Vector2 ( rectangle . Left , rectangle . Top ) , matrix ) ;
Vector2 tr = ProjectiveTransform2D ( rectangle . Right , rectangle . Top , matrix ) ;
Vector2 tr = Vector2 . Transform ( new Vector2 ( rectangle . Right , rectangle . Top ) , matrix ) ;
Vector2 bl = ProjectiveTransform2D ( rectangle . Left , rectangle . Bottom , matrix ) ;
Vector2 bl = Vector2 . Transform ( new Vector2 ( rectangle . Left , rectangle . Bottom ) , matrix ) ;
Vector2 br = ProjectiveTransform2D ( rectangle . Right , 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>
/// <summary>
/// Returns the siz e relative to the source for the given transformation matrix.
/// Returns the rectangl e relative to the source for the given transformation matrix.
/// </summary>
/// </summary>
/// <param name="size">The source siz e.</param>
/// <param name="rectangle">The source rectangl e.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <param name="bounds">The resulting bounding rectangle.</param>
/// <returns>
/// <returns>
/// The <see cref="Siz e"/>.
/// <see langword="true"/> if the transformation was successful; otherwise, <see langword="fals e"/>.
/// </returns>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Size GetTransformedSize ( Size siz e , Matrix4x4 matrix )
private static bool TryGetTransformedRectangle ( RectangleF rectangl e , Matrix4x4 matrix , out Rectangle bounds )
{
{
Guard . IsTrue ( size . Width > 0 & & size . Height > 0 , nameof ( size ) , "Source size dimensions cannot be 0!" ) ;
if ( rectangle . Equals ( default ) | | Matrix4x4 . Identity . Equals ( matrix ) )
if ( matrix . Equals ( default ) | | matrix . Equals ( Matrix4x4 . Identity ) )
{
{
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)]
[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 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 ) ) ) ;
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 ) ) ;
}
}
}
}