Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added Tests/images/zero_height_frame.tif
Binary file not shown.
Binary file added Tests/images/zero_width_frame.tif
Binary file not shown.
12 changes: 12 additions & 0 deletions Tests/test_file_tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,18 @@ def test_invalid_tiled_dimensions(self) -> None:
with pytest.raises(ValueError):
Image.open(b)

@pytest.mark.parametrize(
"test_file",
(
"Tests/images/zero_width_frame.tif",
"Tests/images/zero_height_frame.tif",
),
)
def test_zero_size_frame(self, test_file: str) -> None:
with Image.open(test_file) as im:
with pytest.raises(SyntaxError, match="Invalid dimensions"):
im.seek(1)

@pytest.mark.parametrize("mode", ("P", "PA"))
def test_palette(self, mode: str, tmp_path: Path) -> None:
outfile = tmp_path / "temp.tif"
Expand Down
27 changes: 27 additions & 0 deletions Tests/test_imagefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,33 @@ def test_broken_datastream_without_errors(
monkeypatch.setattr(ImageFile, "LOAD_TRUNCATED_IMAGES", True)
im.load()

def test_mmap_partial_tile(self) -> None:
"""
Test that partial tiles of a file won't be mmapped.
"""
extent = (0, 0, 128, 64)

with Image.open("Tests/images/hopper_8bit.pgm") as im:
im.load()
# Test the premise: this file is regularly loaded with mmap
assert im.map is not None
expected = im.crop(extent)

with Image.open("Tests/images/hopper_8bit.pgm") as im:
im.tile = [im.tile[0]._replace(extents=extent)]
im.load()

assert im.map is None
assert_image_equal(im.crop(extent), expected)

@pytest.mark.parametrize("size", ((0, 128), (128, 0)))
def test_mmap_zero_size(self, size: tuple[int, int]) -> None:
with Image.open("Tests/images/hopper_8bit.pgm") as im:
im._size = size
im.tile = [im.tile[0]._replace(extents=(0, 0, *size))]
with pytest.raises(ValueError, match="tile cannot extend outside image"):
im.load()


class MockPyDecoder(ImageFile.PyDecoder):
last: MockPyDecoder
Expand Down
8 changes: 8 additions & 0 deletions docs/releasenotes/13.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,11 @@ position or contents.

:py:meth:`~PIL.Image.Image.tobytes` also now explicitly cleans up its encoder on
both success and failure, rather than relying on object destruction.

Rejected zero-size TIFF frames
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Seeking to a TIFF frame with a zero width or height now raises a ``SyntaxError``,
as opening such a frame as the first frame already did. Previously, when the image
was opened from a filename and was uncompressed, the frame could be loaded as an
image with no pixels.
2 changes: 2 additions & 0 deletions src/PIL/ImageFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ def load(self) -> Image.core.PixelAccess | None:
args = (args, 0, 1)
if (
decoder_name == "raw"
and extents == (0, 0, *self.size)
and min(self.size) > 0
and isinstance(args, tuple)
and len(args) >= 3
and args[0] == self.mode
Expand Down
3 changes: 3 additions & 0 deletions src/PIL/TiffImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1469,6 +1469,9 @@ def _setup(self) -> None:
if not isinstance(xsize, int) or not isinstance(ysize, int):
msg = "Invalid dimensions"
raise ValueError(msg)
if xsize <= 0 or ysize <= 0:
msg = f"Invalid dimensions {xsize} x {ysize}"
raise SyntaxError(msg)
self._tile_size = xsize, ysize
orientation = self.tag_v2.get(ExifTags.Base.Orientation)
if orientation in (5, 6, 7, 8):
Expand Down
Loading