Skip to content

Add Matrix4x4 transform overloads and corresponding tests#3077

Merged
JimBobSquarePants merged 4 commits intomainfrom
js/transform-enhancements
Mar 10, 2026
Merged

Add Matrix4x4 transform overloads and corresponding tests#3077
JimBobSquarePants merged 4 commits intomainfrom
js/transform-enhancements

Conversation

@JimBobSquarePants
Copy link
Member

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

Description

This pull request introduces support for projective (4x4 matrix) transformations across the Point, PointF, Rectangle, and RectangleF primitives, enabling advanced 2D transformations such as perspective mapping. The changes include new transformation methods, updates to transformation utilities, and comprehensive unit tests to validate both affine and projective scenarios.

4x4 Matrix Transformation Support

  • Added Transform(Point, Matrix4x4) and Transform(PointF, Matrix4x4) methods to Point and PointF respectively, allowing points to be transformed with a projective matrix. [1] [2]
  • Added Transform(Rectangle, Matrix4x4) and Transform(RectangleF, Matrix4x4) methods to Rectangle and RectangleF, enabling rectangle transformations using 4x4 matrices. [1] [2]

Transformation Utility Enhancements

  • Improved documentation and clarified logic in TransformUtilities.ProjectiveTransform2D, explaining how the perspective divide is handled and why negative or zero W values are clamped.

Unit Testing for 4x4 Transformations

  • Added extensive tests for 4x4 matrix transformations in PointTests, PointFTests, RectangleTests, and RectangleFTests, covering affine, identity, translation, scale, and projective cases to ensure correctness and compatibility with existing 3x2 matrix transformations. [1] [2] [3] [4]

Comment on lines +258 to +259
public static PointF Transform(PointF point, Matrix4x4 matrix)
=> TransformUtilities.ProjectiveTransform2D(point.X, point.Y, matrix);
Copy link
Member

@antonfirsov antonfirsov Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an established pattern so nothing we can do probably, but I keep wondering if these methods are expected to be used on hot path anywhere since this translates to 2 copies of Matrix4x4 which is relatively large.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's getting inlined as far as I can see

; Assembly listing for method SixLabors.ImageSharp.PointF:Transform(SixLabors.ImageSharp.PointF,System.Numerics.Matrix3x2):SixLabors.ImageSharp.PointF (FullOpts)
; Emitting BLENDED_CODE for X64 with AVX512 - Windows
; FullOpts code
; optimized code
; rsp based frame
; partially interruptible
; No PGO data
; 0 inlinees with PGO data; 10 single block inlinees; 0 inlinees without PGO data

G_M000_IG01:                ;; offset=0x0000
       push     rax
       vzeroupper 
       mov      qword ptr [rsp+0x10], rcx
 
G_M000_IG02:                ;; offset=0x0009
       vmovss   xmm0, dword ptr [rsp+0x10]
       vmovss   xmm1, dword ptr [rsp+0x14]
       vinsertps xmm0, xmm0, xmm1, 28
       vmovsd   xmm1, qword ptr [rdx]
       vmovsd   xmm2, qword ptr [rdx+0x08]
       vmovsd   xmm3, qword ptr [rdx+0x10]
       vmovaps  xmm4, xmm0
       vbroadcastss xmm4, xmm4
       vmulps   xmm1, xmm1, xmm4
       vmovshdup xmm0, xmm0
       vbroadcastss xmm0, xmm0
       vmulps   xmm0, xmm2, xmm0
       vaddps   xmm0, xmm1, xmm0
       vaddps   xmm0, xmm0, xmm3
       xor      eax, eax
       mov      qword ptr [rsp], rax
       vmovsd   qword ptr [rsp], xmm0
       mov      rax, qword ptr [rsp]
 
G_M000_IG03:                ;; offset=0x005A
       add      rsp, 8
       ret      
 
; Total bytes of code 95

; Assembly listing for method SixLabors.ImageSharp.PointF:Transform(SixLabors.ImageSharp.PointF,System.Numerics.Matrix4x4):SixLabors.ImageSharp.PointF (FullOpts)
; Emitting BLENDED_CODE for X64 with AVX512 - Windows
; FullOpts code
; optimized code
; rsp based frame
; partially interruptible
; No PGO data
; 0 inlinees with PGO data; 10 single block inlinees; 0 inlinees without PGO data

