Merge pull request #6097 from radarhere/bigtiff

This commit is contained in:
Hugo van Kemenade 2022-03-11 23:05:41 +02:00 committed by GitHub
commit 515957b2ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 47 additions and 9 deletions

Binary file not shown.

View File

@ -87,6 +87,10 @@ class TestFileTiff:
assert_image_similar_tofile(im, "Tests/images/pil136.png", 1) 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( @pytest.mark.parametrize(
"file_name,mode,size,offset", "file_name,mode,size,offset",
[ [

View File

@ -177,6 +177,11 @@ Image._repr_pretty_
identity of the object. This allows Jupyter to describe an image and have that identity of the object. This allows Jupyter to describe an image and have that
description stay the same on subsequent executions of the same code. description stay the same on subsequent executions of the same code.
Added BigTIFF reading
^^^^^^^^^^^^^^^^^^^^^
Support has been added for reading BigTIFF images.
Added BLP saving Added BLP saving
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^

View File

@ -49,7 +49,7 @@ except ImportError:
# PILLOW_VERSION was removed in Pillow 9.0.0. # PILLOW_VERSION was removed in Pillow 9.0.0.
# Use __version__ instead. # Use __version__ instead.
from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins
from ._binary import i32le from ._binary import i32le, o32be, o32le
from ._util import deferred_error, isPath from ._util import deferred_error, isPath
@ -1418,6 +1418,7 @@ class Image:
"".join(self.info["Raw profile type exif"].split("\n")[3:]) "".join(self.info["Raw profile type exif"].split("\n")[3:])
) )
elif hasattr(self, "tag_v2"): elif hasattr(self, "tag_v2"):
self._exif.bigtiff = self.tag_v2._bigtiff
self._exif.endian = self.tag_v2._endian self._exif.endian = self.tag_v2._endian
self._exif.load_from_fp(self.fp, self.tag_v2._offset) self._exif.load_from_fp(self.fp, self.tag_v2._offset)
if exif_info is not None: if exif_info is not None:
@ -3426,6 +3427,7 @@ atexit.register(core.clear_cache)
class Exif(MutableMapping): class Exif(MutableMapping):
endian = None endian = None
bigtiff = False
def __init__(self): def __init__(self):
self._data = {} self._data = {}
@ -3461,10 +3463,15 @@ class Exif(MutableMapping):
return self._fixup_dict(info) return self._fixup_dict(info)
def _get_head(self): def _get_head(self):
version = b"\x2B" if self.bigtiff else b"\x2A"
if self.endian == "<": if self.endian == "<":
return b"II\x2A\x00\x08\x00\x00\x00" head = b"II" + version + b"\x00" + o32le(8)
else: 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): def load(self, data):
# Extract EXIF information. This is highly experimental, # Extract EXIF information. This is highly experimental,

View File

@ -260,6 +260,8 @@ PREFIXES = [
b"II\x2A\x00", # Valid TIFF header with little-endian byte order b"II\x2A\x00", # Valid TIFF header with little-endian byte order
b"MM\x2A\x00", # Invalid TIFF header, assume big-endian b"MM\x2A\x00", # Invalid TIFF header, assume big-endian
b"II\x00\x2A", # Invalid TIFF header, assume little-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 @@ class ImageFileDirectory_v2(MutableMapping):
self._endian = "<" self._endian = "<"
else: else:
raise SyntaxError("not a TIFF IFD") raise SyntaxError("not a TIFF IFD")
self._bigtiff = ifh[2] == 43
self.group = group self.group = group
self.tagtype = {} self.tagtype = {}
""" Dictionary of tag types """ """ Dictionary of tag types """
self.reset() 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 self._legacy_api = False
prefix = property(lambda self: self._prefix) prefix = property(lambda self: self._prefix)
@ -699,6 +704,7 @@ class ImageFileDirectory_v2(MutableMapping):
(TiffTags.FLOAT, "f", "float"), (TiffTags.FLOAT, "f", "float"),
(TiffTags.DOUBLE, "d", "double"), (TiffTags.DOUBLE, "d", "double"),
(TiffTags.IFD, "L", "long"), (TiffTags.IFD, "L", "long"),
(TiffTags.LONG8, "Q", "long8"),
], ],
) )
) )
@ -776,8 +782,17 @@ class ImageFileDirectory_v2(MutableMapping):
self._offset = fp.tell() self._offset = fp.tell()
try: try:
for i in range(self._unpack("H", self._ensure_read(fp, 2))[0]): tag_count = (
tag, typ, count, data = self._unpack("HHL4s", self._ensure_read(fp, 12)) 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 tagname = TiffTags.lookup(tag, self.group).name
typname = TYPES.get(typ, "unknown") typname = TYPES.get(typ, "unknown")
@ -789,9 +804,9 @@ class ImageFileDirectory_v2(MutableMapping):
logger.debug(msg + f" - unsupported type {typ}") logger.debug(msg + f" - unsupported type {typ}")
continue # ignore unsupported type continue # ignore unsupported type
size = count * unit_size size = count * unit_size
if size > 4: if size > (8 if self._bigtiff else 4):
here = fp.tell() 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}" msg += f" Tag Location: {here} - Data Location: {offset}"
fp.seek(offset) fp.seek(offset)
data = ImageFile._safe_read(fp, size) data = ImageFile._safe_read(fp, size)
@ -820,7 +835,11 @@ class ImageFileDirectory_v2(MutableMapping):
) )
logger.debug(msg) 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: except OSError as msg:
warnings.warn(str(msg)) warnings.warn(str(msg))
return return
@ -1042,6 +1061,8 @@ class TiffImageFile(ImageFile.ImageFile):
# Header # Header
ifh = self.fp.read(8) ifh = self.fp.read(8)
if ifh[2] == 43:
ifh += self.fp.read(8)
self.tag_v2 = ImageFileDirectory_v2(ifh) self.tag_v2 = ImageFileDirectory_v2(ifh)

View File

@ -74,6 +74,7 @@ SIGNED_RATIONAL = 10
FLOAT = 11 FLOAT = 11
DOUBLE = 12 DOUBLE = 12
IFD = 13 IFD = 13
LONG8 = 16
TAGS_V2 = { TAGS_V2 = {
254: ("NewSubfileType", LONG, 1), 254: ("NewSubfileType", LONG, 1),