Repository: SixLabors/ImageSharp (2025-06-12T11:44:37Z)

Issue:
Feature Request: handle Subject* metadata in Resize/Crop/Rotate transforms
### Prerequisites

- [x] I have written a descriptive issue title
- [x] I have verified that I am running the latest version of ImageSharp
- [x] I have verified if the problem exist in both `DEBUG` and `RELEASE` mode
- [x] I have searched [open](https://github.com/SixLabors/ImageSharp/issues) and [closed](https://github.com/SixLabors/ImageSharp/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported

### Description
Right now, when performing transformations that involve changing the pixel dimensions of the image, the Pixel Size metadata is automatically changed to match the new dimensions.

But there's some more metadata tags that hold pixel positional information these are:
- SubjectLocation ( interpreted as a point within the image )
- SubjectArea ( interpreted as a point, circle, or rectangle within the image )

My guess is that these tags are designed to mark the "main subject" within an image, if it's an image taken with a camera, it's probably the point or area used by the camera to focus.

So, if the value is properly set, it can be used for speed up operations like finding a face within an image (assuming the face is the main subject)

But if manually set, it can also be used for other purposes, like to define the "center/offset" of the image, or even the circle/rectangle of the "main element" within the image. In my case, I would like to use this tag to define the "Pivot" of a sprite in videogame asset authoring. Having a "pivot" in the image can be useful in many ways, and mybe, at some point in the future, GraphicsOptions could have an Enum to define the reference coordiante system as "TopLeft" or "SubjectPivot" or something like that.

I have a helper class to simplify access to these tags [here](https://github.com/vpenades/UberFactory/blob/master/src/Epsylon.ImageSharp.Procedural/SubjectInfo.cs).

My request is that, when performing operations such as Resize, Crop, Rotate, etc, the Subject* tags are adjusted accordingly, so the area represented by the Subject stays meaningful.

I agree some transforms might be diffucult or impossible to perform, so as fallback solutions I propose:
- If the transformation is impossible to resolve, remove the tags.
- If the transformation can be partially solved, fit the result as much as possible, for example, if SubjectArea represents a rectangle, and we rotate the image 45 degrees, we could replace the rectangle with a circle, which is also supported by SubjectArea.

I agree that this is a group of metadata tags that are rarely used (I had a hard time finding official examples, [found this](https://github.com/ianare/exif-samples/blob/master/jpg/Konica_Minolta_DiMAGE_Z3.jpg), which, btw, has the subject area outside the image due to an image resize) , so maybe an intermediate solution would be to have some sort of callback.


<!-- Thanks for reporting the issue to ImageSharp! -->



Discussion:
@vpenades It's been a few years....

Is this something you're still interested in?
hi, sorry for late reply, busy as usual...

I haven't touched the topic in a few years, but the issue is fairly straightforward to understand:   there's some metadata in images that references the current image in its current dimension, for example the ROI metadata.  If the image is manipulated in some way, like resized or cropped, the ROI stored in the metadata is no longer valid.

The only way I could think to fix this is that the metadata contains some kind of interface or listener that is called whenever the image is transformed in some way.

It's the kind of stuff that's easy to say but extremely difficult to do, specially taking into account the heterogeneous nature of metadata.

So I would tag this feature request as a "nice to have, but not worth the development time".

On the other side, I would try to improve the edition and manipulation of metadata as much as possible, because I think it's an underexploited feature of images.


It's happening.... 

![Image](https://github.com/user-attachments/assets/b0e2c921-918e-467a-adb7-9c072a1a8c15)

![Image](https://github.com/user-attachments/assets/b44c3b70-63de-41ed-9bef-93398497c5d7)
That's great!!

This is relevant to AI because I think images metadata is heavily underused; typically it's relied on external metadata files that can be easily misaligned with the actual content of the image.  And most vision models require the image to be resized just before sending it to the model.



Patch:
diff --git a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs
index 1dac74ba3a..3572687c99 100644
--- a/src/ImageSharp/Formats/Bmp/BmpMetadata.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.PixelFormats;
 
 // TODO: Add color table information.
@@ -156,7 +157,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata()
     public BmpMetadata DeepClone() => new(this);
 
     /// <inheritdoc/>
-    public void AfterImageApply<TPixel>(Image<TPixel> destination)
+    public void AfterImageApply<TPixel>(Image<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
         => this.ColorTable = null;
 }
diff --git a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs
index b4f017253d..dbe8183b6d 100644
--- a/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Cur/CurFrameMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.Formats.Bmp;
 using SixLabors.ImageSharp.Formats.Icon;
 using SixLabors.ImageSharp.PixelFormats;
@@ -117,7 +118,7 @@ public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
         };
 
     /// <inheritdoc/>
-    public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
+    public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         float ratioX = destination.Width / (float)source.Width;
diff --git a/src/ImageSharp/Formats/Cur/CurMetadata.cs b/src/ImageSharp/Formats/Cur/CurMetadata.cs
index d8fdb32902..4c4d83dd88 100644
--- a/src/ImageSharp/Formats/Cur/CurMetadata.cs
+++ b/src/ImageSharp/Formats/Cur/CurMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.Formats.Bmp;
 using SixLabors.ImageSharp.Formats.Icon;
 using SixLabors.ImageSharp.PixelFormats;
@@ -148,7 +149,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata()
         };
 
     /// <inheritdoc/>
-    public void AfterImageApply<TPixel>(Image<TPixel> destination)
+    public void AfterImageApply<TPixel>(Image<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
         => this.ColorTable = null;
 
diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs
index 8ea607df6d..f58ee786f3 100644
--- a/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Gif/GifFrameMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Formats.Gif;
@@ -103,7 +104,7 @@ public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
     }
 
     /// <inheritdoc/>
-    public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
+    public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
         => this.LocalColorTable = null;
 
diff --git a/src/ImageSharp/Formats/Gif/GifMetadata.cs b/src/ImageSharp/Formats/Gif/GifMetadata.cs
index 77f600633b..978209b23b 100644
--- a/src/ImageSharp/Formats/Gif/GifMetadata.cs
+++ b/src/ImageSharp/Formats/Gif/GifMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Formats.Gif;
@@ -105,7 +106,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata()
         };
 
     /// <inheritdoc/>
-    public void AfterImageApply<TPixel>(Image<TPixel> destination)
+    public void AfterImageApply<TPixel>(Image<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
         => this.GlobalColorTable = null;
 
diff --git a/src/ImageSharp/Formats/IFormatFrameMetadata.cs b/src/ImageSharp/Formats/IFormatFrameMetadata.cs
index 261cc12639..68959272c5 100644
--- a/src/ImageSharp/Formats/IFormatFrameMetadata.cs
+++ b/src/ImageSharp/Formats/IFormatFrameMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Formats;
@@ -22,7 +23,8 @@ public interface IFormatFrameMetadata : IDeepCloneable
     /// <typeparam name="TPixel">The type of pixel format.</typeparam>
     /// <param name="source">The source image frame.</param>
     /// <param name="destination">The destination image frame.</param>
-    public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
+    /// <param name="matrix">The transformation matrix applied to the image frame.</param>
+    public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>;
 }
 
diff --git a/src/ImageSharp/Formats/IFormatMetadata.cs b/src/ImageSharp/Formats/IFormatMetadata.cs
index 3142b465cb..81b3449352 100644
--- a/src/ImageSharp/Formats/IFormatMetadata.cs
+++ b/src/ImageSharp/Formats/IFormatMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Formats;
@@ -27,7 +28,8 @@ public interface IFormatMetadata : IDeepCloneable
     /// </summary>
     /// <typeparam name="TPixel">The type of pixel format.</typeparam>
     /// <param name="destination">The destination image .</param>
-    public void AfterImageApply<TPixel>(Image<TPixel> destination)
+    /// <param name="matrix">The transformation matrix applied to the image.</param>
+    public void AfterImageApply<TPixel>(Image<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>;
 }
 
diff --git a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs
index 266a3457a7..fb42d60437 100644
--- a/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Ico/IcoFrameMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.Formats.Bmp;
 using SixLabors.ImageSharp.Formats.Icon;
 using SixLabors.ImageSharp.PixelFormats;
@@ -110,7 +111,7 @@ public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
         };
 
     /// <inheritdoc/>
-    public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
+    public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         float ratioX = destination.Width / (float)source.Width;
diff --git a/src/ImageSharp/Formats/Ico/IcoMetadata.cs b/src/ImageSharp/Formats/Ico/IcoMetadata.cs
index f8c2ff40f2..ae768d3111 100644
--- a/src/ImageSharp/Formats/Ico/IcoMetadata.cs
+++ b/src/ImageSharp/Formats/Ico/IcoMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.Formats.Bmp;
 using SixLabors.ImageSharp.Formats.Icon;
 using SixLabors.ImageSharp.PixelFormats;
@@ -148,7 +149,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata()
         };
 
     /// <inheritdoc/>
-    public void AfterImageApply<TPixel>(Image<TPixel> destination)
+    public void AfterImageApply<TPixel>(Image<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
         => this.ColorTable = null;
 
diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
index fe4855dc77..c620079bf8 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.Formats.Jpeg.Components;
 using SixLabors.ImageSharp.PixelFormats;
 
@@ -200,7 +201,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata()
         };
 
     /// <inheritdoc/>
-    public void AfterImageApply<TPixel>(Image<TPixel> destination)
+    public void AfterImageApply<TPixel>(Image<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
     {
     }
diff --git a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs
index d852f3c8eb..bef5da6054 100644
--- a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs
+++ b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Formats.Pbm;
@@ -130,7 +131,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata()
         };
 
     /// <inheritdoc/>
-    public void AfterImageApply<TPixel>(Image<TPixel> destination)
+    public void AfterImageApply<TPixel>(Image<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
     {
     }
diff --git a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs
index b2f993da43..701b9af053 100644
--- a/src/ImageSharp/Formats/Png/PngFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Png/PngFrameMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.Formats.Png.Chunks;
 using SixLabors.ImageSharp.PixelFormats;
 
@@ -86,7 +87,7 @@ public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
     }
 
     /// <inheritdoc/>
-    public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
+    public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
     {
     }
diff --git a/src/ImageSharp/Formats/Png/PngMetadata.cs b/src/ImageSharp/Formats/Png/PngMetadata.cs
index 3bdbf00add..a7b730ec9f 100644
--- a/src/ImageSharp/Formats/Png/PngMetadata.cs
+++ b/src/ImageSharp/Formats/Png/PngMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.Formats.Png.Chunks;
 using SixLabors.ImageSharp.PixelFormats;
 
@@ -227,7 +228,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata()
         };
 
     /// <inheritdoc/>