G_M000_IG01:                ;; offset=0x0000
       sub      rsp, 40
       vzeroupper 
       mov      qword ptr [rsp+0x30], rcx
 
G_M000_IG02:                ;; offset=0x000C
       vmovss   xmm0, dword ptr [rsp+0x30]
       vinsertps xmm0, xmm0, dword ptr [rsp+0x34], 20
       vinsertps xmm0, xmm0, dword ptr [reloc @RWD00], 48
       vmovups  xmm1, xmmword ptr [rdx]
       vmovups  xmm2, xmmword ptr [rdx+0x10]
       vmovups  xmm3, xmmword ptr [rdx+0x20]
       vmovups  xmm4, xmmword ptr [rdx+0x30]
       vmovaps  xmm5, xmm0
       vbroadcastss xmm5, xmm5
       vmulps   xmm1, xmm1, xmm5
       vmovshdup xmm5, xmm0
       vbroadcastss xmm5, xmm5
       vmulps   xmm2, xmm2, xmm5
       vaddps   xmm1, xmm1, xmm2
       vunpckhps xmm2, xmm0, xmm0
       vbroadcastss xmm2, xmm2
       vmulps   xmm2, xmm3, xmm2
       vaddps   xmm1, xmm1, xmm2
       vshufps  xmm0, xmm0, xmm0, -1
       vbroadcastss xmm0, xmm0
       vmulps   xmm0, xmm4, xmm0
       vaddps   xmm0, xmm1, xmm0
       vmovaps  xmmword ptr [rsp+0x10], xmm0
       vmovsd   xmm0, qword ptr [rsp+0x10]
       vmovups  xmm1, xmmword ptr [reloc @RWD16]
       vmaxss   xmm1, xmm1, dword ptr [rsp+0x1C]
       vbroadcastss xmm1, xmm1
       vdivps   xmm0, xmm0, xmm1
       xor      eax, eax
       mov      qword ptr [rsp+0x08], rax
       vmovsd   qword ptr [rsp+0x08], xmm0
       mov      rax, qword ptr [rsp+0x08]
 
G_M000_IG03:                ;; offset=0x00AD
       add      rsp, 40
       ret      
 
RWD00  	dq	3F8000003F800000h, 3F8000003F800000h
RWD16  	dq	0000000033D6BF95h, 0000000000000000h

; Total bytes of code 178

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's even inlined here. I think we're good.

/// <summary>
/// Transforms a rectangle by the given 4x4 matrix, applying a projective transform
/// flattened into 2D space.
/// </summary>
/// <param name="rectangle">The source rectangle.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>A transformed rectangle.</returns>
public static RectangleF Transform(Rectangle rectangle, Matrix4x4 matrix)
{
    PointF bottomRight = PointF.Transform(new PointF(rectangle.Right, rectangle.Bottom), matrix);
    PointF topLeft = PointF.Transform(new PointF(rectangle.Location.X, rectangle.Location.Y), matrix);
    return new RectangleF(topLeft, new SizeF(bottomRight - topLeft));
}
; Assembly listing for method SixLabors.ImageSharp.Rectangle:Transform(SixLabors.ImageSharp.Rectangle,System.Numerics.Matrix3x2):SixLabors.ImageSharp.RectangleF (FullOpts)
; Emitting BLENDED_CODE for X64 with AVX512 - Windows
; FullOpts code
; optimized code
; rsp based frame
; partially interruptible
; No PGO data
; 0 inlinees with PGO data; 70 single block inlinees; 0 inlinees without PGO data

G_M000_IG01:                ;; offset=0x0000
       vzeroupper 
 
