Skip to content

Commit

Permalink
Added BigTIFF reading
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere committed Feb 28, 2022
1 parent 37d28ce commit fc73193
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 9 deletions.
Binary file added Tests/images/hopper_bigtiff.tif
Binary file not shown.
4 changes: 4 additions & 0 deletions Tests/test_file_tiff.py
Expand Up @@ -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",
[
Expand Down
13 changes: 10 additions & 3 deletions src/PIL/Image.py
Expand Up @@ -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


Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -3423,6 +3424,7 @@ def _apply_env_variables(env=None):

class Exif(MutableMapping):
endian = None
bigtiff = False

def __init__(self):
self._data = {}
Expand Down Expand Up @@ -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,
Expand Down
33 changes: 27 additions & 6 deletions src/PIL/TiffImagePlugin.py
Expand Up @@ -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
]


Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"),
],
)
)
Expand Down Expand Up @@ -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")
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
1 change: 1 addition & 0 deletions src/PIL/TiffTags.py
Expand Up @@ -74,6 +74,7 @@ def lookup(tag, group=None):
FLOAT = 11
DOUBLE = 12
IFD = 13
LONG8 = 16

TAGS_V2 = {
254: ("NewSubfileType", LONG, 1),
Expand Down

0 comments on commit fc73193

Please sign in to comment.