diff --git a/Tests/images/hopper_bigtiff.tif b/Tests/images/hopper_bigtiff.tif new file mode 100644 index 00000000000..9588a37d80b Binary files /dev/null and b/Tests/images/hopper_bigtiff.tif differ diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 28aeff075cb..d8d5753f6db 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -87,6 +87,10 @@ def test_mac_tiff(self): assert_image_similar_tofile(im, "Tests/images/pil136.png", 1) + def test_bigtiff(self): + with Image.open("Tests/images/hopper_bigtiff.tif") as im: + assert_image_equal_tofile(im, "Tests/images/hopper.tif") + @pytest.mark.parametrize( "file_name,mode,size,offset", [ diff --git a/src/PIL/Image.py b/src/PIL/Image.py index c9265b5ab62..4ef42af3349 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -49,7 +49,7 @@ # PILLOW_VERSION was removed in Pillow 9.0.0. # Use __version__ instead. from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins -from ._binary import i32le +from ._binary import i32le, o32be, o32le from ._util import deferred_error, isPath @@ -1416,6 +1416,7 @@ def getexif(self): "".join(self.info["Raw profile type exif"].split("\n")[3:]) ) elif hasattr(self, "tag_v2"): + self._exif.bigtiff = self.tag_v2._bigtiff self._exif.endian = self.tag_v2._endian self._exif.load_from_fp(self.fp, self.tag_v2._offset) if exif_info is not None: @@ -3423,6 +3424,7 @@ def _apply_env_variables(env=None): class Exif(MutableMapping): endian = None + bigtiff = False def __init__(self): self._data = {} @@ -3458,10 +3460,15 @@ def _get_ifd_dict(self, offset): return self._fixup_dict(info) def _get_head(self): + version = b"\x2B" if self.bigtiff else b"\x2A" if self.endian == "<": - return b"II\x2A\x00\x08\x00\x00\x00" + head = b"II" + version + b"\x00" + o32le(8) else: - return b"MM\x00\x2A\x00\x00\x00\x08" + head = b"MM\x00" + version + o32be(8) + if self.bigtiff: + head += o32le(8) if self.endian == "<" else o32be(8) + head += b"\x00\x00\x00\x00" + return head def load(self, data): # Extract EXIF information. This is highly experimental, diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 35ff1c1bbf7..e5d1b915b0c 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -260,6 +260,8 @@ b"II\x2A\x00", # Valid TIFF header with little-endian byte order b"MM\x2A\x00", # Invalid TIFF header, assume big-endian b"II\x00\x2A", # Invalid TIFF header, assume little-endian + b"MM\x00\x2B", # BigTIFF with big-endian byte order + b"II\x2B\x00", # BigTIFF with little-endian byte order ] @@ -502,11 +504,14 @@ def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=None): self._endian = "<" else: raise SyntaxError("not a TIFF IFD") + self._bigtiff = ifh[2] == 43 self.group = group self.tagtype = {} """ Dictionary of tag types """ self.reset() - (self.next,) = self._unpack("L", ifh[4:]) + (self.next,) = ( + self._unpack("Q", ifh[8:]) if self._bigtiff else self._unpack("L", ifh[4:]) + ) self._legacy_api = False prefix = property(lambda self: self._prefix) @@ -699,6 +704,7 @@ def _register_basic(idx_fmt_name): (TiffTags.FLOAT, "f", "float"), (TiffTags.DOUBLE, "d", "double"), (TiffTags.IFD, "L", "long"), + (TiffTags.LONG8, "Q", "long8"), ], ) ) @@ -776,8 +782,17 @@ def load(self, fp): self._offset = fp.tell() try: - for i in range(self._unpack("H", self._ensure_read(fp, 2))[0]): - tag, typ, count, data = self._unpack("HHL4s", self._ensure_read(fp, 12)) + tag_count = ( + self._unpack("Q", self._ensure_read(fp, 8)) + if self._bigtiff + else self._unpack("H", self._ensure_read(fp, 2)) + )[0] + for i in range(tag_count): + tag, typ, count, data = ( + self._unpack("HHQ8s", self._ensure_read(fp, 20)) + if self._bigtiff + else self._unpack("HHL4s", self._ensure_read(fp, 12)) + ) tagname = TiffTags.lookup(tag, self.group).name typname = TYPES.get(typ, "unknown") @@ -789,9 +804,9 @@ def load(self, fp): logger.debug(msg + f" - unsupported type {typ}") continue # ignore unsupported type size = count * unit_size - if size > 4: + if size > (8 if self._bigtiff else 4): here = fp.tell() - (offset,) = self._unpack("L", data) + (offset,) = self._unpack("Q" if self._bigtiff else "L", data) msg += f" Tag Location: {here} - Data Location: {offset}" fp.seek(offset) data = ImageFile._safe_read(fp, size) @@ -820,7 +835,11 @@ def load(self, fp): ) logger.debug(msg) - (self.next,) = self._unpack("L", self._ensure_read(fp, 4)) + (self.next,) = ( + self._unpack("Q", self._ensure_read(fp, 8)) + if self._bigtiff + else self._unpack("L", self._ensure_read(fp, 4)) + ) except OSError as msg: warnings.warn(str(msg)) return @@ -1042,6 +1061,8 @@ def _open(self): # Header ifh = self.fp.read(8) + if ifh[2] == 43: + ifh += self.fp.read(8) self.tag_v2 = ImageFileDirectory_v2(ifh) diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index 88856aa92d5..b37c8cf5ff4 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -74,6 +74,7 @@ def lookup(tag, group=None): FLOAT = 11 DOUBLE = 12 IFD = 13 +LONG8 = 16 TAGS_V2 = { 254: ("NewSubfileType", LONG, 1),