Skip to content

Commit

Permalink
Merge pull request python-pillow#8642 from radarhere/bigtiff
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk authored Dec 31, 2024
2 parents c3fac1d + 1de617f commit c7026d9
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 36 deletions.
7 changes: 7 additions & 0 deletions Tests/test_file_tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ def test_bigtiff(self, tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.tif")
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:
with pytest.raises(ValueError, match="Unable to seek to frame"):
Image.open("Tests/images/seek_too_large.tif")
Expand Down
5 changes: 5 additions & 0 deletions docs/handbook/image-file-formats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,11 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum

.. versionadded:: 8.4.0

**big_tiff**
If true, the image will be saved as a BigTIFF.

.. versionadded:: 11.1.0

**compression**
A string containing the desired compression method for the
file. (valid only with libtiff installed) Valid compression
Expand Down
26 changes: 7 additions & 19 deletions docs/releasenotes/11.1.0.rst
Original file line number Diff line number Diff line change
@@ -1,25 +1,6 @@
11.1.0
------

Security
========

TODO
^^^^

TODO

:cve:`YYYY-XXXXX`: TODO
^^^^^^^^^^^^^^^^^^^^^^^

TODO

Backwards Incompatible Changes
==============================

TODO
^^^^

Deprecations
============

Expand Down Expand Up @@ -66,6 +47,13 @@ zlib library, and what version of zlib-ng is being used::
features.check_feature("zlib_ng") # True or False
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
=============

Expand Down
44 changes: 27 additions & 17 deletions src/PIL/TiffImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ class ImageFileDirectory_v2(_IFDv2Base):

def __init__(
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,
group: int | None = None,
) -> None:
Expand Down Expand Up @@ -949,28 +949,34 @@ def load(self, fp: IO[bytes]) -> None:
warnings.warn(str(msg))
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:
# 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]] = []
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

# pass 1: convert tags to binary format
# 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()):
if tag == STRIPOFFSETS:
stripoffsets = len(entries)
typ = self.tagtype[tag]
logger.debug("Tag %s, Type: %s, Value: %s", tag, typ, repr(value))
is_ifd = typ == TiffTags.LONG and isinstance(value, dict)
if is_ifd:
if self._endian == "<":
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)
ifd = ImageFileDirectory_v2(self._get_ifh(), group=tag)
values = self._tags_v2[tag]
for ifd_tag, ifd_value in values.items():
ifd[ifd_tag] = ifd_value
Expand All @@ -993,10 +999,10 @@ def tobytes(self, offset: int = 0) -> bytes:
else:
count = len(values)
# figure out if data fits into the entry
if len(data) <= 4:
entries.append((tag, typ, count, data.ljust(4, b"\0"), b""))
if len(data) <= fmt_size:
entries.append((tag, typ, count, data.ljust(fmt_size, b"\0"), b""))
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

# update strip offset data to point beyond auxiliary data
Expand All @@ -1007,13 +1013,15 @@ def tobytes(self, offset: int = 0) -> bytes:
values = [val + offset for val in handler(self, data, self.legacy_api)]
data = self._write_dispatch[typ](self, *values)
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

# pass 2: write entries to file
for tag, typ, count, value, data in entries:
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 --
result += b"\0\0\0\0" # end of entries
Expand All @@ -1028,8 +1036,7 @@ def tobytes(self, offset: int = 0) -> bytes:

def save(self, fp: IO[bytes]) -> int:
if fp.tell() == 0: # skip TIFF header on subsequent pages
# tiff header -- PIL always starts the first IFD at offset 8
fp.write(self._prefix + self._pack("HL", 42, 8))
fp.write(self._get_ifh())

offset = fp.tell()
result = self.tobytes(offset)
Expand Down Expand Up @@ -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"
raise OSError(msg) from e

ifd = ImageFileDirectory_v2(prefix=prefix)

encoderinfo = im.encoderinfo
encoderconfig = im.encoderconfig

ifd = ImageFileDirectory_v2(prefix=prefix)
if encoderinfo.get("big_tiff"):
ifd._bigtiff = True

try:
compression = encoderinfo["compression"]
except KeyError:
Expand Down

0 comments on commit c7026d9

Please sign in to comment.