-    public void AfterImageApply<TPixel>(Image<TPixel> destination)
+    public void AfterImageApply<TPixel>(Image<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
         => this.ColorTable = null;
 
diff --git a/src/ImageSharp/Formats/Qoi/QoiMetadata.cs b/src/ImageSharp/Formats/Qoi/QoiMetadata.cs
index e463d511d2..46ed2f210c 100644
--- a/src/ImageSharp/Formats/Qoi/QoiMetadata.cs
+++ b/src/ImageSharp/Formats/Qoi/QoiMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Formats.Qoi;
@@ -89,7 +90,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata()
         };
 
     /// <inheritdoc/>
-    public void AfterImageApply<TPixel>(Image<TPixel> destination)
+    public void AfterImageApply<TPixel>(Image<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
     {
     }
diff --git a/src/ImageSharp/Formats/Tga/TgaMetadata.cs b/src/ImageSharp/Formats/Tga/TgaMetadata.cs
index 8d40f86464..07aec8c3ee 100644
--- a/src/ImageSharp/Formats/Tga/TgaMetadata.cs
+++ b/src/ImageSharp/Formats/Tga/TgaMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Formats.Tga;
@@ -95,7 +96,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata()
         };
 
     /// <inheritdoc/>
-    public void AfterImageApply<TPixel>(Image<TPixel> destination)
+    public void AfterImageApply<TPixel>(Image<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
     {
     }
diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
index 189fee8b0c..d9dfafbcc6 100644
--- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.Formats.Tiff.Constants;
 using SixLabors.ImageSharp.Metadata.Profiles.Exif;
 using SixLabors.ImageSharp.PixelFormats;
@@ -96,7 +97,7 @@ public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
         };
 
     /// <inheritdoc/>
-    public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
+    public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         float ratioX = destination.Width / (float)source.Width;
diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs
index e965fcb4f6..69b03f36fb 100644
--- a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.Formats.Tiff.Constants;
 using SixLabors.ImageSharp.PixelFormats;
 
@@ -181,7 +182,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata()
         };
 
     /// <inheritdoc/>
-    public void AfterImageApply<TPixel>(Image<TPixel> destination)
+    public void AfterImageApply<TPixel>(Image<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
     {
     }
diff --git a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs
index 3f976a6401..8a6b30ab44 100644
--- a/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Webp/WebpFrameMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Formats.Webp;
@@ -66,7 +67,7 @@ public FormatConnectingFrameMetadata ToFormatConnectingFrameMetadata()
         };
 
     /// <inheritdoc/>
-    public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
+    public void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
     {
     }
diff --git a/src/ImageSharp/Formats/Webp/WebpMetadata.cs b/src/ImageSharp/Formats/Webp/WebpMetadata.cs
index 1cdaeadb49..9461acaf7f 100644
--- a/src/ImageSharp/Formats/Webp/WebpMetadata.cs
+++ b/src/ImageSharp/Formats/Webp/WebpMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Formats.Webp;
@@ -146,7 +147,7 @@ public FormatConnectingMetadata ToFormatConnectingMetadata()
         };
 
     /// <inheritdoc/>
