From ea953c0514d999bd0243613bd1386f28c496b793 Mon Sep 17 00:00:00 2001 From: Andrew Murray 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 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 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 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