diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index af7d2d9f525..d85b7513dcb 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1256,7 +1256,8 @@ def test_jpeg_short_strip(self, tiled: bool) -> None: def test_orientation(self) -> None: 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: + infile = "Tests/images/g4_orientation_" + str(i) + ".tif" + with Image.open(infile) as im: assert isinstance(im, TiffImagePlugin.TiffImageFile) assert 274 in im.tag_v2 @@ -1265,6 +1266,14 @@ def test_orientation(self) -> None: assert_image_similar(base_im, im, 0.7) + with Image.open(infile) as im: + assert im.size == (590, 88) + + im.tile.append(im.tile[0]) + with pytest.raises(OSError, match="Not exactly one tile"): + im.load() + assert im.size == (590, 88) + def test_exif_transpose(self) -> None: with Image.open("Tests/images/g4_orientation_1.tif") as base_im: for i in range(2, 9): diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 523db1d0a29..3fad75a2474 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -556,6 +556,25 @@ def test_exif_frames(self) -> None: im.seek(1) assert im.getexif()[273] == (1408, 1907) + @pytest.mark.parametrize( + "orientation, method", + ( + (5, Image.Transpose.TRANSPOSE), + (6, Image.Transpose.ROTATE_270), + (7, Image.Transpose.TRANSVERSE), + (8, Image.Transpose.ROTATE_90), + ), + ) + def test_orientation_mmap( + self, orientation: int, method: Image.Transpose, tmp_path: Path + ) -> None: + filename = tmp_path / "temp.tif" + im = hopper("L").crop((0, 0, 128, 64)) + im.save(filename, tiffinfo={274: orientation}) + with Image.open(filename) as reloaded: + assert reloaded.size == (64, 128) + assert_image_equal(reloaded, im.transpose(method)) + @pytest.mark.parametrize("mode", ("1", "L")) def test_photometric(self, mode: str, tmp_path: Path) -> None: filename = tmp_path / "temp.tif" diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 35b490bd4e3..356b9e79465 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1316,15 +1316,21 @@ def get_photoshop_blocks(self) -> dict[int, dict[str, bytes]]: return blocks def load(self) -> Image.core.PixelAccess | None: - if self.tile and self.use_load_libtiff: - return self._load_libtiff() - return super().load() + if not self.tile: + return super().load() - def load_prepare(self) -> None: - if self._im is None: - Image._decompression_bomb_check(self._tile_size) - self.im = Image.core.new(self.mode, self._tile_size) - ImageFile.ImageFile.load_prepare(self) + original_size = self._size + self._size = self._tile_size + + try: + if self.use_load_libtiff: + self._load_libtiff() + px = Image.Image.load(self) + else: + px = super().load() + finally: + self._size = original_size + return px def load_end(self) -> None: # allow closing if we're on the first frame, there's no next @@ -1343,7 +1349,7 @@ def load_end(self) -> None: if ExifTags.Base.Orientation in self.tag_v2: del self.tag_v2[ExifTags.Base.Orientation] - def _load_libtiff(self) -> Image.core.PixelAccess | None: + def _load_libtiff(self) -> None: """Overload method triggered when we detect a compressed tiff Calls out to libtiff""" @@ -1436,8 +1442,6 @@ def _load_libtiff(self) -> Image.core.PixelAccess | None: msg = f"decoder error {err}" raise OSError(msg) - return Image.Image.load(self) - def _setup(self) -> None: """Setup this image object based on current tags""" @@ -1477,6 +1481,7 @@ def _setup(self) -> None: msg = "Invalid dimensions" raise ValueError(msg) self._tile_size = xsize, ysize + Image._decompression_bomb_check(self._tile_size) orientation = self.tag_v2.get(ExifTags.Base.Orientation) if orientation in (5, 6, 7, 8): self._size = ysize, xsize