-    public void AfterImageApply<TPixel>(Image<TPixel> destination)
+    public void AfterImageApply<TPixel>(Image<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
     {
     }
diff --git a/src/ImageSharp/Metadata/ImageFrameMetadata.cs b/src/ImageSharp/Metadata/ImageFrameMetadata.cs
index b24aa140fc..554afd69aa 100644
--- a/src/ImageSharp/Metadata/ImageFrameMetadata.cs
+++ b/src/ImageSharp/Metadata/ImageFrameMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.Formats;
 using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
 using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@@ -158,16 +159,21 @@ public TFormatFrameMetadata CloneFormatMetadata<TFormatMetadata, TFormatFrameMet
     /// <typeparam name="TPixel">The type of pixel format.</typeparam>
     /// <param name="source">The source image frame.</param>
     /// <param name="destination">The destination image frame.</param>
-    internal void AfterFrameApply<TPixel>(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
+    /// <param name="matrix">The transformation matrix applied to the frame.</param>
+    internal void AfterFrameApply<TPixel>(
+        ImageFrame<TPixel> source,
+        ImageFrame<TPixel> destination,
+        Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         // Always updated using the full frame dimensions.
         // Individual format frame metadata will update with sub region dimensions if appropriate.
         this.ExifProfile?.SyncDimensions(destination.Width, destination.Height);
+        this.ExifProfile?.SyncSubject(destination.Width, destination.Height, matrix);
 
         foreach (KeyValuePair<IImageFormat, IFormatFrameMetadata> meta in this.formatMetadata)
         {
-            meta.Value.AfterFrameApply(source, destination);
+            meta.Value.AfterFrameApply(source, destination, matrix);
         }
     }
 }
diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs
index 1961dbf192..918cf162a5 100644
--- a/src/ImageSharp/Metadata/ImageMetadata.cs
+++ b/src/ImageSharp/Metadata/ImageMetadata.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.Formats;
 using SixLabors.ImageSharp.Metadata.Profiles.Cicp;
 using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@@ -235,14 +236,16 @@ internal void SetFormatMetadata<TFormatMetadata>(IImageFormat<TFormatMetadata> k
     /// </summary>
     /// <typeparam name="TPixel">The type of pixel format.</typeparam>
     /// <param name="destination">The destination image.</param>
-    internal void AfterImageApply<TPixel>(Image<TPixel> destination)
+    /// <param name="matrix">The transformation matrix applied to the image.</param>
+    internal void AfterImageApply<TPixel>(Image<TPixel> destination, Matrix4x4 matrix)
         where TPixel : unmanaged, IPixel<TPixel>
     {
         this.ExifProfile?.SyncDimensions(destination.Width, destination.Height);
+        this.ExifProfile?.SyncSubject(destination.Width, destination.Height, matrix);
 
         foreach (KeyValuePair<IImageFormat, IFormatMetadata> meta in this.formatMetadata)
         {
-            meta.Value.AfterImageApply(destination);
+            meta.Value.AfterImageApply(destination, matrix);
         }
     }
 
diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
index aa987bbe75..d7932f90b6 100644
--- a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
+++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
@@ -2,7 +2,9 @@
 // Licensed under the Six Labors Split License.
 
 using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
 using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing.Processors.Transforms;
 
 namespace SixLabors.ImageSharp.Metadata.Profiles.Exif;
 
@@ -160,11 +162,9 @@ public bool TryCreateThumbnail<TPixel>([NotNullWhen(true)] out Image<TPixel>? im
             return false;
         }
 
-        using (MemoryStream memStream = new(this.data, this.thumbnailOffset, this.thumbnailLength))
-        {
-            image = Image.Load<TPixel>(memStream);
-            return true;
-        }
+        using MemoryStream memStream = new(this.data, this.thumbnailOffset, this.thumbnailLength);
+        image = Image.Load<TPixel>(memStream);
+        return true;
     }
 
     /// <summary>
@@ -266,7 +266,7 @@ public void SetValue<TValueType>(ExifTag<TValueType> tag, TValueType value)
     /// </summary>
     /// <param name="tag">The tag of the Exif value.</param>
     /// <param name="value">The value.</param>
-    /// <exception cref="NotSupportedException">Newly created value is null.</exception>
+    /// <exception cref="NotSupportedException">The newly created value is null.</exception>
     internal void SetValueInternal(ExifTag tag, object? value)
     {
         foreach (IExifValue exifValue in this.Values)
@@ -307,6 +307,60 @@ internal void SyncDimensions(int width, int height)
         }
     }
 
+    internal void SyncSubject(int width, int height, Matrix4x4 matrix)
+    {
+        if (matrix.IsIdentity)
+        {
+            return;
+        }
+
+        if (this.TryGetValue(ExifTag.SubjectLocation, out IExifValue<ushort[]>? location))
+        {
+            if (location.Value?.Length == 2)
+            {
+                Vector2 point = TransformUtils.ProjectiveTransform2D(location.Value[0], location.Value[1], matrix);
+
+                // Ensure the point is within the image dimensions.
+                point = Vector2.Clamp(point, Vector2.Zero, new Vector2(width - 1, height - 1));
+
+                // Floor the point to the nearest pixel.
+                location.Value[0] = (ushort)Math.Floor(point.X);
+                location.Value[1] = (ushort)Math.Floor(point.Y);
+
+                this.SetValue(ExifTag.SubjectLocation, location.Value);
+            }
+            else
+            {
+                this.RemoveValue(ExifTag.SubjectLocation);
+            }
+        }
+
+        if (this.TryGetValue(ExifTag.SubjectArea, out IExifValue<ushort[]>? area))
+        {
+            if (area.Value?.Length == 4)
+            {
+                RectangleF rectangle = new(area.Value[0], area.Value[1], area.Value[2], area.Value[3]);
+                if (!TransformUtils.TryGetTransformedRectangle(rectangle, matrix, out Rectangle bounds))
+                {
+                    return;
+                }
+
+                // Ensure the bounds are within the image dimensions.
+                bounds = Rectangle.Intersect(bounds, new Rectangle(0, 0, width, height));
+
+                area.Value[0] = (ushort)bounds.X;
+                area.Value[1] = (ushort)bounds.Y;
+                area.Value[2] = (ushort)bounds.Width;
+                area.Value[3] = (ushort)bounds.Height;
+                this.SetValue(ExifTag.SubjectArea, area.Value);
+            }
+            else
+            {
+                this.RemoveValue(ExifTag.SubjectArea);
+            }
+        }
+    }
+
     /// <summary>
     /// Synchronizes the profiles with the specified metadata.
     /// </summary>
