mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-26 09:14:27 +03:00
Merge pull request #8642 from radarhere/bigtiff
This commit is contained in:
commit
c7026d9bc8
|
@ -115,6 +115,13 @@ class TestFileTiff:
|
||||||
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)
|
||||||
|
|
||||||
|
def test_bigtiff_save(self, tmp_path: Path) -> None:
|
||||||
|
outfile = str(tmp_path / "temp.tif")
|
||||||
|
hopper().save(outfile, big_tiff=True)
|
||||||
|
|
||||||
|
with Image.open(outfile) as im:
|
||||||
|
assert im.tag_v2._bigtiff is True
|
||||||
|
|
||||||
def test_seek_too_large(self) -> None:
|
def test_seek_too_large(self) -> None:
|
||||||
with pytest.raises(ValueError, match="Unable to seek to frame"):
|
with pytest.raises(ValueError, match="Unable to seek to frame"):
|
||||||
Image.open("Tests/images/seek_too_large.tif")
|
Image.open("Tests/images/seek_too_large.tif")
|
||||||
|
|
|
@ -1208,6 +1208,11 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
|
||||||
|
|
||||||
.. versionadded:: 8.4.0
|
.. versionadded:: 8.4.0
|
||||||
|
|
||||||
|
**big_tiff**
|
||||||
|
If true, the image will be saved as a BigTIFF.
|
||||||
|
|
||||||
|
.. versionadded:: 11.1.0
|
||||||
|
|
||||||
**compression**
|
**compression**
|
||||||
A string containing the desired compression method for the
|
A string containing the desired compression method for the
|
||||||
file. (valid only with libtiff installed) Valid compression
|
file. (valid only with libtiff installed) Valid compression
|
||||||
|
|
|
@ -1,25 +1,6 @@
|
||||||
11.1.0
|
11.1.0
|
||||||
------
|
------
|
||||||
|
|
||||||
Security
|
|
||||||
========
|
|
||||||
|
|
||||||
TODO
|
|
||||||
^^^^
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
:cve:`YYYY-XXXXX`: TODO
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
Backwards Incompatible Changes
|
|
||||||
==============================
|
|
||||||
|
|
||||||
TODO
|
|
||||||
^^^^
|
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
||||||
|
@ -66,6 +47,13 @@ zlib library, and what version of zlib-ng is being used::
|
||||||
features.check_feature("zlib_ng") # True or False
|
features.check_feature("zlib_ng") # True or False
|
||||||
features.version_feature("zlib_ng") # "2.2.2" for example, or None
|
features.version_feature("zlib_ng") # "2.2.2" for example, or None
|
||||||
|
|
||||||
|
Saving TIFF as BigTIFF
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
TIFF images can now be saved as BigTIFF using a ``big_tiff`` argument::
|
||||||
|
|
||||||
|
im.save("out.tiff", big_tiff=True)
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
@ -582,7 +582,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
ifh: bytes = b"II\052\0\0\0\0\0",
|
ifh: bytes = b"II\x2A\x00\x00\x00\x00\x00",
|
||||||
prefix: bytes | None = None,
|
prefix: bytes | None = None,
|
||||||
group: int | None = None,
|
group: int | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -949,16 +949,26 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
warnings.warn(str(msg))
|
warnings.warn(str(msg))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _get_ifh(self):
|
||||||
|
ifh = self._prefix + self._pack("H", 43 if self._bigtiff else 42)
|
||||||
|
if self._bigtiff:
|
||||||
|
ifh += self._pack("HH", 8, 0)
|
||||||
|
ifh += self._pack("Q", 16) if self._bigtiff else self._pack("L", 8)
|
||||||
|
|
||||||
|
return ifh
|
||||||
|
|
||||||
def tobytes(self, offset: int = 0) -> bytes:
|
def tobytes(self, offset: int = 0) -> bytes:
|
||||||
# FIXME What about tagdata?
|
# FIXME What about tagdata?
|
||||||
result = self._pack("H", len(self._tags_v2))
|
result = self._pack("Q" if self._bigtiff else "H", len(self._tags_v2))
|
||||||
|
|
||||||
entries: list[tuple[int, int, int, bytes, bytes]] = []
|
entries: list[tuple[int, int, int, bytes, bytes]] = []
|
||||||
offset = offset + len(result) + len(self._tags_v2) * 12 + 4
|
offset += len(result) + len(self._tags_v2) * (20 if self._bigtiff else 12) + 4
|
||||||
stripoffsets = None
|
stripoffsets = None
|
||||||
|
|
||||||
# pass 1: convert tags to binary format
|
# pass 1: convert tags to binary format
|
||||||
# always write tags in ascending order
|
# always write tags in ascending order
|
||||||
|
fmt = "Q" if self._bigtiff else "L"
|
||||||
|
fmt_size = 8 if self._bigtiff else 4
|
||||||
for tag, value in sorted(self._tags_v2.items()):
|
for tag, value in sorted(self._tags_v2.items()):
|
||||||
if tag == STRIPOFFSETS:
|
if tag == STRIPOFFSETS:
|
||||||
stripoffsets = len(entries)
|
stripoffsets = len(entries)
|
||||||
|
@ -966,11 +976,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
logger.debug("Tag %s, Type: %s, Value: %s", tag, typ, repr(value))
|
logger.debug("Tag %s, Type: %s, Value: %s", tag, typ, repr(value))
|
||||||
is_ifd = typ == TiffTags.LONG and isinstance(value, dict)
|
is_ifd = typ == TiffTags.LONG and isinstance(value, dict)
|
||||||
if is_ifd:
|
if is_ifd:
|
||||||
if self._endian == "<":
|
ifd = ImageFileDirectory_v2(self._get_ifh(), group=tag)
|
||||||
ifh = b"II\x2A\x00\x08\x00\x00\x00"
|
|
||||||
else:
|
|
||||||
ifh = b"MM\x00\x2A\x00\x00\x00\x08"
|
|
||||||
ifd = ImageFileDirectory_v2(ifh, group=tag)
|
|
||||||
values = self._tags_v2[tag]
|
values = self._tags_v2[tag]
|
||||||
for ifd_tag, ifd_value in values.items():
|
for ifd_tag, ifd_value in values.items():
|
||||||
ifd[ifd_tag] = ifd_value
|
ifd[ifd_tag] = ifd_value
|
||||||
|
@ -993,10 +999,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
else:
|
else:
|
||||||
count = len(values)
|
count = len(values)
|
||||||
# figure out if data fits into the entry
|
# figure out if data fits into the entry
|
||||||
if len(data) <= 4:
|
if len(data) <= fmt_size:
|
||||||
entries.append((tag, typ, count, data.ljust(4, b"\0"), b""))
|
entries.append((tag, typ, count, data.ljust(fmt_size, b"\0"), b""))
|
||||||
else:
|
else:
|
||||||
entries.append((tag, typ, count, self._pack("L", offset), data))
|
entries.append((tag, typ, count, self._pack(fmt, offset), data))
|
||||||
offset += (len(data) + 1) // 2 * 2 # pad to word
|
offset += (len(data) + 1) // 2 * 2 # pad to word
|
||||||
|
|
||||||
# update strip offset data to point beyond auxiliary data
|
# update strip offset data to point beyond auxiliary data
|
||||||
|
@ -1007,13 +1013,15 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
values = [val + offset for val in handler(self, data, self.legacy_api)]
|
values = [val + offset for val in handler(self, data, self.legacy_api)]
|
||||||
data = self._write_dispatch[typ](self, *values)
|
data = self._write_dispatch[typ](self, *values)
|
||||||
else:
|
else:
|
||||||
value = self._pack("L", self._unpack("L", value)[0] + offset)
|
value = self._pack(fmt, self._unpack(fmt, value)[0] + offset)
|
||||||
entries[stripoffsets] = tag, typ, count, value, data
|
entries[stripoffsets] = tag, typ, count, value, data
|
||||||
|
|
||||||
# pass 2: write entries to file
|
# pass 2: write entries to file
|
||||||
for tag, typ, count, value, data in entries:
|
for tag, typ, count, value, data in entries:
|
||||||
logger.debug("%s %s %s %s %s", tag, typ, count, repr(value), repr(data))
|
logger.debug("%s %s %s %s %s", tag, typ, count, repr(value), repr(data))
|
||||||
result += self._pack("HHL4s", tag, typ, count, value)
|
result += self._pack(
|
||||||
|
"HHQ8s" if self._bigtiff else "HHL4s", tag, typ, count, value
|
||||||
|
)
|
||||||
|
|
||||||
# -- overwrite here for multi-page --
|
# -- overwrite here for multi-page --
|
||||||
result += b"\0\0\0\0" # end of entries
|
result += b"\0\0\0\0" # end of entries
|
||||||
|
@ -1028,8 +1036,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
|
|
||||||
def save(self, fp: IO[bytes]) -> int:
|
def save(self, fp: IO[bytes]) -> int:
|
||||||
if fp.tell() == 0: # skip TIFF header on subsequent pages
|
if fp.tell() == 0: # skip TIFF header on subsequent pages
|
||||||
# tiff header -- PIL always starts the first IFD at offset 8
|
fp.write(self._get_ifh())
|
||||||
fp.write(self._prefix + self._pack("HL", 42, 8))
|
|
||||||
|
|
||||||
offset = fp.tell()
|
offset = fp.tell()
|
||||||
result = self.tobytes(offset)
|
result = self.tobytes(offset)
|
||||||
|
@ -1680,10 +1687,13 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
msg = f"cannot write mode {im.mode} as TIFF"
|
msg = f"cannot write mode {im.mode} as TIFF"
|
||||||
raise OSError(msg) from e
|
raise OSError(msg) from e
|
||||||
|
|
||||||
ifd = ImageFileDirectory_v2(prefix=prefix)
|
|
||||||
|
|
||||||
encoderinfo = im.encoderinfo
|
encoderinfo = im.encoderinfo
|
||||||
encoderconfig = im.encoderconfig
|
encoderconfig = im.encoderconfig
|
||||||
|
|
||||||
|
ifd = ImageFileDirectory_v2(prefix=prefix)
|
||||||
|
if encoderinfo.get("big_tiff"):
|
||||||
|
ifd._bigtiff = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
compression = encoderinfo["compression"]
|
compression = encoderinfo["compression"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user