diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index cf169a3ec5a..3d6f946728c 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -155,6 +155,45 @@ def test_bigtiff_save_big_endian_ifd(self, tmp_path: Path) -> None: ifd.load(BytesIO(data)) assert ifd.tagtype[32768] == TiffTags.LONG8 + def test_bigtiff_save_two_strips(self, tmp_path: Path) -> None: + outfile = tmp_path / "temp.tif" + im = hopper("L") + + # Two LONG strip offsets fit within a BigTIFF entry + im.save(outfile, big_tiff=True, tiffinfo={TiffImagePlugin.ROWSPERSTRIP: 64}) + + with Image.open(outfile) as reloaded: + assert isinstance(reloaded, TiffImagePlugin.TiffImageFile) + assert len(reloaded.tag_v2[TiffImagePlugin.STRIPOFFSETS]) == 2 + assert_image_equal(reloaded, im) + + def test_bigtiff_save_big_endian_strip_offset(self, tmp_path: Path) -> None: + outfile = tmp_path / "temp.tif" + # I;16B is saved as big-endian + im = hopper("I;16B") + + # The single LONG strip offset fits within a BigTIFF entry + im.save(outfile, big_tiff=True) + + with Image.open(outfile) as reloaded: + assert isinstance(reloaded, TiffImagePlugin.TiffImageFile) + assert reloaded.tag_v2.prefix == b"MM" + assert reloaded.tag_v2._bigtiff is True + assert ( + reloaded.tag_v2.tagtype[TiffImagePlugin.STRIPOFFSETS] == TiffTags.LONG + ) + assert len(reloaded.tag_v2[TiffImagePlugin.STRIPOFFSETS]) == 1 + assert_image_equal(reloaded, im) + + # Check that the strip offset is upgraded to LONG8 + # when it is too large for a LONG + data = reloaded.tag_v2.tobytes(2**32) + + ifd = TiffImagePlugin.ImageFileDirectory_v2(b"MM\x00\x2b" + b"\x00" * 12) + ifd.load(BytesIO(data)) + assert ifd.tagtype[TiffImagePlugin.STRIPOFFSETS] == TiffTags.LONG8 + assert ifd[TiffImagePlugin.STRIPOFFSETS][0] > 2**32 + def test_seek_too_large(self) -> None: with pytest.raises(ValueError, match="Unable to seek to frame"): Image.open("Tests/images/seek_too_large.tif") diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 6b26b4fb0fb..be8dd435e7f 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1054,12 +1054,25 @@ def tobytes(self, offset: int = 0) -> bytes: # update strip offset data to point beyond auxiliary data if stripoffsets is not None: tag, typ, count, value, data = entries[stripoffsets] + size, handler = self._load_dispatch[typ] + values = [ + val + offset for val in handler(self, data or value, self.legacy_api) + ] if data: - size, handler = self._load_dispatch[typ] - values = [val + offset for val in handler(self, data, self.legacy_api)] data = self._write_dispatch[typ](self, *values) else: - value = self._pack(fmt, self._unpack(fmt, value)[0] + offset) + # The offsets fit within the entry + # May be one offset, or two LONG offsets in BigTIFF + if ( + self._bigtiff + and typ == TiffTags.LONG + and count == 1 + and values[0] >= 2**32 + ): + # If a single offset is too large for a LONG, + # in BigTIFF there is room to change the entry to LONG8 + typ = TiffTags.LONG8 + value = self._write_dispatch[typ](self, *values).ljust(fmt_size, b"\0") entries[stripoffsets] = tag, typ, count, value, data # pass 2: write entries to file