G_M000_IG02:                ;; offset=0x0003
       mov      eax, dword ptr [rdx]
       mov      r10d, dword ptr [rdx+0x04]
       mov      r9d, dword ptr [rdx+0x08]
       mov      edx, dword ptr [rdx+0x0C]
       vmovsd   xmm0, qword ptr [r8]
       vmovsd   xmm1, qword ptr [r8+0x08]
       vmovsd   xmm2, qword ptr [r8+0x10]
       add      r9d, eax
       add      edx, r10d
       vxorps   xmm3, xmm3, xmm3
       vcvtsi2ss xmm3, xmm3, r9d
       vxorps   xmm4, xmm4, xmm4
       vcvtsi2ss xmm4, xmm4, edx
       vinsertps xmm3, xmm3, xmm4, 28
       vmovaps  xmm4, xmm3
       vbroadcastss xmm4, xmm4
       vmulps   xmm4, xmm0, xmm4
       vmovshdup xmm3, xmm3
       vbroadcastss xmm3, xmm3
       vmulps   xmm3, xmm1, xmm3
       vaddps   xmm3, xmm4, xmm3
       vaddps   xmm3, xmm3, xmm2
       vmovaps  xmm4, xmm3
       vroundss xmm4, xmm4, xmm4, 4
       vcvttss2si  edx, xmm4
       vmovshdup xmm3, xmm3
       vroundss xmm3, xmm3, xmm3, 4
       vcvttss2si  r8d, xmm3
       vxorps   xmm3, xmm3, xmm3
       vcvtsi2ss xmm3, xmm3, edx
       vxorps   xmm4, xmm4, xmm4
       vcvtsi2ss xmm4, xmm4, r8d
       vxorps   xmm5, xmm5, xmm5
       vcvtsi2ss xmm5, xmm5, eax
       vxorps   xmm16, xmm16, xmm16
       vcvtsi2ss xmm16, xmm16, r10d
       vinsertps xmm5, xmm5, xmm16, 28
       vmovaps  xmm16, xmm5
       vbroadcastss xmm16, xmm16
       vmulps   xmm0, xmm0, xmm16
       vmovshdup xmm5, xmm5
       vbroadcastss xmm5, xmm5
       vmulps   xmm1, xmm1, xmm5
       vaddps   xmm0, xmm0, xmm1
       vaddps   xmm0, xmm0, xmm2
       vmovaps  xmm1, xmm0
       vroundss xmm1, xmm1, xmm1, 4
       vcvttss2si  eax, xmm1
       vmovshdup xmm0, xmm0
       vroundss xmm0, xmm0, xmm0, 4
       vcvttss2si  edx, xmm0
       vxorps   xmm0, xmm0, xmm0
       vcvtsi2ss xmm0, xmm0, eax
       vxorps   xmm1, xmm1, xmm1
       vcvtsi2ss xmm1, xmm1, edx
       vsubss   xmm2, xmm3, xmm0
       vsubss   xmm3, xmm4, xmm1
       vmovss   dword ptr [rcx], xmm0
       vmovss   dword ptr [rcx+0x04], xmm1
       vmovss   dword ptr [rcx+0x08], xmm2
       vmovss   dword ptr [rcx+0x0C], xmm3
       mov      rax, rcx
 
G_M000_IG03:                ;; offset=0x0119
       ret      
 
; Total bytes of code 282

; Assembly listing for method SixLabors.ImageSharp.Rectangle:Transform(SixLabors.ImageSharp.Rectangle,System.Numerics.Matrix4x4):SixLabors.ImageSharp.RectangleF (FullOpts)
; Emitting BLENDED_CODE for X64 with AVX512 - Windows
; FullOpts code
; optimized code
; rsp based frame
; partially interruptible
; No PGO data
; 0 inlinees with PGO data; 71 single block inlinees; 0 inlinees without PGO data

G_M000_IG01:                ;; offset=0x0000
       sub      rsp, 72
       vzeroupper 
 
