TIFF save options are not applied to appended images
What did you do?
Save a multiframe tiff image with group3 compression and PhotometricInterpretation WhiteIsZero.
What did you expect to happen?
Settings applied to all frames.
pageaaa.tif: TIFF image data, little-endian, direntries=9, height=2291, bps=1, compression=bi-level group 3, PhotometricInterpretation=WhiteIsZero, width=1686
pageaab.tif: TIFF image data, little-endian, direntries=9, height=2291, bps=1, compression=bi-level group 3, PhotometricInterpretation=WhiteIsZero, width=1686
pageaac.tif: TIFF image data, little-endian, direntries=9, height=2291, bps=1, compression=bi-level group 3, PhotometricInterpretation=WhiteIsZero, width=1686
pageaad.tif: TIFF image data, little-endian, direntries=9, height=2291, bps=1, compression=bi-level group 3, PhotometricInterpretation=WhiteIsZero, width=1686
What actually happened?
Settings apply only to the first frame.
Pageaaa.tif: TIFF image data, little-endian, direntries=9, height=2291, bps=1, compression=bi-level group 3, PhotometricInterpretation=WhiteIsZero, width=1686
pageaab.tif: TIFF image data, little-endian, direntries=8, height=2291, compression=none, PhotometricInterpretation=BlackIsZero, width=1686
pageaac.tif: TIFF image data, little-endian, direntries=8, height=2291, compression=none, PhotometricInterpretation=BlackIsZero, width=1686
pageaad.tif: TIFF image data, little-endian, direntries=8, height=2291, compression=none, PhotometricInterpretation=BlackIsZero, width=1686
What are your OS, Python and Pillow versions?
- OS: Ubuntu 24.04 LTS
- Python: 3.12.3
- Pillow: 11.2.1
Pillow 11.1.0 yields the expected result.
git bisect results:
5c93145061953d8633397bb79ace396ab1e71eb5 is the first bad commit
commit 5c93145061953d8633397bb79ace396ab1e71eb5
Author: Andrew Murray <[email protected]>
Date: Fri Feb 28 22:16:52 2025 +1100
Allow encoderconfig and encoderinfo to be set for appended TIFF images
Tests/test_file_tiff.py | 12 ++++++++++++
docs/handbook/image-file-formats.rst | 4 +---
src/PIL/TiffImagePlugin.py | 15 ++++++---------
3 files changed, 19 insertions(+), 12 deletions(-)
Example code:
from PIL import Image
fax_images = []
for page in ["img/1.png", "img/2.png", "img/3.png", "img/4.png"]:
image = Image.open(page)
image = image.convert(mode="1")
fax_images.append(image)
# TIFF settings (0 is white)
tiffinfo = {262: 0}
first = fax_images.pop(0)
first.save(
"fax-document.tif",
"TIFF",
append_images=fax_images,
save_all=True,
compression="group3",
tiffinfo=tiffinfo,
)
Test commands:
python test-case.py
tiffsplit fax-document.tif page
file page*tif
Hi. This change was made to allow users to save different information to different frames, see #8775.
Instead, you can specify the arguments for each subsequent frame with image.encoderinfo = {"tiffinfo": {262: 0}, "compression": "group3"}.
from PIL import Image
fax_images = []
for page in ["img/1.png", "img/2.png", "img/3.png", "img/4.png"]:
image = Image.open(page)
image = image.convert(mode="1")
image.encoderinfo = {"tiffinfo": {262: 0}, "compression": "group3"}
fax_images.append(image)
# TIFF settings (0 is white)
tiffinfo = {262: 0}
first = fax_images.pop(0)
first.save(
"fax-document.tif",
"TIFF",
append_images=fax_images,
save_all=True,
compression="group3",
tiffinfo=tiffinfo,
)
Yes, that will work. But I would expect that the save options apply by default to all frames, as the old behavior was, unless you specify encoderinfo properties explicitly for a frame.
- 11.1.0
- Save options apply to all frames
- Image encoderinfo ignored (encoderinfo of first frame applies to all)
- 11.2.1
- Save options apply to first frame only
- Subsequent frames use only encoderinfo of the current frame
- proposal:
- Save options apply to first frame, and all subsequent frames by default
- Subsequent frames will use default, unless explicit encoderinfo has been specified
This will restore the old behavior, and allow for different information in different frames.
I can make a PR for this.
@anntzer, as the creator of #8775, do you have any thoughts on this?
Sure, the proposed behavior seems reasonable (in fact, likely better). I guess the current strategy in Image.save (stash the params into encoderinfo and later let the plugin's _save_all parse that) will need to be refined to distinguish between globally set parameters and parameters originally set on the first frame's encoderinfo but not to be applied to all frames, though.
I've created https://github.com/python-pillow/Pillow/pull/9001 to resolve this.
This PR works for me and seems more generic than #8974, so please merge.