From 24a5405a9f7ea22f28f9c98b3e407292ea5ee1d3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 28 Nov 2022 08:39:56 +1100 Subject: [PATCH 1/5] Added IFD enum --- docs/reference/ExifTags.rst | 7 ++++++ src/PIL/ExifTags.py | 7 ++++++ src/PIL/Image.py | 45 ++++++++++++++++++++++++------------- src/PIL/MpoImagePlugin.py | 11 +++++++-- 4 files changed, 52 insertions(+), 18 deletions(-) diff --git a/docs/reference/ExifTags.rst b/docs/reference/ExifTags.rst index d362334a59c..650bb4f9546 100644 --- a/docs/reference/ExifTags.rst +++ b/docs/reference/ExifTags.rst @@ -31,6 +31,13 @@ which provide constants and clear-text names for various well-known EXIF tags. >>> Interop(4096).name 'RelatedImageFileFormat' +.. py:data:: IFD + + >>> from PIL.ExifTags import IFD + >>> IFD.Exif.value + 34665 + >>> IFD(34665).name + 'Exif' Two of these values are also exposed as dictionaries. diff --git a/src/PIL/ExifTags.py b/src/PIL/ExifTags.py index c00730ba91d..97a21335f14 100644 --- a/src/PIL/ExifTags.py +++ b/src/PIL/ExifTags.py @@ -346,3 +346,10 @@ class Interop(IntEnum): RelatedImageFileFormat = 4096 RelatedImageWidth = 4097 RleatedImageHeight = 4098 + + +class IFD(IntEnum): + Exif = 34665 + GPSInfo = 34853 + Makernote = 37500 + Interop = 40965 diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 7faf0c2481b..3fcc869315a 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -47,7 +47,14 @@ # VERSION was removed in Pillow 6.0.0. # PILLOW_VERSION was removed in Pillow 9.0.0. # Use __version__ instead. -from . import ImageMode, TiffTags, UnidentifiedImageError, __version__, _plugins +from . import ( + ExifTags, + ImageMode, + TiffTags, + UnidentifiedImageError, + __version__, + _plugins, +) from ._binary import i32le, o32be, o32le from ._deprecate import deprecate from ._util import DeferredError, is_path @@ -3598,14 +3605,16 @@ def _get_merged_dict(self): merged_dict = dict(self) # get EXIF extension - if 0x8769 in self: - ifd = self._get_ifd_dict(self[0x8769]) + if ExifTags.IFD.Exif in self: + ifd = self._get_ifd_dict(self[ExifTags.IFD.Exif]) if ifd: merged_dict.update(ifd) # GPS - if 0x8825 in self: - merged_dict[0x8825] = self._get_ifd_dict(self[0x8825]) + if ExifTags.IFD.GPSInfo in self: + merged_dict[ExifTags.IFD.GPSInfo] = self._get_ifd_dict( + self[ExifTags.IFD.GPSInfo] + ) return merged_dict @@ -3615,30 +3624,34 @@ def tobytes(self, offset=8): head = self._get_head() ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head) for tag, value in self.items(): - if tag in [0x8769, 0x8225, 0x8825] and not isinstance(value, dict): + if tag in [ + ExifTags.IFD.Exif, + 0x8225, + ExifTags.IFD.GPSInfo, + ] and not isinstance(value, dict): value = self.get_ifd(tag) if ( - tag == 0x8769 - and 0xA005 in value - and not isinstance(value[0xA005], dict) + tag == ExifTags.IFD.Exif + and ExifTags.IFD.Interop in value + and not isinstance(value[ExifTags.IFD.Interop], dict) ): value = value.copy() - value[0xA005] = self.get_ifd(0xA005) + value[ExifTags.IFD.Interop] = self.get_ifd(ExifTags.IFD.Interop) ifd[tag] = value return b"Exif\x00\x00" + head + ifd.tobytes(offset) def get_ifd(self, tag): if tag not in self._ifds: - if tag in [0x8769, 0x8825]: + if tag in [ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo]: # exif, gpsinfo if tag in self: self._ifds[tag] = self._get_ifd_dict(self[tag]) - elif tag in [0xA005, 0x927C]: + elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.Makernote]: # interop, makernote - if 0x8769 not in self._ifds: - self.get_ifd(0x8769) - tag_data = self._ifds[0x8769][tag] - if tag == 0x927C: + if ExifTags.IFD.Exif not in self._ifds: + self.get_ifd(ExifTags.IFD.Exif) + tag_data = self._ifds[ExifTags.IFD.Exif][tag] + if tag == ExifTags.IFD.Makernote: # makernote from .TiffImagePlugin import ImageFileDirectory_v2 diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index 92d288f2fb1..3ae4d4abf5b 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -22,7 +22,14 @@ import os import struct -from . import Image, ImageFile, ImageSequence, JpegImagePlugin, TiffImagePlugin +from . import ( + ExifTags, + Image, + ImageFile, + ImageSequence, + JpegImagePlugin, + TiffImagePlugin, +) from ._binary import i16be as i16 from ._binary import o32le @@ -137,7 +144,7 @@ def seek(self, frame): mptype = self.mpinfo[0xB002][frame]["Attribute"]["MPType"] if mptype.startswith("Large Thumbnail"): - exif = self.getexif().get_ifd(0x8769) + exif = self.getexif().get_ifd(ExifTags.IFD.Exif) if 40962 in exif and 40963 in exif: self._size = (exif[40962], exif[40963]) elif "exif" in self.info: From a0326245a288801b7ea4753eef32d94398c5b9af Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 9 Nov 2022 21:19:13 +1100 Subject: [PATCH 2/5] Removed typo --- src/PIL/Image.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 3fcc869315a..d07fc716ce2 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3626,7 +3626,6 @@ def tobytes(self, offset=8): for tag, value in self.items(): if tag in [ ExifTags.IFD.Exif, - 0x8225, ExifTags.IFD.GPSInfo, ] and not isinstance(value, dict): value = self.get_ifd(tag) From 8ada23ed04ee18730d44d14dd82b0aabc12a0917 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 5 Dec 2022 09:09:00 +1100 Subject: [PATCH 3/5] Added IFD1 reading --- Tests/test_image.py | 21 ++++++++++++++++++++- src/PIL/ExifTags.py | 1 + src/PIL/Image.py | 10 +++++----- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index e579034904d..b4e81e46613 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -7,7 +7,14 @@ import pytest -from PIL import Image, ImageDraw, ImagePalette, UnidentifiedImageError, features +from PIL import ( + ExifTags, + Image, + ImageDraw, + ImagePalette, + UnidentifiedImageError, + features, +) from .helper import ( assert_image_equal, @@ -808,6 +815,18 @@ def test_exif_interop(self): reloaded_exif.load(exif.tobytes()) assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005) + def test_exif_ifd1(self): + with Image.open("Tests/images/flower.jpg") as im: + exif = im.getexif() + assert exif.get_ifd(ExifTags.IFD.IFD1) == { + 513: 2036, + 514: 5448, + 259: 6, + 296: 2, + 282: 180.0, + 283: 180.0, + } + def test_exif_ifd(self): with Image.open("Tests/images/flower.jpg") as im: exif = im.getexif() diff --git a/src/PIL/ExifTags.py b/src/PIL/ExifTags.py index 97a21335f14..ffab7e55454 100644 --- a/src/PIL/ExifTags.py +++ b/src/PIL/ExifTags.py @@ -353,3 +353,4 @@ class IFD(IntEnum): GPSInfo = 34853 Makernote = 37500 Interop = 40965 + IFD1 = -1 diff --git a/src/PIL/Image.py b/src/PIL/Image.py index d07fc716ce2..1f3d4b74f9c 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3641,17 +3641,17 @@ def tobytes(self, offset=8): def get_ifd(self, tag): if tag not in self._ifds: - if tag in [ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo]: - # exif, gpsinfo + if tag == ExifTags.IFD.IFD1: + if self._info is not None: + self._ifds[tag] = self._get_ifd_dict(self._info.next) + elif tag in [ExifTags.IFD.Exif, ExifTags.IFD.GPSInfo]: if tag in self: self._ifds[tag] = self._get_ifd_dict(self[tag]) elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.Makernote]: - # interop, makernote if ExifTags.IFD.Exif not in self._ifds: self.get_ifd(ExifTags.IFD.Exif) tag_data = self._ifds[ExifTags.IFD.Exif][tag] if tag == ExifTags.IFD.Makernote: - # makernote from .TiffImagePlugin import ImageFileDirectory_v2 if tag_data[:8] == b"FUJIFILM": @@ -3727,7 +3727,7 @@ def get_ifd(self, tag): makernote = {0x1101: dict(self._fixup_dict(camerainfo))} self._ifds[tag] = makernote else: - # interop + # Interop self._ifds[tag] = self._get_ifd_dict(tag_data) return self._ifds.get(tag, {}) From c2a42655e10c7b3888f3c50b49717886296e1720 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 6 Dec 2022 19:30:53 +1100 Subject: [PATCH 4/5] Allow get_child_images to access JPEG thumbnails --- Tests/images/flower_thumbnail.png | Bin 0 -> 35617 bytes Tests/test_file_jpeg.py | 7 +++++ src/PIL/Image.py | 43 ++++++++++++++++++++++++++++++ src/PIL/JpegImagePlugin.py | 1 + src/PIL/TiffImagePlugin.py | 33 ----------------------- 5 files changed, 51 insertions(+), 33 deletions(-) create mode 100644 Tests/images/flower_thumbnail.png diff --git a/Tests/images/flower_thumbnail.png b/Tests/images/flower_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..4a362535f25fcdf10c49c368f4252f48f8d45bd0 GIT binary patch literal 35617 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 RR_6c!002ovPDHLkV1fuV02lxO literal 0 HcmV?d00001 diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index fa96e425b8c..9871875565f 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -415,6 +415,13 @@ def test_exif(self): info = im._getexif() assert info[305] == "Adobe Photoshop CS Macintosh" + def test_get_child_images(self): + with Image.open("Tests/images/flower.jpg") as im: + ims = im.get_child_images() + + assert len(ims) == 1 + assert_image_equal_tofile(ims[0], "Tests/images/flower_thumbnail.png") + def test_mp(self): with Image.open("Tests/images/pil_sample_rgb.jpg") as im: assert im._getmp() is None diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 1f3d4b74f9c..e568e6afa0b 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1454,6 +1454,49 @@ def _reload_exif(self): self._exif._loaded = False self.getexif() + def get_child_images(self): + child_images = [] + exif = self.getexif() + ifds = [] + if ExifTags.Base.SubIFDs in exif: + subifd_offsets = exif[ExifTags.Base.SubIFDs] + if subifd_offsets: + if not isinstance(subifd_offsets, tuple): + subifd_offsets = (subifd_offsets,) + for subifd_offset in subifd_offsets: + ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset)) + ifd1 = exif.get_ifd(ExifTags.IFD.IFD1) + if ifd1 and ifd1.get(513): + ifds.append((ifd1, exif._info.next)) + + offset = None + for ifd, ifd_offset in ifds: + current_offset = self.fp.tell() + if offset is None: + offset = current_offset + + fp = self.fp + thumbnailOffset = ifd.get(513) + if thumbnailOffset is not None: + try: + thumbnailOffset += self._exif_offset + except AttributeError: + pass + self.fp.seek(thumbnailOffset) + data = self.fp.read(ifd.get(514)) + fp = io.BytesIO(data) + + with open(fp) as im: + if thumbnailOffset is None: + im._frame_pos = [ifd_offset] + im._seek(0) + im.load() + child_images.append(im) + + if offset is not None: + self.fp.seek(offset) + return child_images + def getim(self): """ Returns a capsule that points to the internal image memory. diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index a6ed223bc6f..f2d8c4846f8 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -89,6 +89,7 @@ def APP(self, marker): if "exif" not in self.info: # extract EXIF information (incomplete) self.info["exif"] = s # FIXME: value will change + self._exif_offset = self.fp.tell() - n + 6 elif marker == 0xFFE2 and s[:5] == b"FPXR\0": # extract FlashPix information (incomplete) self.info["flashpix"] = s # FIXME: value will change diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index ab9ac5ea23a..aa2a782c28f 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1153,39 +1153,6 @@ def tell(self): """Return the current frame number""" return self.__frame - def get_child_images(self): - if SUBIFD not in self.tag_v2: - return [] - child_images = [] - exif = self.getexif() - offset = None - for im_offset in self.tag_v2[SUBIFD]: - # reset buffered io handle in case fp - # was passed to libtiff, invalidating the buffer - current_offset = self._fp.tell() - if offset is None: - offset = current_offset - - fp = self._fp - ifd = exif._get_ifd_dict(im_offset) - jpegInterchangeFormat = ifd.get(513) - if jpegInterchangeFormat is not None: - fp.seek(jpegInterchangeFormat) - jpeg_data = fp.read(ifd.get(514)) - - fp = io.BytesIO(jpeg_data) - - with Image.open(fp) as im: - if jpegInterchangeFormat is None: - im._frame_pos = [im_offset] - im._seek(0) - im.load() - child_images.append(im) - - if offset is not None: - self._fp.seek(offset) - return child_images - def getxmp(self): """ Returns a dictionary containing the XMP tags. From 5301b86f1cd255fc55a464b38af176f37f91c396 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 14 Dec 2022 06:48:36 +1100 Subject: [PATCH 5/5] Use snake case --- src/PIL/Image.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index e568e6afa0b..c2216e27ad0 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1476,18 +1476,18 @@ def get_child_images(self): offset = current_offset fp = self.fp - thumbnailOffset = ifd.get(513) - if thumbnailOffset is not None: + thumbnail_offset = ifd.get(513) + if thumbnail_offset is not None: try: - thumbnailOffset += self._exif_offset + thumbnail_offset += self._exif_offset except AttributeError: pass - self.fp.seek(thumbnailOffset) + self.fp.seek(thumbnail_offset) data = self.fp.read(ifd.get(514)) fp = io.BytesIO(data) with open(fp) as im: - if thumbnailOffset is None: + if thumbnail_offset is None: im._frame_pos = [ifd_offset] im._seek(0) im.load()