diff --git a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs
index bc34f759a0..b641dceb5f 100644
--- a/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/CloningImageProcessor{TPixel}.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Processing.Processors;
@@ -132,14 +133,14 @@ protected virtual void BeforeFrameApply(ImageFrame<TPixel> source, ImageFrame<TP
     /// <param name="source">The source image. Cannot be null.</param>
     /// <param name="destination">The cloned/destination image. Cannot be null.</param>
     protected virtual void AfterFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
-        => destination.Metadata.AfterFrameApply(source, destination);
+        => destination.Metadata.AfterFrameApply(source, destination, Matrix4x4.Identity);
 
     /// <summary>
     /// This method is called after the process is applied to prepare the processor.
     /// </summary>
     /// <param name="destination">The cloned/destination image. Cannot be null.</param>
     protected virtual void AfterImageApply(Image<TPixel> destination)
-        => destination.Metadata.AfterImageApply(destination);
+        => destination.Metadata.AfterImageApply(destination, Matrix4x4.Identity);
 
     /// <summary>
     /// Disposes the object and frees resources for the Garbage Collector.
diff --git a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs
index e1f7d1fffb..3510fe7a8e 100644
--- a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.PixelFormats;
 
 namespace SixLabors.ImageSharp.Processing.Processors;
@@ -99,14 +100,13 @@ protected virtual void BeforeFrameApply(ImageFrame<TPixel> source)
     /// </summary>
     /// <param name="source">The source image. Cannot be null.</param>
     protected virtual void AfterFrameApply(ImageFrame<TPixel> source)
-    {
-    }
+        => source.Metadata.AfterFrameApply(source, source, Matrix4x4.Identity);
 
     /// <summary>
     /// This method is called after the process is applied to the complete image.
     /// </summary>
     protected virtual void AfterImageApply()
-        => this.Source.Metadata.AfterImageApply(this.Source);
+        => this.Source.Metadata.AfterImageApply(this.Source, Matrix4x4.Identity);
 
     /// <summary>
     /// Disposes the object and frees resources for the Garbage Collector.
diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
index 37e61b7d76..96c350c6c8 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using System.Runtime.CompilerServices;
 using SixLabors.ImageSharp.Advanced;
 using SixLabors.ImageSharp.Memory;
@@ -16,6 +17,7 @@ internal class CropProcessor<TPixel> : TransformProcessor<TPixel>
     where TPixel : unmanaged, IPixel<TPixel>
 {
     private readonly Rectangle cropRectangle;
+    private readonly Matrix4x4 transformMatrix;
 
     /// <summary>
     /// Initializes a new instance of the <see cref="CropProcessor{TPixel}"/> class.
@@ -26,11 +28,22 @@ internal class CropProcessor<TPixel> : TransformProcessor<TPixel>
     /// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
     public CropProcessor(Configuration configuration, CropProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
         : base(configuration, source, sourceRectangle)
-        => this.cropRectangle = definition.CropRectangle;
+    {
+        this.cropRectangle = definition.CropRectangle;
+
+        // Calculate the transform matrix from the crop operation to allow us
+        // to update any metadata that represents pixel coordinates in the source image.
+        this.transformMatrix = new ProjectiveTransformBuilder()
+            .AppendTranslation(new PointF(-this.cropRectangle.X, -this.cropRectangle.Y))
+            .BuildMatrix(sourceRectangle);
+    }
 
     /// <inheritdoc/>
     protected override Size GetDestinationSize() => new(this.cropRectangle.Width, this.cropRectangle.Height);
 
+    /// <inheritdoc/>
+    protected override Matrix4x4 GetTransformMatrix() => this.transformMatrix;
+
     /// <inheritdoc/>
     protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
     {
diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs
index 2eda61e41d..8a76ca42f3 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor{TPixel}.cs
@@ -2,7 +2,6 @@
 // Licensed under the Six Labors Split License.
 
 using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.Advanced;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.Processing.Processors.Binarization;
 using SixLabors.ImageSharp.Processing.Processors.Convolution;
@@ -36,7 +35,7 @@ protected override void BeforeImageApply()
 
         // TODO: This is clunky. We should add behavior enum to ExtractFrame.
         // All frames have be the same size so we only need to calculate the correct dimensions for the first frame
-        using (Image<TPixel> temp = new(this.Configuration, this.Source.Metadata.DeepClone(), new[] { this.Source.Frames.RootFrame.Clone() }))
+        using (Image<TPixel> temp = new(this.Configuration, this.Source.Metadata.DeepClone(), [this.Source.Frames.RootFrame.Clone()]))
         {
             Configuration configuration = this.Source.Configuration;
 
diff --git a/src/ImageSharp/Processing/Processors/Transforms/ISwizzler.cs b/src/ImageSharp/Processing/Processors/Transforms/ISwizzler.cs
index 321ecf9684..3c11c32b44 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/ISwizzler.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/ISwizzler.cs
@@ -11,12 +11,12 @@ public interface ISwizzler
     /// <summary>
     /// Gets the size of the image after transformation.
     /// </summary>
-    Size DestinationSize { get; }
+    public Size DestinationSize { get; }
 
     /// <summary>
     /// Applies the swizzle transformation to a given point.
     /// </summary>
     /// <param name="point">Point to transform.</param>
     /// <returns>The transformed point.</returns>
-    Point Transform(Point point);
+    public Point Transform(Point point);
 }
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs
index b3919e5844..59f5773cf1 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs
@@ -18,6 +18,7 @@ internal class AffineTransformProcessor<TPixel> : TransformProcessor<TPixel>, IR
 {
     private readonly Size destinationSize;
     private readonly Matrix3x2 transformMatrix;
+    private readonly Matrix4x4 transformMatrix4x4;
     private readonly IResampler resampler;
     private ImageFrame<TPixel>? source;
     private ImageFrame<TPixel>? destination;
@@ -34,6 +35,7 @@ public AffineTransformProcessor(Configuration configuration, AffineTransformProc
     {
         this.destinationSize = definition.DestinationSize;
         this.transformMatrix = definition.TransformMatrix;
+        this.transformMatrix4x4 = new(this.transformMatrix);
         this.resampler = definition.Sampler;
     }
 
@@ -47,6 +49,9 @@ protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixe
         this.resampler.ApplyTransform(this);
     }
 
+    /// <inheritdoc/>
+    protected override Matrix4x4 GetTransformMatrix() => this.transformMatrix4x4;
+
     /// <inheritdoc/>
     public void ApplyTransform<TResampler>(in TResampler sampler)
         where TResampler : struct, IResampler
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs
index 1adda1d043..86ba2f0f9a 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs
@@ -2,6 +2,7 @@
 // Licensed under the Six Labors Split License.
 
 using System.Buffers;
+using System.Numerics;
 using System.Runtime.CompilerServices;
 using SixLabors.ImageSharp.Advanced;
 using SixLabors.ImageSharp.Memory;
@@ -17,16 +18,45 @@ internal class FlipProcessor<TPixel> : ImageProcessor<TPixel>
     where TPixel : unmanaged, IPixel<TPixel>
 {
     private readonly FlipProcessor definition;
+    private readonly Matrix4x4 transformMatrix;
 
     /// <summary>
     /// Initializes a new instance of the <see cref="FlipProcessor{TPixel}"/> class.
     /// </summary>
-    /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
+    /// <param name="configuration">The configuration which allows altering default behavior or extending the library.</param>
     /// <param name="definition">The <see cref="FlipProcessor"/>.</param>
     /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
     /// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
     public FlipProcessor(Configuration configuration, FlipProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
-        : base(configuration, source, sourceRectangle) => this.definition = definition;
+        : base(configuration, source, sourceRectangle)
+    {
+        this.definition = definition;
+
+        // Calculate the transform matrix from the flip operation to allow us
+        // to update any metadata that represents pixel coordinates in the source image.
+        ProjectiveTransformBuilder builder = new();
+        switch (this.definition.FlipMode)
+        {
+            // No default needed as we have already set the pixels.
+            case FlipMode.Vertical:
+
+                // Flip vertically by scaling the Y axis by -1 and translating the Y coordinate.
+                builder.AppendScale(new Vector2(1, -1))
+                       .AppendTranslation(new PointF(0, this.SourceRectangle.Height - 1));
+                break;
+            case FlipMode.Horizontal:
+
+                // Flip horizontally by scaling the X axis by -1 and translating the X coordinate.
+                builder.AppendScale(new Vector2(-1, 1))
+                       .AppendTranslation(new PointF(this.SourceRectangle.Width - 1, 0));
+                break;
+            default:
+                this.transformMatrix = Matrix4x4.Identity;
+                return;
+        }
+
+        this.transformMatrix = builder.BuildMatrix(sourceRectangle);
+    }
 
     /// <inheritdoc/>
     protected override void OnFrameApply(ImageFrame<TPixel> source)
@@ -43,6 +73,14 @@ protected override void OnFrameApply(ImageFrame<TPixel> source)
         }
     }
 
+    /// <inheritdoc/>
+    protected override void AfterFrameApply(ImageFrame<TPixel> source)
+        => source.Metadata.AfterFrameApply(source, source, this.transformMatrix);
+
+    /// <inheritdoc/>
+    protected override void AfterImageApply()
+        => this.Source.Metadata.AfterImageApply(this.Source, this.transformMatrix);
+
     /// <summary>
     /// Swaps the image at the X-axis, which goes horizontally through the middle at half the height of the image.
     /// </summary>
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs
index 16b0070e90..1c30fd1145 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs
@@ -47,6 +47,9 @@ protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixe
         this.resampler.ApplyTransform(this);
     }
 
+    /// <inheritdoc/>
+    protected override Matrix4x4 GetTransformMatrix() => this.transformMatrix;
+
     /// <inheritdoc/>
     public void ApplyTransform<TResampler>(in TResampler sampler)
         where TResampler : struct, IResampler
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
index a047015d95..49439545b3 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using System.Runtime.CompilerServices;
 using SixLabors.ImageSharp.Advanced;
 using SixLabors.ImageSharp.Memory;
@@ -21,6 +22,7 @@ internal class ResizeProcessor<TPixel> : TransformProcessor<TPixel>, IResampling
     private readonly IResampler resampler;
     private readonly Rectangle destinationRectangle;
     private Image<TPixel>? destination;
+    private readonly Matrix4x4 transformMatrix;
 
     public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
         : base(configuration, source, sourceRectangle)
@@ -30,6 +32,17 @@ public ResizeProcessor(Configuration configuration, ResizeProcessor definition,
         this.destinationRectangle = definition.DestinationRectangle;
         this.options = definition.Options;
         this.resampler = definition.Options.Sampler;
+
+        // Calculate the transform matrix from the resize operation to allow us
+        // to update any metadata that represents pixel coordinates in the source image.
+        Vector2 scale = new(
+            this.destinationRectangle.Width / (float)this.SourceRectangle.Width,
+            this.destinationRectangle.Height / (float)this.SourceRectangle.Height);
+
+        this.transformMatrix = new ProjectiveTransformBuilder()
+                                    .AppendScale(scale)
+                                    .AppendTranslation((PointF)this.destinationRectangle.Location)
+                                    .BuildMatrix(sourceRectangle);
     }
 
     /// <inheritdoc/>
@@ -50,6 +63,9 @@ protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixe
         // Everything happens in BeforeImageApply.
     }
 
+    /// <inheritdoc/>
+    protected override Matrix4x4 GetTransformMatrix() => this.transformMatrix;
+
     public void ApplyTransform<TResampler>(in TResampler sampler)
         where TResampler : struct, IResampler
     {
@@ -179,10 +195,8 @@ private static PixelConversionModifiers GetModifiers(bool compand, bool premulti
         {
             return PixelConversionModifiers.Premultiply.ApplyCompanding(compand);
         }
-        else
-        {
-            return PixelConversionModifiers.None.ApplyCompanding(compand);
-        }
+
+        return PixelConversionModifiers.None.ApplyCompanding(compand);
     }
 
     private static void ApplyResizeFrameTransform(
diff --git a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs
index 1ea2156c8f..3bec82cce5 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs
@@ -1,6 +1,7 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.Memory;
 using SixLabors.ImageSharp.PixelFormats;
 
@@ -12,17 +13,28 @@ internal class SwizzleProcessor<TSwizzler, TPixel> : TransformProcessor<TPixel>
 {
     private readonly TSwizzler swizzler;
     private readonly Size destinationSize;
+    private readonly Matrix4x4 transformMatrix;
 
     public SwizzleProcessor(Configuration configuration, TSwizzler swizzler, Image<TPixel> source, Rectangle sourceRectangle)
         : base(configuration, source, sourceRectangle)
     {
         this.swizzler = swizzler;
         this.destinationSize = swizzler.DestinationSize;
+
+        // Calculate the transform matrix from the swizzle operation to allow us
+        // to update any metadata that represents pixel coordinates in the source image.
+        this.transformMatrix = new ProjectiveTransformBuilder()
+            .AppendMatrix(TransformUtils.GetSwizzlerMatrix(swizzler, sourceRectangle))
+            .BuildMatrix(sourceRectangle);
     }
 
-    protected override Size GetDestinationSize()
-        => this.destinationSize;
+    /// <inheritdoc />
+    protected override Size GetDestinationSize() => this.destinationSize;
+
+    /// <inheritdoc />
+    protected override Matrix4x4 GetTransformMatrix() => this.transformMatrix;
 
+    /// <inheritdoc />
     protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
     {
         Point p = default;
diff --git a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler}.cs b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler}.cs
index 0b8274aff7..d00e01711e 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler}.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler}.cs
@@ -16,10 +16,7 @@ public sealed class SwizzleProcessor<TSwizzler> : IImageProcessor
     /// Initializes a new instance of the <see cref="SwizzleProcessor{TSwizzler}"/> class.
     /// </summary>
     /// <param name="swizzler">The swizzler operation.</param>
-    public SwizzleProcessor(TSwizzler swizzler)
-    {
-        this.Swizzler = swizzler;
-    }
+    public SwizzleProcessor(TSwizzler swizzler) => this.Swizzler = swizzler;
 
     /// <summary>
     /// Gets the swizzler operation.
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs
index a8455a06ea..0d40cd32fd 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformProcessor{TPixel}.cs
@@ -1,9 +1,9 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using System.Numerics;
 using SixLabors.ImageSharp.PixelFormats;
 
-// TODO: DO we need this class?
 namespace SixLabors.ImageSharp.Processing.Processors.Transforms;
 
 /// <summary>
@@ -23,4 +23,20 @@ protected TransformProcessor(Configuration configuration, Image<TPixel> source,
         : base(configuration, source, sourceRectangle)
     {
     }
+
+    /// <summary>
+    /// Gets the transform matrix that will be applied to the image.
+    /// </summary>
+    /// <returns>
+    /// The <see cref="Matrix4x4"/> that represents the transformation to be applied to the image.
+    /// </returns>
+    protected abstract Matrix4x4 GetTransformMatrix();
+
+    /// <inheritdoc/>
+    protected override void AfterFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination)
+        => destination.Metadata.AfterFrameApply(source, destination, this.GetTransformMatrix());
+
+    /// <inheritdoc/>
+    protected override void AfterImageApply(Image<TPixel> destination)
+        => destination.Metadata.AfterImageApply(destination, this.GetTransformMatrix());
 }
diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
index 06a2dd292f..d25d4f4749 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
+++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs
@@ -419,6 +419,28 @@ public static Size GetTransformedSize(Matrix4x4 matrix, Size size, TransformSpac
         return size;
     }
 
+    /// <summary>
+    /// Attempts to derive a 4x4 projective transform matrix that approximates the behavior of an <typeparamref name="T"/>.
+    /// </summary>
+    /// <param name="swizzler">
+    /// The swizzler to use for the transformation.
+    /// </param>
+    /// <param name="sourceRectangle">
+    /// The source rectangle that defines the area to be transformed.
+    /// </param>
+    /// <typeparam name="T">
+    /// The type of the swizzler, which must implement <see cref="ISwizzler"/>.
+    /// </typeparam>
+    public static Matrix4x4 GetSwizzlerMatrix<T>(T swizzler, Rectangle sourceRectangle)
+        where T : struct, ISwizzler
+        => CreateQuadDistortionMatrix(
+            sourceRectangle,
+            swizzler.Transform(new Point(sourceRectangle.Left, sourceRectangle.Top)),
+            swizzler.Transform(new Point(sourceRectangle.Right, sourceRectangle.Top)),
+            swizzler.Transform(new Point(sourceRectangle.Right, sourceRectangle.Bottom)),
+            swizzler.Transform(new Point(sourceRectangle.Left, sourceRectangle.Bottom)),
+            TransformSpace.Pixel);
+
     /// <summary>
     /// Returns the size relative to the source for the given transformation matrix.
     /// </summary>
@@ -504,7 +526,7 @@ private static bool TryGetTransformedRectangle(RectangleF rectangle, Matrix3x2 m
     /// <see langword="true"/> if the transformation was successful; otherwise, <see langword="false"/>.
     /// </returns>
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    private static bool TryGetTransformedRectangle(RectangleF rectangle, Matrix4x4 matrix, out Rectangle bounds)
+    internal static bool TryGetTransformedRectangle(RectangleF rectangle, Matrix4x4 matrix, out Rectangle bounds)
     {
         if (matrix.IsIdentity || rectangle.Equals(default))
         {


Test patch:
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs
index f4a0aeb0cc..56e9a5201b 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/CropTest.cs
@@ -1,8 +1,10 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Tests.TestUtilities;
 using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
 
 namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms;
@@ -22,7 +24,31 @@ public void Crop<TPixel>(TestImageProvider<TPixel> provider, int x, int y, int w
         provider.RunValidatingProcessorTest(
             ctx => ctx.Crop(rect),
             info,
-            appendPixelTypeToFileName: false,
-            comparer: ImageComparer.Exact);
+            comparer: ImageComparer.Exact,
+            appendPixelTypeToFileName: false);
+    }
+
+    [Theory]
+    [WithTestPatternImages(100, 100, PixelTypes.Rgba32)]
+    public void CropUpdatesSubject<TPixel>(TestImageProvider<TPixel> provider)
+        where TPixel : unmanaged, IPixel<TPixel>
+    {
+        using Image<TPixel> image = provider.GetImage();
+
+        image.Metadata.ExifProfile = new();
+        image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]);
+        image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 50, 50]);
+
+        image.Mutate(ctx => ctx.Crop(Rectangle.FromLTRB(20, 20, 50, 50)));
+
+        // The new subject area is now relative to the cropped area.
+        // overhanging pixels are constrained to the dimensions of the image.
+        Assert.Equal(
+            [0, 0],
+            image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value);
+
+        Assert.Equal(
+            [0, 0, 30, 30],
+            image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value);
     }
 }
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs
index 5c545147e1..9aa04e370a 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/FlipTests.cs
@@ -1,8 +1,10 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Tests.TestUtilities;
 
 // ReSharper disable InconsistentNaming
 namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms;
