mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-11-14 21:56:56 +03:00
Merge pull request #8417 from radarhere/appendingTiffWriter
Support writing LONG8 offsets in AppendingTiffWriter
This commit is contained in:
commit
fd74857bcf
|
@ -108,10 +108,6 @@ class TestFileTiff:
|
||||||
assert_image_equal_tofile(im, "Tests/images/hopper.tif")
|
assert_image_equal_tofile(im, "Tests/images/hopper.tif")
|
||||||
|
|
||||||
with Image.open("Tests/images/hopper_bigtiff.tif") as im:
|
with Image.open("Tests/images/hopper_bigtiff.tif") as im:
|
||||||
# The data type of this file's StripOffsets tag is LONG8,
|
|
||||||
# which is not yet supported for offset data when saving multiple frames.
|
|
||||||
del im.tag_v2[273]
|
|
||||||
|
|
||||||
outfile = str(tmp_path / "temp.tif")
|
outfile = str(tmp_path / "temp.tif")
|
||||||
im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2)
|
im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2)
|
||||||
|
|
||||||
|
@ -732,6 +728,20 @@ class TestFileTiff:
|
||||||
with Image.open(mp) as reread:
|
with Image.open(mp) as reread:
|
||||||
assert reread.n_frames == 3
|
assert reread.n_frames == 3
|
||||||
|
|
||||||
|
def test_fixoffsets(self) -> None:
|
||||||
|
b = BytesIO(b"II\x2a\x00\x00\x00\x00\x00")
|
||||||
|
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
||||||
|
b.seek(0)
|
||||||
|
a.fixOffsets(1, isShort=True)
|
||||||
|
|
||||||
|
b.seek(0)
|
||||||
|
a.fixOffsets(1, isLong=True)
|
||||||
|
|
||||||
|
# Neither short nor long
|
||||||
|
b.seek(0)
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
a.fixOffsets(1)
|
||||||
|
|
||||||
def test_saving_icc_profile(self, tmp_path: Path) -> None:
|
def test_saving_icc_profile(self, tmp_path: Path) -> None:
|
||||||
# Tests saving TIFF with icc_profile set.
|
# Tests saving TIFF with icc_profile set.
|
||||||
# At the time of writing this will only work for non-compressed tiffs
|
# At the time of writing this will only work for non-compressed tiffs
|
||||||
|
|
|
@ -2115,13 +2115,24 @@ class AppendingTiffWriter(io.BytesIO):
|
||||||
def write(self, data: Buffer, /) -> int:
|
def write(self, data: Buffer, /) -> int:
|
||||||
return self.f.write(data)
|
return self.f.write(data)
|
||||||
|
|
||||||
def readShort(self) -> int:
|
def _fmt(self, field_size: int) -> str:
|
||||||
(value,) = struct.unpack(self.shortFmt, self.f.read(2))
|
try:
|
||||||
|
return {2: "H", 4: "L", 8: "Q"}[field_size]
|
||||||
|
except KeyError:
|
||||||
|
msg = "offset is not supported"
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
def _read(self, field_size: int) -> int:
|
||||||
|
(value,) = struct.unpack(
|
||||||
|
self.endian + self._fmt(field_size), self.f.read(field_size)
|
||||||
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def readShort(self) -> int:
|
||||||
|
return self._read(2)
|
||||||
|
|
||||||
def readLong(self) -> int:
|
def readLong(self) -> int:
|
||||||
(value,) = struct.unpack(self.longFmt, self.f.read(4))
|
return self._read(4)
|
||||||
return value
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _verify_bytes_written(bytes_written: int | None, expected: int) -> None:
|
def _verify_bytes_written(bytes_written: int | None, expected: int) -> None:
|
||||||
|
@ -2134,15 +2145,18 @@ class AppendingTiffWriter(io.BytesIO):
|
||||||
bytes_written = self.f.write(struct.pack(self.longFmt, value))
|
bytes_written = self.f.write(struct.pack(self.longFmt, value))
|
||||||
self._verify_bytes_written(bytes_written, 4)
|
self._verify_bytes_written(bytes_written, 4)
|
||||||
|
|
||||||
|
def _rewriteLast(self, value: int, field_size: int) -> None:
|
||||||
|
self.f.seek(-field_size, os.SEEK_CUR)
|
||||||
|
bytes_written = self.f.write(
|
||||||
|
struct.pack(self.endian + self._fmt(field_size), value)
|
||||||
|
)
|
||||||
|
self._verify_bytes_written(bytes_written, field_size)
|
||||||
|
|
||||||
def rewriteLastShort(self, value: int) -> None:
|
def rewriteLastShort(self, value: int) -> None:
|
||||||
self.f.seek(-2, os.SEEK_CUR)
|
return self._rewriteLast(value, 2)
|
||||||
bytes_written = self.f.write(struct.pack(self.shortFmt, value))
|
|
||||||
self._verify_bytes_written(bytes_written, 2)
|
|
||||||
|
|
||||||
def rewriteLastLong(self, value: int) -> None:
|
def rewriteLastLong(self, value: int) -> None:
|
||||||
self.f.seek(-4, os.SEEK_CUR)
|
return self._rewriteLast(value, 4)
|
||||||
bytes_written = self.f.write(struct.pack(self.longFmt, value))
|
|
||||||
self._verify_bytes_written(bytes_written, 4)
|
|
||||||
|
|
||||||
def writeShort(self, value: int) -> None:
|
def writeShort(self, value: int) -> None:
|
||||||
bytes_written = self.f.write(struct.pack(self.shortFmt, value))
|
bytes_written = self.f.write(struct.pack(self.shortFmt, value))
|
||||||
|
@ -2174,32 +2188,22 @@ class AppendingTiffWriter(io.BytesIO):
|
||||||
cur_pos = self.f.tell()
|
cur_pos = self.f.tell()
|
||||||
|
|
||||||
if is_local:
|
if is_local:
|
||||||
self.fixOffsets(
|
self._fixOffsets(count, field_size)
|
||||||
count, isShort=(field_size == 2), isLong=(field_size == 4)
|
|
||||||
)
|
|
||||||
self.f.seek(cur_pos + 4)
|
self.f.seek(cur_pos + 4)
|
||||||
else:
|
else:
|
||||||
self.f.seek(offset)
|
self.f.seek(offset)
|
||||||
self.fixOffsets(
|
self._fixOffsets(count, field_size)
|
||||||
count, isShort=(field_size == 2), isLong=(field_size == 4)
|
|
||||||
)
|
|
||||||
self.f.seek(cur_pos)
|
self.f.seek(cur_pos)
|
||||||
|
|
||||||
elif is_local:
|
elif is_local:
|
||||||
# skip the locally stored value that is not an offset
|
# skip the locally stored value that is not an offset
|
||||||
self.f.seek(4, os.SEEK_CUR)
|
self.f.seek(4, os.SEEK_CUR)
|
||||||
|
|
||||||
def fixOffsets(
|
def _fixOffsets(self, count: int, field_size: int) -> None:
|
||||||
self, count: int, isShort: bool = False, isLong: bool = False
|
|
||||||
) -> None:
|
|
||||||
if not isShort and not isLong:
|
|
||||||
msg = "offset is neither short nor long"
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
offset = self.readShort() if isShort else self.readLong()
|
offset = self._read(field_size)
|
||||||
offset += self.offsetOfNewPage
|
offset += self.offsetOfNewPage
|
||||||
if isShort and offset >= 65536:
|
if field_size == 2 and offset >= 65536:
|
||||||
# offset is now too large - we must convert shorts to longs
|
# offset is now too large - we must convert shorts to longs
|
||||||
if count != 1:
|
if count != 1:
|
||||||
msg = "not implemented"
|
msg = "not implemented"
|
||||||
|
@ -2211,10 +2215,19 @@ class AppendingTiffWriter(io.BytesIO):
|
||||||
self.f.seek(-10, os.SEEK_CUR)
|
self.f.seek(-10, os.SEEK_CUR)
|
||||||
self.writeShort(TiffTags.LONG) # rewrite the type to LONG
|
self.writeShort(TiffTags.LONG) # rewrite the type to LONG
|
||||||
self.f.seek(8, os.SEEK_CUR)
|
self.f.seek(8, os.SEEK_CUR)
|
||||||
elif isShort:
|
|
||||||
self.rewriteLastShort(offset)
|
|
||||||
else:
|
else:
|
||||||
self.rewriteLastLong(offset)
|
self._rewriteLast(offset, field_size)
|
||||||
|
|
||||||
|
def fixOffsets(
|
||||||
|
self, count: int, isShort: bool = False, isLong: bool = False
|
||||||
|
) -> None:
|
||||||
|
if isShort:
|
||||||
|
field_size = 2
|
||||||
|
elif isLong:
|
||||||
|
field_size = 4
|
||||||
|
else:
|
||||||
|
field_size = 0
|
||||||
|
return self._fixOffsets(count, field_size)
|
||||||
|
|
||||||
|
|
||||||
def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user