From ea953c0514d999bd0243613bd1386f28c496b793 Mon Sep 17 00:00:00 2001
From: Andrew Murray <radarhere@users.noreply.github.com>
Date: Fri, 8 Sep 2023 15:05:36 +1000
Subject: [PATCH 1/4] When TIFF applies orientation on load, delete tag from
 getexif()

---
 src/PIL/TiffImagePlugin.py | 18 ++----------------
 1 file changed, 2 insertions(+), 16 deletions(-)

diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index 99c23ad4b28..9aab863ff22 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -1203,20 +1203,6 @@ def load(self):
         return super().load()
 
     def load_end(self):
-        if self._tile_orientation:
-            method = {
-                2: Image.Transpose.FLIP_LEFT_RIGHT,
-                3: Image.Transpose.ROTATE_180,
-                4: Image.Transpose.FLIP_TOP_BOTTOM,
-                5: Image.Transpose.TRANSPOSE,
-                6: Image.Transpose.ROTATE_270,
-                7: Image.Transpose.TRANSVERSE,
-                8: Image.Transpose.ROTATE_90,
-            }.get(self._tile_orientation)
-            if method is not None:
-                self.im = self.im.transpose(method)
-                self._size = self.im.size
-
         # allow closing if we're on the first frame, there's no next
         # This is the ImageFile.load path only, libtiff specific below.
         if not self.is_animated:
@@ -1233,6 +1219,8 @@ def load_end(self):
                     continue
                 exif.get_ifd(key)
 
+        ImageOps.exif_transpose(self, in_place=True)
+
     def _load_libtiff(self):
         """Overload method triggered when we detect a compressed tiff
         Calls out to libtiff"""
@@ -1542,8 +1530,6 @@ def _setup(self):
             palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]
             self.palette = ImagePalette.raw("RGB;L", b"".join(palette))
 
-        self._tile_orientation = self.tag_v2.get(ExifTags.Base.Orientation)
-
 
 #
 # --------------------------------------------------------------------

From e195e60ce2a672f289da2678a51d899695c2c2aa Mon Sep 17 00:00:00 2001
From: Andrew Murray <radarhere@users.noreply.github.com>
Date: Fri, 8 Sep 2023 08:23:35 +1000
Subject: [PATCH 2/4] Ensure TIFF has transposed on load before checking
 orientation

---
 Tests/test_file_libtiff.py | 10 +++++++++-
 src/PIL/ImageOps.py        |  1 +
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py
index ac78b086965..69499ff6ba8 100644
--- a/Tests/test_file_libtiff.py
+++ b/Tests/test_file_libtiff.py
@@ -8,7 +8,7 @@
 
 import pytest
 
-from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features
+from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features
 from PIL.TiffImagePlugin import SAMPLEFORMAT, STRIPOFFSETS, SUBIFD
 
 from .helper import (
@@ -1039,6 +1039,14 @@ def test_orientation(self):
 
                     assert_image_similar(base_im, im, 0.7)
 
+    def test_exif_transpose(self):
+        with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
+            for i in range(2, 9):
+                with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
+                    im = ImageOps.exif_transpose(im)
+
+                    assert_image_similar(base_im, im, 0.7)
+
     @pytest.mark.valgrind_known_error(reason="Backtrace in Python Core")
     def test_sampleformat_not_corrupted(self):
         # Assert that a TIFF image with SampleFormat=UINT tag is not corrupted
diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py
index 17702778c13..1231ad6ebda 100644
--- a/src/PIL/ImageOps.py
+++ b/src/PIL/ImageOps.py
@@ -588,6 +588,7 @@ def exif_transpose(image, *, in_place=False):
         with the transposition applied. If there is no transposition, a copy of the
         image will be returned.
     """
+    image.load()
     image_exif = image.getexif()
     orientation = image_exif.get(ExifTags.Base.Orientation)
     method = {

From d0abab7997f716f0a0a0b9d4717e8274d0efbb0e Mon Sep 17 00:00:00 2001
From: Andrew Murray <radarhere@users.noreply.github.com>
Date: Fri, 8 Sep 2023 14:09:47 +1000
Subject: [PATCH 3/4] When orientation is applied, delete TIFF tag

---
 Tests/test_file_libtiff.py | 3 +++
 src/PIL/ImageOps.py        | 4 ++++
 2 files changed, 7 insertions(+)

diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py
index 69499ff6ba8..a7394f1bf5c 100644
--- a/Tests/test_file_libtiff.py
+++ b/Tests/test_file_libtiff.py
@@ -1035,7 +1035,10 @@ def test_orientation(self):
         with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
             for i in range(2, 9):
                 with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
+                    assert 274 in im.tag_v2
+
                     im.load()
+                    assert 274 not in im.tag_v2
 
                     assert_image_similar(base_im, im, 0.7)
 
diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py
index 1231ad6ebda..ce2b1bd9b4d 100644
--- a/src/PIL/ImageOps.py
+++ b/src/PIL/ImageOps.py
@@ -611,6 +611,10 @@ def exif_transpose(image, *, in_place=False):
         exif = exif_image.getexif()
         if ExifTags.Base.Orientation in exif:
             del exif[ExifTags.Base.Orientation]
+            if in_place and ExifTags.Base.Orientation in getattr(
+                exif_image, "tag_v2", {}
+            ):
+                del exif_image.tag_v2[ExifTags.Base.Orientation]
             if "exif" in exif_image.info:
                 exif_image.info["exif"] = exif.tobytes()
             elif "Raw profile type exif" in exif_image.info:

From 1217b13b9e7bec6987f459cb1517bd189d12e052 Mon Sep 17 00:00:00 2001
From: Andrew Murray <radarhere@users.noreply.github.com>
Date: Mon, 11 Sep 2023 19:02:17 +1000
Subject: [PATCH 4/4] Handle tag_v2 in TiffImagePlugin

---
 src/PIL/ImageOps.py        | 4 ----
 src/PIL/TiffImagePlugin.py | 2 ++
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py
index ce2b1bd9b4d..1231ad6ebda 100644
--- a/src/PIL/ImageOps.py
+++ b/src/PIL/ImageOps.py
@@ -611,10 +611,6 @@ def exif_transpose(image, *, in_place=False):
         exif = exif_image.getexif()
         if ExifTags.Base.Orientation in exif:
             del exif[ExifTags.Base.Orientation]
-            if in_place and ExifTags.Base.Orientation in getattr(
-                exif_image, "tag_v2", {}
-            ):
-                del exif_image.tag_v2[ExifTags.Base.Orientation]
             if "exif" in exif_image.info:
                 exif_image.info["exif"] = exif.tobytes()
             elif "Raw profile type exif" in exif_image.info:
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index 9aab863ff22..5d3bc4f838b 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -1220,6 +1220,8 @@ def load_end(self):
                 exif.get_ifd(key)
 
         ImageOps.exif_transpose(self, in_place=True)
+        if ExifTags.Base.Orientation in self.tag_v2:
+            del self.tag_v2[ExifTags.Base.Orientation]
 
     def _load_libtiff(self):
         """Overload method triggered when we detect a compressed tiff