From f1e86965f6f791bf1af57f4d202a0c342221190b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 18 Sep 2024 20:26:06 +1000 Subject: [PATCH 1/5] Use transposed size after opening for TIFF images --- Tests/test_image_copy.py | 2 ++ Tests/test_image_resize.py | 4 +--- Tests/test_image_thumbnail.py | 8 +++----- Tests/test_numpy.py | 4 +++- src/PIL/Image.py | 20 +++++--------------- src/PIL/ImageFile.py | 2 +- src/PIL/TiffImagePlugin.py | 16 +++++++++++++++- 7 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Tests/test_image_copy.py b/Tests/test_image_copy.py index 027e5338b7a..a0b829cc565 100644 --- a/Tests/test_image_copy.py +++ b/Tests/test_image_copy.py @@ -49,5 +49,7 @@ def test_copy_zero() -> None: @skip_unless_feature("libtiff") def test_deepcopy() -> None: with Image.open("Tests/images/g4_orientation_5.tif") as im: + assert im.size == (590, 88) + out = copy.deepcopy(im) assert out.size == (590, 88) diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index c9e3045121f..d9ddf500913 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -300,9 +300,7 @@ def resize(mode: str, size: tuple[int, int] | list[int]) -> None: im.resize((10, 10), "unknown") @skip_unless_feature("libtiff") - def test_load_first(self) -> None: - # load() may change the size of the image - # Test that resize() is calling it before getting the size + def test_transposed(self) -> None: with Image.open("Tests/images/g4_orientation_5.tif") as im: im = im.resize((64, 64)) assert im.size == (64, 64) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index bdbf09c407e..01bd4b1d76b 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -92,15 +92,13 @@ def test_no_resize() -> None: @skip_unless_feature("libtiff") -def test_load_first() -> None: - # load() may change the size of the image - # Test that thumbnail() is calling it before performing size calculations +def test_transposed() -> None: with Image.open("Tests/images/g4_orientation_5.tif") as im: + assert im.size == (590, 88) + im.thumbnail((64, 64)) assert im.size == (64, 10) - # Test thumbnail(), without draft(), - # on an image that is large enough once load() has changed the size with Image.open("Tests/images/g4_orientation_5.tif") as im: im.thumbnail((590, 88), reducing_gap=None) assert im.size == (590, 88) diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 312e32e0ca7..040472d69e3 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -238,8 +238,10 @@ def test_zero_size() -> None: @skip_unless_feature("libtiff") -def test_load_first() -> None: +def test_transposed() -> None: with Image.open("Tests/images/g4_orientation_5.tif") as im: + assert im.size == (590, 88) + a = numpy.array(im) assert a.shape == (88, 590) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index f88cdf6e2f7..3f94cef3892 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2332,7 +2332,6 @@ def resize( msg = "reducing_gap must be 1.0 or greater" raise ValueError(msg) - self.load() if box is None: box = (0, 0) + self.size @@ -2781,27 +2780,18 @@ def round_aspect(number: float, key: Callable[[int], float]) -> int: ) return x, y + preserved_size = preserve_aspect_ratio() + if preserved_size is None: + return + final_size = preserved_size + box = None - final_size: tuple[int, int] if reducing_gap is not None: - preserved_size = preserve_aspect_ratio() - if preserved_size is None: - return - final_size = preserved_size - res = self.draft( None, (int(size[0] * reducing_gap), int(size[1] * reducing_gap)) ) if res is not None: box = res[1] - if box is None: - self.load() - - # load() may have changed the size of the image - preserved_size = preserve_aspect_ratio() - if preserved_size is None: - return - final_size = preserved_size if self.size != final_size: im = self.resize(final_size, resample, box=box, reducing_gap=reducing_gap) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index b8bf0b7fe90..721319fd7bf 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -322,7 +322,7 @@ def load(self) -> Image.core.PixelAccess | None: def load_prepare(self) -> None: # create image memory if necessary - if self._im is None or self.im.mode != self.mode or self.im.size != self.size: + if self._im is None or self.im.mode != self.mode: self.im = Image.core.new(self.mode, self.size) # create palette (optional) if self.mode == "P": diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index cc16cbfb083..28e50474471 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1275,6 +1275,11 @@ def load(self) -> Image.core.PixelAccess | None: return self._load_libtiff() return super().load() + def load_prepare(self) -> None: + if self._im is None and self._will_be_transposed: + self.im = Image.core.new(self.mode, self.size[::-1]) + ImageFile.ImageFile.load_prepare(self) + def load_end(self) -> None: # allow closing if we're on the first frame, there's no next # This is the ImageFile.load path only, libtiff specific below. @@ -1416,7 +1421,16 @@ def _setup(self) -> None: if not isinstance(xsize, int) or not isinstance(ysize, int): msg = "Invalid dimensions" raise ValueError(msg) - self._size = xsize, ysize + self._will_be_transposed = self.tag_v2.get(ExifTags.Base.Orientation) in ( + 5, + 6, + 7, + 8, + ) + if self._will_be_transposed: + self._size = ysize, xsize + else: + self._size = xsize, ysize logger.debug("- size: %s", self.size) From 84e275d90629bfdf508f81a1142eddd5d5ea46e4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 18 Sep 2024 20:27:35 +1000 Subject: [PATCH 2/5] Loading does not change mode --- src/PIL/ImageFile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 721319fd7bf..d69d8456850 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -322,7 +322,7 @@ def load(self) -> Image.core.PixelAccess | None: def load_prepare(self) -> None: # create image memory if necessary - if self._im is None or self.im.mode != self.mode: + if self._im is None: self.im = Image.core.new(self.mode, self.size) # create palette (optional) if self.mode == "P": From a859695d9a0bf9d9dc20ef7c053909658a12df6a Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 18 Sep 2024 21:17:49 +1000 Subject: [PATCH 3/5] Rearranged code Co-authored-by: Alexander Karpinsky --- src/PIL/TiffImagePlugin.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 28e50474471..437f7d98bd7 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1421,12 +1421,8 @@ def _setup(self) -> None: if not isinstance(xsize, int) or not isinstance(ysize, int): msg = "Invalid dimensions" raise ValueError(msg) - self._will_be_transposed = self.tag_v2.get(ExifTags.Base.Orientation) in ( - 5, - 6, - 7, - 8, - ) + orientation = self.tag_v2.get(ExifTags.Base.Orientation) + self._will_be_transposed = orientation in (5, 6, 7, 8) if self._will_be_transposed: self._size = ysize, xsize else: From a92dca66bdeb26106447079fcf2d36bc397298c2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 18 Sep 2024 21:22:59 +1000 Subject: [PATCH 4/5] Use raw size for striped tiles --- src/PIL/TiffImagePlugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 437f7d98bd7..c024e57fce4 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1569,7 +1569,7 @@ def _setup(self) -> None: if STRIPOFFSETS in self.tag_v2: offsets = self.tag_v2[STRIPOFFSETS] h = self.tag_v2.get(ROWSPERSTRIP, ysize) - w = self.size[0] + w = xsize else: # tiled image offsets = self.tag_v2[TILEOFFSETS] @@ -1603,9 +1603,9 @@ def _setup(self) -> None: ) ) x = x + w - if x >= self.size[0]: + if x >= xsize: x, y = 0, y + h - if y >= self.size[1]: + if y >= ysize: x = y = 0 layer += 1 else: From 629f5be52eb9043d70b5d5467f10a07ad3a68cdf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 18 Sep 2024 22:56:29 +1000 Subject: [PATCH 5/5] In seek(), create core image at size needed for loading --- src/PIL/TiffImagePlugin.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index c024e57fce4..14e6ea2786e 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1194,8 +1194,8 @@ def seek(self, frame: int) -> None: # Create a new core image object on second and # subsequent frames in the image. Image may be # different size/mode. - Image._decompression_bomb_check(self.size) - self.im = Image.core.new(self.mode, self.size) + Image._decompression_bomb_check(self._tile_size) + self.im = Image.core.new(self.mode, self._tile_size) def _seek(self, frame: int) -> None: self.fp = self._fp @@ -1276,8 +1276,8 @@ def load(self) -> Image.core.PixelAccess | None: return super().load() def load_prepare(self) -> None: - if self._im is None and self._will_be_transposed: - self.im = Image.core.new(self.mode, self.size[::-1]) + if self._im is None: + self.im = Image.core.new(self.mode, self._tile_size) ImageFile.ImageFile.load_prepare(self) def load_end(self) -> None: @@ -1421,9 +1421,9 @@ def _setup(self) -> None: if not isinstance(xsize, int) or not isinstance(ysize, int): msg = "Invalid dimensions" raise ValueError(msg) + self._tile_size = xsize, ysize orientation = self.tag_v2.get(ExifTags.Base.Orientation) - self._will_be_transposed = orientation in (5, 6, 7, 8) - if self._will_be_transposed: + if orientation in (5, 6, 7, 8): self._size = ysize, xsize else: self._size = xsize, ysize