Skip to content
Merged
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
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 @@

def __init__(
self,
ifh: bytes = b"II\052\0\0\0\0\0",
ifh: bytes = b"II\x2A\x00\x00\x00\x00\x00",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same, I'm just changing it to the form seen elsewhere in this file.

prefix: bytes | None = None,
group: int | None = None,
) -> None:
Expand Down Expand Up @@ -949,28 +949,34 @@
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)

Check warning on line 956 in src/PIL/TiffImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/TiffImagePlugin.py#L952-L956

Added lines #L952 - L956 were not covered by tests
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


return ifh

Check warning on line 958 in src/PIL/TiffImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/TiffImagePlugin.py#L958

Added line #L958 was not covered by tests

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))

Check warning on line 962 in src/PIL/TiffImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/TiffImagePlugin.py#L962

Added line #L962 was not covered by tests

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

Check warning on line 965 in src/PIL/TiffImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/TiffImagePlugin.py#L965

Added line #L965 was not covered by tests
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

Check warning on line 971 in src/PIL/TiffImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/TiffImagePlugin.py#L970-L971

Added lines #L970 - L971 were not covered by tests
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)

Check warning on line 979 in src/PIL/TiffImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/TiffImagePlugin.py#L979

Added line #L979 was not covered by tests
values = self._tags_v2[tag]
for ifd_tag, ifd_value in values.items():
ifd[ifd_tag] = ifd_value
Expand All @@ -993,10 +999,10 @@
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""))

Check warning on line 1003 in src/PIL/TiffImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/TiffImagePlugin.py#L1002-L1003

Added lines #L1002 - L1003 were not covered by tests
else:
entries.append((tag, typ, count, self._pack("L", offset), data))
entries.append((tag, typ, count, self._pack(fmt, offset), data))

Check warning on line 1005 in src/PIL/TiffImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/TiffImagePlugin.py#L1005

Added line #L1005 was not covered by tests
offset += (len(data) + 1) // 2 * 2 # pad to word

# update strip offset data to point beyond auxiliary data
Expand All @@ -1007,13 +1013,15 @@
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)

Check warning on line 1016 in src/PIL/TiffImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/TiffImagePlugin.py#L1016

Added line #L1016 was not covered by tests
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(

Check warning on line 1022 in src/PIL/TiffImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/TiffImagePlugin.py#L1022

Added line #L1022 was not covered by tests
"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 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())

Check warning on line 1039 in src/PIL/TiffImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/TiffImagePlugin.py#L1039

Added line #L1039 was not covered by tests

offset = fp.tell()
result = self.tobytes(offset)
Expand Down Expand Up @@ -1680,10 +1687,13 @@
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

Check warning on line 1695 in src/PIL/TiffImagePlugin.py

View check run for this annotation

Codecov / codecov/patch

src/PIL/TiffImagePlugin.py#L1693-L1695

Added lines #L1693 - L1695 were not covered by tests

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