@@ -14,10 +16,10 @@ public class FlipTests
     public static readonly TheoryData<FlipMode> FlipValues =
         new()
         {
-                FlipMode.None,
-                FlipMode.Vertical,
-                FlipMode.Horizontal,
-            };
+            FlipMode.None,
+            FlipMode.Vertical,
+            FlipMode.Horizontal
+        };
 
     [Theory]
     [WithTestPatternImages(nameof(FlipValues), 20, 37, PixelTypes.Rgba32)]
@@ -25,23 +27,77 @@ public class FlipTests
     [WithTestPatternImages(nameof(FlipValues), 17, 32, PixelTypes.Rgba32)]
     public void Flip<TPixel>(TestImageProvider<TPixel> provider, FlipMode flipMode)
         where TPixel : unmanaged, IPixel<TPixel>
-    {
-        provider.RunValidatingProcessorTest(
+        => provider.RunValidatingProcessorTest(
             ctx => ctx.Flip(flipMode),
             testOutputDetails: flipMode,
             appendPixelTypeToFileName: false);
-    }
 
     [Theory]
     [WithTestPatternImages(nameof(FlipValues), 53, 37, PixelTypes.Rgba32)]
     [WithTestPatternImages(nameof(FlipValues), 17, 32, PixelTypes.Rgba32)]
     public void Flip_WorksOnWrappedMemoryImage<TPixel>(TestImageProvider<TPixel> provider, FlipMode flipMode)
         where TPixel : unmanaged, IPixel<TPixel>
-    {
-        provider.RunValidatingProcessorTestOnWrappedMemoryImage(
+        => provider.RunValidatingProcessorTestOnWrappedMemoryImage(
             ctx => ctx.Flip(flipMode),
             testOutputDetails: flipMode,
             useReferenceOutputFrom: nameof(this.Flip),
             appendPixelTypeToFileName: false);
+
+    [Theory]
+    [WithTestPatternImages(100, 100, PixelTypes.Rgba32)]
+    public void FlipVerticalUpdatesSubject<TPixel>(TestImageProvider<TPixel> provider)
+        where TPixel : unmanaged, IPixel<TPixel>
+    {
+        using Image<TPixel> image = provider.GetImage();
+
+        image.Metadata.ExifProfile = new();
+        image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]);
+        image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 50, 50]);
+
+        image.Mutate(ctx => ctx.Flip(FlipMode.Vertical));
+
+        // The subject location is a single coordinate, so a vertical flip simply reflects its Y position:
+        // newY = imageHeight - originalY - 1
+        // This mirrors the point vertically around the image's horizontal axis, preserving its X coordinate.
+        Assert.Equal(
+            [5, 84],
+            image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value);
+
+        // The subject area is now inverted because a vertical flip reflects the image across
+        // the horizontal axis passing through the image center.
+        // The Y-coordinate of the top edge is recalculated as:
+        // newY = imageHeight - originalY - height - 1
+        Assert.Equal(
+            [5, 34, 50, 50],
+            image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value);
+    }
+
+    [Theory]
+    [WithTestPatternImages(100, 100, PixelTypes.Rgba32)]
+    public void FlipHorizontalUpdatesSubject<TPixel>(TestImageProvider<TPixel> provider)
+        where TPixel : unmanaged, IPixel<TPixel>
+    {
+        using Image<TPixel> image = provider.GetImage();
+
+        image.Metadata.ExifProfile = new();
+        image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]);
+        image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 50, 50]);
+
+        image.Mutate(ctx => ctx.Flip(FlipMode.Horizontal));
+
+        // The subject location is a single coordinate, so a horizontal flip simply reflects its X position:
+        // newX = imageWidth - originalX - 1
+        // This mirrors the point horizontally around the image's vertical axis, preserving its Y coordinate.
+        Assert.Equal(
+            [94, 15],
+            image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value);
+
+        // The subject area is now inverted because a horizontal flip reflects the image across
+        // the vertical axis passing through the image center.
+        // The X-coordinate of the left edge is recalculated as:
+        // newX = imageWidth - originalX - width - 1
+        Assert.Equal(
+            [44, 15, 50, 50],
+            image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value);
     }
 }
diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ProjectiveTransformTests.cs
similarity index 67%
rename from tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs
rename to tests/ImageSharp.Tests/Processing/Processors/Transforms/ProjectiveTransformTests.cs
index 6ba04f79a2..2e580ea9fa 100644
--- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ProjectiveTransformTests.cs
@@ -3,14 +3,16 @@
 
 using System.Numerics;
 using System.Reflection;
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.Processing;
 using SixLabors.ImageSharp.Processing.Processors.Transforms;
+using SixLabors.ImageSharp.Tests.TestUtilities;
 using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
 using Xunit.Abstractions;
 
 // ReSharper disable InconsistentNaming
-namespace SixLabors.ImageSharp.Tests.Processing.Transforms;
+namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms;
 
 [Trait("Category", "Processors")]
 public class ProjectiveTransformTests
@@ -71,16 +73,14 @@ public void Transform_WithSampler<TPixel>(TestImageProvider<TPixel> provider, st
         where TPixel : unmanaged, IPixel<TPixel>
     {
         IResampler sampler = GetResampler(resamplerName);
-        using (Image<TPixel> image = provider.GetImage())
-        {
-            ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder()
-                .AppendTaper(TaperSide.Right, TaperCorner.Both, .5F);
+        using Image<TPixel> image = provider.GetImage();
+        ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder()
+            .AppendTaper(TaperSide.Right, TaperCorner.Both, .5F);
 
-            image.Mutate(i => i.Transform(builder, sampler));
+        image.Mutate(i => i.Transform(builder, sampler));
 
-            image.DebugSave(provider, resamplerName);
-            image.CompareToReferenceOutput(ValidatorComparer, provider, resamplerName);
-        }
+        image.DebugSave(provider, resamplerName);
+        image.CompareToReferenceOutput(ValidatorComparer, provider, resamplerName);
     }
 
     [Theory]
@@ -88,17 +88,15 @@ public void Transform_WithSampler<TPixel>(TestImageProvider<TPixel> provider, st
     public void Transform_WithTaperMatrix<TPixel>(TestImageProvider<TPixel> provider, TaperSide taperSide, TaperCorner taperCorner)
         where TPixel : unmanaged, IPixel<TPixel>
     {
-        using (Image<TPixel> image = provider.GetImage())
-        {
-            ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder()
-                .AppendTaper(taperSide, taperCorner, .5F);
+        using Image<TPixel> image = provider.GetImage();
+        ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder()
+            .AppendTaper(taperSide, taperCorner, .5F);
 
-            image.Mutate(i => i.Transform(builder));
+        image.Mutate(i => i.Transform(builder));
 
-            FormattableString testOutputDetails = $"{taperSide}-{taperCorner}";
-            image.DebugSave(provider, testOutputDetails);
-            image.CompareFirstFrameToReferenceOutput(TolerantComparer, provider, testOutputDetails);
-        }
+        FormattableString testOutputDetails = $"{taperSide}-{taperCorner}";
+        image.DebugSave(provider, testOutputDetails);
+        image.CompareFirstFrameToReferenceOutput(TolerantComparer, provider, testOutputDetails);
     }
 
     [Theory]
@@ -106,17 +104,15 @@ public void Transform_WithTaperMatrix<TPixel>(TestImageProvider<TPixel> provider
     public void Transform_WithQuadDistortion<TPixel>(TestImageProvider<TPixel> provider, PointF topLeft, PointF topRight, PointF bottomRight, PointF bottomLeft)
         where TPixel : unmanaged, IPixel<TPixel>
     {
-        using (Image<TPixel> image = provider.GetImage())
-        {
-            ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder()
-                .AppendQuadDistortion(topLeft, topRight, bottomRight, bottomLeft);
+        using Image<TPixel> image = provider.GetImage();
+        ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder()
+            .AppendQuadDistortion(topLeft, topRight, bottomRight, bottomLeft);
 
-            image.Mutate(i => i.Transform(builder));
+        image.Mutate(i => i.Transform(builder));
 
-            FormattableString testOutputDetails = $"{topLeft}-{topRight}-{bottomRight}-{bottomLeft}";
-            image.DebugSave(provider, testOutputDetails);
-            image.CompareFirstFrameToReferenceOutput(TolerantComparer, provider, testOutputDetails);
-        }
+        FormattableString testOutputDetails = $"{topLeft}-{topRight}-{bottomRight}-{bottomLeft}";
+        image.DebugSave(provider, testOutputDetails);
+        image.CompareFirstFrameToReferenceOutput(TolerantComparer, provider, testOutputDetails);
     }
 
     [Theory]
@@ -129,19 +125,17 @@ public void RawTransformMatchesDocumentedExample<TPixel>(TestImageProvider<TPixe
 
         // This test matches the output described in the example at
         // https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/transforms/non-affine
-        using (Image<TPixel> image = provider.GetImage())
-        {
-            Matrix4x4 matrix = Matrix4x4.Identity;
-            matrix.M14 = 0.01F;
+        using Image<TPixel> image = provider.GetImage();
+        Matrix4x4 matrix = Matrix4x4.Identity;
+        matrix.M14 = 0.01F;
 
-            ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder()
+        ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder()
             .AppendMatrix(matrix);
 
-            image.Mutate(i => i.Transform(builder));
+        image.Mutate(i => i.Transform(builder));
 
-            image.DebugSave(provider);
-            image.CompareToReferenceOutput(TolerantComparer, provider);
-        }
+        image.DebugSave(provider);
+        image.CompareToReferenceOutput(TolerantComparer, provider);
     }
 
     [Theory]
@@ -151,24 +145,22 @@ public void PerspectiveTransformMatchesCSS<TPixel>(TestImageProvider<TPixel> pro
     {
         // https://jsfiddle.net/dFrHS/545/
         // https://github.com/SixLabors/ImageSharp/issues/787
-        using (Image<TPixel> image = provider.GetImage())
-        {
+        using Image<TPixel> image = provider.GetImage();
 #pragma warning disable SA1117 // Parameters should be on same line or separate lines
-            Matrix4x4 matrix = new(
-                0.260987f, -0.434909f, 0, -0.0022184f,
-                0.373196f, 0.949882f, 0, -0.000312129f,
-                0, 0, 1, 0,
-                52, 165, 0, 1);
+        Matrix4x4 matrix = new(
+            0.260987f, -0.434909f, 0, -0.0022184f,
+            0.373196f, 0.949882f, 0, -0.000312129f,
+            0, 0, 1, 0,
+            52, 165, 0, 1);
 #pragma warning restore SA1117 // Parameters should be on same line or separate lines
 
-            ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder()
+        ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder()
             .AppendMatrix(matrix);
 
-            image.Mutate(i => i.Transform(builder));
+        image.Mutate(i => i.Transform(builder));
 
-            image.DebugSave(provider);
-            image.CompareToReferenceOutput(TolerantComparer, provider);
-        }
+        image.DebugSave(provider);
+        image.CompareToReferenceOutput(TolerantComparer, provider);
     }
 
     [Fact]
@@ -240,13 +232,44 @@ public void TransformRotationDoesNotOffset()
         ImageComparer.Exact.VerifySimilarity(img, img3);
     }
 
+    [Theory]
+    [WithTestPatternImages(100, 100, PixelTypes.Rgba32)]
+    public void TransformUpdatesSubject<TPixel>(TestImageProvider<TPixel> provider)
+        where TPixel : unmanaged, IPixel<TPixel>
+    {
+        using Image<TPixel> image = provider.GetImage();
+
+        image.Metadata.ExifProfile = new();
+        image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]);
+        image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 50, 50]);
+
+        ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder()
+            .AppendRotationDegrees(180);
+
+        image.Mutate(ctx => ctx.Transform(builder));
+
+        // A 180-degree rotation inverts both axes around the image center.
+        // The subject location (5, 15) becomes (imageWidth - 5 - 1, imageHeight - 15 - 1) = (94, 84)
+        Assert.Equal(
+            [94, 84],
+            image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value);
+
+        // The subject area is also mirrored around the center.
+        // New X = imageWidth - originalX - width
+        // New Y = imageHeight - originalY - height
+        // (5, 15, 50, 50) becomes (44, 34, 50, 50)
+        Assert.Equal(
+            [44, 34, 50, 50],
+            image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value);
+    }
+
     private static IResampler GetResampler(string name)
     {
         PropertyInfo property = typeof(KnownResamplers).GetTypeInfo().GetProperty(name);
 
         if (property is null)
         {
-            throw new Exception($"No resampler named {name}");
+            throw new InvalidOperationException($"No resampler named {name}");
         }
 
         return (IResampler)property.GetValue(null);
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
index 859634e37d..1e0e66965a 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
@@ -3,10 +3,12 @@
 
 using System.Numerics;
 using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.Processing;
 using SixLabors.ImageSharp.Processing.Processors.Transforms;
 using SixLabors.ImageSharp.Tests.Memory;
+using SixLabors.ImageSharp.Tests.TestUtilities;
 using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
 
 // ReSharper disable InconsistentNaming
@@ -20,15 +22,15 @@ public class ResizeTests
 
     public static readonly string[] AllResamplerNames = TestUtils.GetAllResamplerNames();
 
-    public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial };
+    public static readonly string[] CommonTestImages = [TestImages.Png.CalliphoraPartial];
 
     public static readonly string[] SmokeTestResamplerNames =
