Merge pull request #8780 from radarhere/save_all

Allow encoderconfig and encoderinfo to be set for appended TIFF images
This commit is contained in:
Hugo van Kemenade 2025-03-05 18:04:28 +02:00 committed by GitHub
commit 00593ff3f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 34 additions and 13 deletions

View File

@ -660,6 +660,18 @@ class TestFileTiff:
assert isinstance(im, TiffImagePlugin.TiffImageFile) assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im.tag_v2[278] == 256 assert im.tag_v2[278] == 256
im = hopper()
im2 = Image.new("L", (128, 128))
im2.encoderinfo = {"tiffinfo": {278: 256}}
im.save(outfile, save_all=True, append_images=[im2])
with Image.open(outfile) as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
assert im.tag_v2[278] == 128
im.seek(1)
assert im.tag_v2[278] == 256
def test_strip_raw(self) -> None: def test_strip_raw(self) -> None:
infile = "Tests/images/tiff_strip_raw.tif" infile = "Tests/images/tiff_strip_raw.tif"
with Image.open(infile) as im: with Image.open(infile) as im:

View File

@ -1163,9 +1163,7 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
**append_images** **append_images**
A list of images to append as additional frames. Each of the A list of images to append as additional frames. Each of the
images in the list can be single or multiframe images. Note however, that for images in the list can be single or multiframe images.
correct results, all the appended images should have the same
``encoderinfo`` and ``encoderconfig`` properties.
.. versionadded:: 4.2.0 .. versionadded:: 4.2.0

View File

@ -2475,7 +2475,21 @@ class Image:
format to use is determined from the filename extension. format to use is determined from the filename extension.
If a file object was used instead of a filename, this If a file object was used instead of a filename, this
parameter should always be used. parameter should always be used.
:param params: Extra parameters to the image writer. :param params: Extra parameters to the image writer. These can also be
set on the image itself through ``encoderinfo``. This is useful when
saving multiple images::
# Saving XMP data to a single image
from PIL import Image
red = Image.new("RGB", (1, 1), "#f00")
red.save("out.mpo", xmp=b"test")
# Saving XMP data to the second frame of an image
from PIL import Image
black = Image.new("RGB", (1, 1))
red = Image.new("RGB", (1, 1), "#f00")
red.encoderinfo = {"xmp": b"test"}
black.save("out.mpo", save_all=True, append_images=[red])
:returns: None :returns: None
:exception ValueError: If the output format could not be determined :exception ValueError: If the output format could not be determined
from the file name. Use the format option to solve this. from the file name. Use the format option to solve this.

View File

@ -2295,9 +2295,7 @@ class AppendingTiffWriter(io.BytesIO):
def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
encoderinfo = im.encoderinfo.copy() append_images = list(im.encoderinfo.get("append_images", []))
encoderconfig = im.encoderconfig
append_images = list(encoderinfo.get("append_images", []))
if not hasattr(im, "n_frames") and not append_images: if not hasattr(im, "n_frames") and not append_images:
return _save(im, fp, filename) return _save(im, fp, filename)
@ -2305,12 +2303,11 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
try: try:
with AppendingTiffWriter(fp) as tf: with AppendingTiffWriter(fp) as tf:
for ims in [im] + append_images: for ims in [im] + append_images:
ims.encoderinfo = encoderinfo if not hasattr(ims, "encoderinfo"):
ims.encoderconfig = encoderconfig ims.encoderinfo = {}
if not hasattr(ims, "n_frames"): if not hasattr(ims, "encoderconfig"):
nfr = 1 ims.encoderconfig = ()
else: nfr = getattr(ims, "n_frames", 1)
nfr = ims.n_frames
for idx in range(nfr): for idx in range(nfr):
ims.seek(idx) ims.seek(idx)