G_M000_IG02:                ;; offset=0x0007
       mov      eax, dword ptr [rdx]
       mov      r10d, dword ptr [rdx+0x04]
       mov      r9d, dword ptr [rdx+0x08]
       mov      edx, dword ptr [rdx+0x0C]
       vmovups  xmm0, xmmword ptr [r8]
       vmovups  xmm1, xmmword ptr [r8+0x10]
       vmovups  xmm2, xmmword ptr [r8+0x20]
       vmovups  xmm3, xmmword ptr [r8+0x30]
       add      r9d, eax
       vxorps   xmm4, xmm4, xmm4
       vcvtsi2ss xmm4, xmm4, r9d
       add      edx, r10d
       vxorps   xmm5, xmm5, xmm5
       vcvtsi2ss xmm5, xmm5, edx
       vinsertps xmm4, xmm4, xmm5, 20
       vmovss   xmm5, dword ptr [reloc @RWD00]
       vinsertps xmm4, xmm4, xmm5, 48
       vmovaps  xmm16, xmm4
       vbroadcastss xmm16, xmm16
       vmulps   xmm16, xmm0, xmm16
       vmovshdup xmm17, xmm4
       vbroadcastss xmm17, xmm17
       vmulps   xmm17, xmm1, xmm17
       vaddps   xmm16, xmm16, xmm17
       vunpckhps xmm17, xmm4, xmm4
       vbroadcastss xmm17, xmm17
       vmulps   xmm17, xmm2, xmm17
       vaddps   xmm16, xmm16, xmm17
       vshufps  xmm4, xmm4, xmm4, -1
       vbroadcastss xmm4, xmm4
       vmulps   xmm4, xmm3, xmm4
       vaddps   xmm4, xmm16, xmm4
       vmovaps  xmmword ptr [rsp+0x30], xmm4
       vmovups  xmm4, xmmword ptr [reloc @RWD16]
       vmaxss   xmm16, xmm4, dword ptr [rsp+0x3C]
       vbroadcastss xmm16, xmm16
       vmovsd   xmm17, qword ptr [rsp+0x30]
       vdivps   xmm16, xmm17, xmm16
       xor      edx, edx
       mov      qword ptr [rsp+0x28], rdx
       vmovsd   qword ptr [rsp+0x28], xmm16
       vmovss   xmm16, dword ptr [rsp+0x28]
       vmovss   xmm17, dword ptr [rsp+0x2C]
       vxorps   xmm18, xmm18, xmm18
       vcvtsi2ss xmm18, xmm18, eax
       vxorps   xmm19, xmm19, xmm19
       vcvtsi2ss xmm19, xmm19, r10d
       vinsertps xmm18, xmm18, xmm19, 20
       vinsertps xmm5, xmm18, xmm5, 48
       vmovaps  xmm18, xmm5
       vbroadcastss xmm18, xmm18
       vmulps   xmm0, xmm0, xmm18
       vmovshdup xmm18, xmm5
       vbroadcastss xmm18, xmm18
       vmulps   xmm1, xmm1, xmm18
       vaddps   xmm0, xmm0, xmm1
       vunpckhps xmm1, xmm5, xmm5
       vbroadcastss xmm1, xmm1
       vmulps   xmm1, xmm2, xmm1
       vaddps   xmm0, xmm0, xmm1
       vshufps  xmm1, xmm5, xmm5, -1
       vbroadcastss xmm1, xmm1
       vmulps   xmm1, xmm3, xmm1
       vaddps   xmm0, xmm0, xmm1
 
G_M000_IG03:                ;; offset=0x0166
       vmovaps  xmmword ptr [rsp+0x10], xmm0
       vmaxss   xmm0, xmm4, dword ptr [rsp+0x1C]
       vbroadcastss xmm0, xmm0
       vmovsd   xmm1, qword ptr [rsp+0x10]
       vdivps   xmm0, xmm1, xmm0
 
G_M000_IG04:                ;; offset=0x0181
       mov      qword ptr [rsp+0x08], rdx
       vmovsd   qword ptr [rsp+0x08], xmm0
       vmovss   xmm0, dword ptr [rsp+0x08]
       vmovss   xmm1, dword ptr [rsp+0x0C]
       vsubss   xmm2, xmm16, xmm0
       vsubss   xmm3, xmm17, xmm1
       vmovss   dword ptr [rcx], xmm0
       vmovss   dword ptr [rcx+0x04], xmm1
       vmovss   dword ptr [rcx+0x08], xmm2
       vmovss   dword ptr [rcx+0x0C], xmm3
       mov      rax, rcx
 
G_M000_IG05:                ;; offset=0x01BA
       add      rsp, 72
       ret      
 
RWD00  	dd	3F800000h		;         1
RWD04  	dd	00000000h, 00000000h, 00000000h
RWD16  	dq	0000000033D6BF95h, 0000000000000000h

; Total bytes of code 447

@JimBobSquarePants JimBobSquarePants merged commit 0d53f96 into main Mar 10, 2026
12 checks passed
@JimBobSquarePants JimBobSquarePants deleted the js/transform-enhancements branch March 10, 2026 02:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants