mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-22 07:14:34 +03:00
Merge pull request #8689 from radarhere/get_child_images
This commit is contained in:
commit
4f7510b77b
|
@ -989,6 +989,11 @@ class TestImage:
|
|||
else:
|
||||
assert im.getxmp() == {"xmpmeta": None}
|
||||
|
||||
def test_get_child_images(self) -> None:
|
||||
im = Image.new("RGB", (1, 1))
|
||||
with pytest.warns(DeprecationWarning):
|
||||
assert im.get_child_images() == []
|
||||
|
||||
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
|
||||
def test_zero_tobytes(self, size: tuple[int, int]) -> None:
|
||||
im = Image.new("RGB", size)
|
||||
|
|
|
@ -183,6 +183,16 @@ ExifTags.IFD.Makernote
|
|||
``ExifTags.IFD.Makernote`` has been deprecated. Instead, use
|
||||
``ExifTags.IFD.MakerNote``.
|
||||
|
||||
Image.Image.get_child_images()
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. deprecated:: 11.2.0
|
||||
|
||||
``Image.Image.get_child_images()`` has been deprecated. and will be removed in Pillow
|
||||
13 (2026-10-15). It will be moved to ``ImageFile.ImageFile.get_child_images()``. The
|
||||
method uses an image's file pointer, and so child images could only be retrieved from
|
||||
an :py:class:`PIL.ImageFile.ImageFile` instance.
|
||||
|
||||
Removed features
|
||||
----------------
|
||||
|
||||
|
|
58
docs/releasenotes/11.2.0.rst
Normal file
58
docs/releasenotes/11.2.0.rst
Normal file
|
@ -0,0 +1,58 @@
|
|||
11.2.0
|
||||
------
|
||||
|
||||
Security
|
||||
========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
:cve:`YYYY-XXXXX`: TODO
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
|
||||
Backwards Incompatible Changes
|
||||
==============================
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
Deprecations
|
||||
============
|
||||
|
||||
Image.Image.get_child_images()
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. deprecated:: 11.2.0
|
||||
|
||||
``Image.Image.get_child_images()`` has been deprecated. and will be removed in Pillow
|
||||
13 (2026-10-15). It will be moved to ``ImageFile.ImageFile.get_child_images()``. The
|
||||
method uses an image's file pointer, and so child images could only be retrieved from
|
||||
an :py:class:`PIL.ImageFile.ImageFile` instance.
|
||||
|
||||
API Changes
|
||||
===========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
API Additions
|
||||
=============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
Other Changes
|
||||
=============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
|
@ -14,6 +14,7 @@ expected to be backported to earlier versions.
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
11.2.0
|
||||
11.1.0
|
||||
11.0.0
|
||||
10.4.0
|
||||
|
|
|
@ -1554,50 +1554,10 @@ class Image:
|
|||
self.getexif()
|
||||
|
||||
def get_child_images(self) -> list[ImageFile.ImageFile]:
|
||||
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(ExifTags.Base.JpegIFOffset):
|
||||
assert exif._info is not None
|
||||
ifds.append((ifd1, exif._info.next))
|
||||
from . import ImageFile
|
||||
|
||||
offset = None
|
||||
for ifd, ifd_offset in ifds:
|
||||
current_offset = self.fp.tell()
|
||||
if offset is None:
|
||||
offset = current_offset
|
||||
|
||||
fp = self.fp
|
||||
if ifd is not None:
|
||||
thumbnail_offset = ifd.get(ExifTags.Base.JpegIFOffset)
|
||||
if thumbnail_offset is not None:
|
||||
thumbnail_offset += getattr(self, "_exif_offset", 0)
|
||||
self.fp.seek(thumbnail_offset)
|
||||
data = self.fp.read(ifd.get(ExifTags.Base.JpegIFByteCount))
|
||||
fp = io.BytesIO(data)
|
||||
|
||||
with open(fp) as im:
|
||||
from . import TiffImagePlugin
|
||||
|
||||
if thumbnail_offset is None and isinstance(
|
||||
im, TiffImagePlugin.TiffImageFile
|
||||
):
|
||||
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
|
||||
deprecate("Image.Image.get_child_images", 13)
|
||||
return ImageFile.ImageFile.get_child_images(self) # type: ignore[arg-type]
|
||||
|
||||
def getim(self) -> CapsuleType:
|
||||
"""
|
||||
|
|
|
@ -36,7 +36,7 @@ import struct
|
|||
import sys
|
||||
from typing import IO, TYPE_CHECKING, Any, NamedTuple, cast
|
||||
|
||||
from . import Image
|
||||
from . import ExifTags, Image
|
||||
from ._deprecate import deprecate
|
||||
from ._util import is_path
|
||||
|
||||
|
@ -163,6 +163,57 @@ class ImageFile(Image.Image):
|
|||
def _open(self) -> None:
|
||||
pass
|
||||
|
||||
def get_child_images(self) -> list[ImageFile]:
|
||||
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(ExifTags.Base.JpegIFOffset):
|
||||
assert exif._info is not None
|
||||
ifds.append((ifd1, exif._info.next))
|
||||
|
||||
offset = None
|
||||
for ifd, ifd_offset in ifds:
|
||||
assert self.fp is not None
|
||||
current_offset = self.fp.tell()
|
||||
if offset is None:
|
||||
offset = current_offset
|
||||
|
||||
fp = self.fp
|
||||
if ifd is not None:
|
||||
thumbnail_offset = ifd.get(ExifTags.Base.JpegIFOffset)
|
||||
if thumbnail_offset is not None:
|
||||
thumbnail_offset += getattr(self, "_exif_offset", 0)
|
||||
self.fp.seek(thumbnail_offset)
|
||||
|
||||
length = ifd.get(ExifTags.Base.JpegIFByteCount)
|
||||
assert isinstance(length, int)
|
||||
data = self.fp.read(length)
|
||||
fp = io.BytesIO(data)
|
||||
|
||||
with Image.open(fp) as im:
|
||||
from . import TiffImagePlugin
|
||||
|
||||
if thumbnail_offset is None and isinstance(
|
||||
im, TiffImagePlugin.TiffImageFile
|
||||
):
|
||||
im._frame_pos = [ifd_offset]
|
||||
im._seek(0)
|
||||
im.load()
|
||||
child_images.append(im)
|
||||
|
||||
if offset is not None:
|
||||
assert self.fp is not None
|
||||
self.fp.seek(offset)
|
||||
return child_images
|
||||
|
||||
def get_format_mimetype(self) -> str | None:
|
||||
if self.custom_mimetype:
|
||||
return self.custom_mimetype
|
||||
|
|
|
@ -47,6 +47,8 @@ def deprecate(
|
|||
raise RuntimeError(msg)
|
||||
elif when == 12:
|
||||
removed = "Pillow 12 (2025-10-15)"
|
||||
elif when == 13:
|
||||
removed = "Pillow 13 (2026-10-15)"
|
||||
else:
|
||||
msg = f"Unknown removal version: {when}. Update {__name__}?"
|
||||
raise ValueError(msg)
|
||||
|
|
Loading…
Reference in New Issue
Block a user