-        {
+        [
             nameof(KnownResamplers.NearestNeighbor),
             nameof(KnownResamplers.Bicubic),
             nameof(KnownResamplers.Box),
             nameof(KnownResamplers.Lanczos5),
-        };
+        ];
 
     private static readonly ImageComparer ValidatorComparer =
         ImageComparer.TolerantPercentage(0.07F);
@@ -242,9 +244,7 @@ public void Resize_IsAppliedToAllFrames<TPixel>(TestImageProvider<TPixel> provid
     [WithTestPatternImages(50, 50, CommonNonDefaultPixelTypes)]
     public void Resize_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
         where TPixel : unmanaged, IPixel<TPixel>
-    {
-        provider.RunValidatingProcessorTest(x => x.Resize(x.GetCurrentSize() / 2), comparer: ValidatorComparer);
-    }
+        => provider.RunValidatingProcessorTest(x => x.Resize(x.GetCurrentSize() / 2), comparer: ValidatorComparer);
 
     [Theory]
     [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)]
@@ -257,7 +257,7 @@ public void Resize_ThrowsForWrappedMemoryImage<TPixel>(TestImageProvider<TPixel>
 
         using Image<TPixel> image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height);
         Assert.ThrowsAny<Exception>(
-            () => { image1.Mutate(x => x.Resize(image0.Width / 2, image0.Height / 2, true)); });
+            () => image1.Mutate(x => x.Resize(image0.Width / 2, image0.Height / 2, true)));
     }
 
     [Theory]
