Repository: python-pillow/Pillow

Issue:
ImageOps.exif_transpose not expected behaviour for TIFF images
### What did you do?
I have a simple tiff image 150x100px rotated 90 degrees according to exif tag: 

[sample-out.zip](https://github.com/python-pillow/Pillow/files/12549680/sample-out.zip)

```bash
exiftool -n sample-out.tif
...
Image Width                     : 150
Image Height                    : 100
Orientation                     : 6
...
```
I tried to rotate the image in Pillow according to EXIF Orientation tag.

### What did you expect to happen?
New resolution is 100x150, correct orientation

### What actually happened?
New resolution is 150x100, image transposed wrong way

### What are your OS, Python and Pillow versions?

* OS: Ubuntu
* Python: 3.10
* Pillow: 9.3.0

```python
from PIL import Image, ImageOps
im = Image.open('sample-out.tif') 
print(im.size) # (150, 100) - correct
im = ImageOps.exif_transpose(im)
print(im.size) # (150, 100) - not correct
im.save('output.tif') # you may save and see that now image rotated 540 degrees and does not contain
```

```bash
exiftool -n output.tif
...
Image Width                     : 150
Image Height                    : 100
# there is not Orientation tag anymore
...
```

### Reason

``Image.transpose`` calls ``Image.load()``.
``Image.load()``calls ``TiffImageFile.load()``
``TiffImageFile.load()`` calls ``ImageFile.load()``
``ImageFile.load()`` calls ``TiffImageFile.load_end()``
``load_end()`` transposes image. 

After ``Image.load()`` is done, image is transposed again. 


Discussion:
I've created PR #7383 to resolve this.

Resolving patch:
diff --git a/src/PIL/ImageOps.py b/src/PIL/ImageOps.py
--- 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 = {
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
--- 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,10 @@ def load_end(self):
                     continue
                 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
         Calls out to libtiff"""
@@ -1542,8 +1532,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)
-
 
 #
 # --------------------------------------------------------------------


Test patch:
diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py
--- 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 (
@@ -1035,7 +1035,18 @@ 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)
+
+    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)