diff --git a/Tests/images/child_ifd.tiff b/Tests/images/child_ifd.tiff new file mode 100644 index 00000000000..700185d88ae Binary files /dev/null and b/Tests/images/child_ifd.tiff differ diff --git a/Tests/images/child_ifd_jpeg.tiff b/Tests/images/child_ifd_jpeg.tiff new file mode 100644 index 00000000000..f5e3d129d5d Binary files /dev/null and b/Tests/images/child_ifd_jpeg.tiff differ diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index 8706cb950dd..ac0bd7f6096 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -84,6 +84,24 @@ def test_context_manager(self): with Image.open("Tests/images/multipage.tiff") as im: im.load() + @pytest.mark.parametrize( + "path, sizes", + ( + ("Tests/images/hopper.tif", ()), + ("Tests/images/child_ifd.tiff", (16, 8)), + ("Tests/images/child_ifd_jpeg.tiff", (20,)), + ), + ) + def test_get_child_images(self, path, sizes): + with Image.open(path) as im: + ims = im.get_child_images() + + assert len(ims) == len(sizes) + for i, im in enumerate(ims): + w = sizes[i] + expected = Image.new("RGB", (w, w), "#f00") + assert_image_similar(im, expected, 1) + def test_mac_tiff(self): # Read RGBa images from macOS [@PIL136] diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index f2f1299122e..766d46ffb23 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1148,6 +1148,39 @@ 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. diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index e3094b4db47..3f3a1ccd2a7 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -160,6 +160,7 @@ def lookup(tag, group=None): 323: ("TileLength", LONG, 1), 324: ("TileOffsets", LONG, 0), 325: ("TileByteCounts", LONG, 0), + 330: ("SubIFDs", LONG, 0), 332: ("InkSet", SHORT, 1), 333: ("InkNames", ASCII, 1), 334: ("NumberOfInks", SHORT, 1),