@@ -579,6 +579,7 @@ public void CanResizeExifIssueImages<TPixel>(TestImageProvider<TPixel> provider)
         }
 
         using Image<TPixel> image = provider.GetImage();
+
         // Don't bother saving, we're testing the EXIF metadata updates.
         image.Mutate(x => x.Resize(image.Width / 2, image.Height / 2));
     }
@@ -630,4 +631,28 @@ public void Issue1625_LimitedAllocator<TPixel>(TestImageProvider<TPixel> provide
             appendPixelTypeToFileName: false,
             appendSourceFileOrDescription: false);
     }
+
+    [Theory]
+    [WithTestPatternImages(100, 100, PixelTypes.Rgba32)]
+    public void ResizeUpdatesSubject<TPixel>(TestImageProvider<TPixel> provider)
+        where TPixel : unmanaged, IPixel<TPixel>
+    {
+        using Image<TPixel> image = provider.GetImage();
+
+        image.Metadata.ExifProfile = new();
+        image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]);
+        image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 20, 20]);
+
+        image.Mutate(ctx => ctx.Resize(new Size(image.Width / 2, image.Height / 2)));
+
+        // The transform operates in pixel space, so the resulting values correspond to the
+        // scaled pixel centers, producing whole-number coordinates after resizing.
+        Assert.Equal(
+            [2, 7],
+            image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value);
+
+        Assert.Equal(
+            [2, 7, 11, 11],
+            image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value);
+    }
 }
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs
index 7c3a97a444..dfa263fec0 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/RotateTests.cs
@@ -1,8 +1,10 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.Processing;
+using SixLabors.ImageSharp.Tests.TestUtilities;
 
 namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms;
 
@@ -13,33 +15,57 @@ public class RotateTests
     public static readonly TheoryData<float> RotateAngles
         = new()
         {
-        50, -50, 170, -170
-    };
+            50, -50, 170, -170
+        };
 
     public static readonly TheoryData<RotateMode> RotateEnumValues
         = new()
         {
-        RotateMode.None,
-        RotateMode.Rotate90,
-        RotateMode.Rotate180,
-        RotateMode.Rotate270
-    };
+            RotateMode.None,
+            RotateMode.Rotate90,
+            RotateMode.Rotate180,
+            RotateMode.Rotate270
+        };
 
     [Theory]
     [WithTestPatternImages(nameof(RotateAngles), 100, 50, PixelTypes.Rgba32)]
     [WithTestPatternImages(nameof(RotateAngles), 50, 100, PixelTypes.Rgba32)]
     public void Rotate_WithAngle<TPixel>(TestImageProvider<TPixel> provider, float value)
         where TPixel : unmanaged, IPixel<TPixel>
-    {
-        provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false);
-    }
+        => provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false);
 
     [Theory]
     [WithTestPatternImages(nameof(RotateEnumValues), 100, 50, PixelTypes.Rgba32)]
     [WithTestPatternImages(nameof(RotateEnumValues), 50, 100, PixelTypes.Rgba32)]
     public void Rotate_WithRotateTypeEnum<TPixel>(TestImageProvider<TPixel> provider, RotateMode value)
         where TPixel : unmanaged, IPixel<TPixel>
+        => provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false);
+
+    [Theory]
+    [WithTestPatternImages(100, 100, PixelTypes.Rgba32)]
+    public void RotateUpdatesSubject<TPixel>(TestImageProvider<TPixel> provider)
+        where TPixel : unmanaged, IPixel<TPixel>
     {
-        provider.RunValidatingProcessorTest(ctx => ctx.Rotate(value), value, appendPixelTypeToFileName: false);
+        using Image<TPixel> image = provider.GetImage();
+
+        image.Metadata.ExifProfile = new();
+        image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]);
+        image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 50, 50]);
+
+        image.Mutate(ctx => ctx.Rotate(180));
+
+        // A 180-degree rotation inverts both axes around the image center.
+        // The subject location (5, 15) becomes (imageWidth - 5 - 1, imageHeight - 15 - 1) = (94, 84)
+        Assert.Equal(
+            [94, 84],
+            image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value);
+
+        // The subject area is also mirrored around the center.
+        // New X = imageWidth - originalX - width
+        // New Y = imageHeight - originalY - height
+        // (5, 15, 50, 50) becomes (44, 34, 50, 50)
+        Assert.Equal(
+            [44, 34, 50, 50],
+            image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value);
     }
 }
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs
index dee698db09..33e22d3644 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs
+++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/SwizzleTests.cs
@@ -1,9 +1,11 @@
 // Copyright (c) Six Labors.
 // Licensed under the Six Labors Split License.
 
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
 using SixLabors.ImageSharp.PixelFormats;
 using SixLabors.ImageSharp.Processing;
 using SixLabors.ImageSharp.Processing.Processors.Transforms;
+using SixLabors.ImageSharp.Tests.TestUtilities;
 using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
 
 namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms;
@@ -12,12 +14,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms;
 [GroupOutput("Transforms")]
 public class SwizzleTests
 {
-    private struct InvertXAndYSwizzler : ISwizzler
+    private readonly struct SwapXAndYSwizzler : ISwizzler
     {
-        public InvertXAndYSwizzler(Size sourceSize)
-        {
-            this.DestinationSize = new Size(sourceSize.Height, sourceSize.Width);
-        }
+        public SwapXAndYSwizzler(Size sourceSize)
+            => this.DestinationSize = new Size(sourceSize.Height, sourceSize.Width);
 
         public Size DestinationSize { get; }
 
@@ -34,15 +34,15 @@ public void InvertXAndYSwizzle<TPixel>(TestImageProvider<TPixel> provider)
         using Image<TPixel> expectedImage = provider.GetImage();
         using Image<TPixel> image = provider.GetImage();
 
-        image.Mutate(ctx => ctx.Swizzle(new InvertXAndYSwizzler(new Size(image.Width, image.Height))));
+        image.Mutate(ctx => ctx.Swizzle(new SwapXAndYSwizzler(new Size(image.Width, image.Height))));
 
         image.DebugSave(
             provider,
-            nameof(InvertXAndYSwizzler),
+            nameof(SwapXAndYSwizzler),
             appendPixelTypeToFileName: false,
             appendSourceFileOrDescription: true);
 
-        image.Mutate(ctx => ctx.Swizzle(new InvertXAndYSwizzler(new Size(image.Width, image.Height))));
+        image.Mutate(ctx => ctx.Swizzle(new SwapXAndYSwizzler(new Size(image.Width, image.Height))));
 
         image.DebugSave(
             provider,
@@ -52,4 +52,26 @@ public void InvertXAndYSwizzle<TPixel>(TestImageProvider<TPixel> provider)
 
         ImageComparer.Exact.VerifySimilarity(expectedImage, image);
     }
+
+    [Theory]
+    [WithTestPatternImages(100, 100, PixelTypes.Rgba32)]
+    public void SwizzleUpdatesSubject<TPixel>(TestImageProvider<TPixel> provider)
+        where TPixel : unmanaged, IPixel<TPixel>
+    {
+        using Image<TPixel> image = provider.GetImage();
+
+        image.Metadata.ExifProfile = new();
+        image.Metadata.ExifProfile.SetValue(ExifTag.SubjectLocation, [5, 15]);
+        image.Metadata.ExifProfile.SetValue(ExifTag.SubjectArea, [5, 15, 20, 20]);
+
+        image.Mutate(ctx => ctx.Swizzle(new SwapXAndYSwizzler(new Size(image.Width, image.Height))));
+
+        Assert.Equal(
+            [15, 5],
+            image.Metadata.ExifProfile.GetValue(ExifTag.SubjectLocation).Value);
+
+        Assert.Equal(
+            [15, 5, 20, 20],
+            image.Metadata.ExifProfile.GetValue(ExifTag.SubjectArea).Value);
+    }
 }