Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change in behavior with JPEG Images in 11.0.0 #8472

Open
davidmezzetti opened this issue Oct 16, 2024 · 5 comments · May be fixed by #8476
Open

Change in behavior with JPEG Images in 11.0.0 #8472

davidmezzetti opened this issue Oct 16, 2024 · 5 comments · May be fixed by #8476
Labels

Comments

@davidmezzetti
Copy link

davidmezzetti commented Oct 16, 2024

The following code that worked in Pillow 10.4 no longer works with Pillow 11.0.

import pickle

from io import BytesIO

from PIL import Image

im = Image.open("books.jpg")

p = pickle.dumps(im)
im = pickle.loads(p)

# Fails in 11.0, works in 10.4
output = BytesIO()
im.save(output, format=im.format, quality="keep")

Error stack.

Traceback (most recent call last):
  File "test.py", line 15, in <module>
    im.save(output, format=im.format, quality="keep")
  File "python3.9/site-packages/PIL/Image.py", line 2605, in save
    save_handler(self, fp, filename)
  File "python3.9/site-packages/PIL/JpegImagePlugin.py", line 700, in _save
    subsampling = get_sampling(im)
  File "python3.9/site-packages/PIL/JpegImagePlugin.py", line 643, in get_sampling
    if not isinstance(im, JpegImageFile) or im.layers in (1, 4):
  File "python3.9/site-packages/PIL/JpegImagePlugin.py", line 396, in __getattr__
    raise AttributeError(name)
AttributeError: layers

This has been traced to the following change in src/PIL/JpegImagePlugin.py

Pillow 10.4.0

Code

def get_sampling(im):
...
    if not hasattr(im, "layers") or im.layers in (1, 4):
        return -1
...

Pillow 11.0.0

Code

def get_sampling(im: Image.Image) -> int:
...
    if not isinstance(im, JpegImageFile) or im.layers in (1, 4):
        return -1
...

Suggested fix

def get_sampling(im: Image.Image) -> int:
...
    if not isinstance(im, JpegImageFile) or not hasattr(im, "layers") or im.layers in (1, 4):
        return -1
...

Note that the following hack does workaround the issue in 11.0.

im.layers = 1
im.save(output, format=im.format, quality="keep")
@aclark4life
Copy link
Member

Is this a bug or feature? It looks like Pillow got more strict in verifying that im was a JpegImageFile ?

@davidmezzetti
Copy link
Author

That is a good question. Serializing an image and reloading it changes how it looks.

I wasn't sure if this check for the layers attribute was at one point intentional for this use case or just coincidental.

@Yay295
Copy link
Contributor

Yay295 commented Oct 16, 2024

Is there a reason you want to pickle the ImageFile instead of just opening the image again?

@davidmezzetti
Copy link
Author

The serialization is happening in a different part of the code. This example is for simplicity in reproducing.

@radarhere radarhere linked a pull request Oct 17, 2024 that will close this issue
@radarhere
Copy link
Member

I've created #8476 as a possible solution to this - rather than ignoring the fact that layers was removed by the pickling process, let's make sure it stays present.

@radarhere radarhere changed the title Change in behavior with JPEG Images in 11.0 Change in behavior with JPEG Images in 11.0.0 Oct 17, 2024
@radarhere radarhere added the JPEG label Nov 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants