Skip to content

Allow ImageOps.scale() to return zero dimension images - #10364

Merged
radarhere merged 2 commits into
python-pillow:mainfrom
qodelicious:fix-scale-zero-dimension
Oct 31, 2026
Merged

radarhere merged 2 commits into
python-pillow:mainfrom
qodelicious:fix-scale-zero-dimension

Conversation

@qodelicious

Copy link
Copy Markdown
Contributor

Changes proposed in this pull request:

Comment thread src/PIL/ImageOps.py
else:
size = (round(factor * image.width), round(factor * image.height))
if min(size) == 0:
return _new_empty(image, size)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this path load the image before copying its metadata? Pillow’s existing Tests/images/hopper.png has text chunks after IDAT. With this PR’s method:

from PIL import Image, ImageOps

with Image.open("Tests/images/hopper.png") as im:
    small = ImageOps.scale(im, 0.001)
    print("comment" in small.info)  # False

A non-empty scale retains comment, and loading first retains it here too. PNG permits text chunks after IDAT. The existing zero-size ImageOps.contain() path also skips this metadata, so the current behavior may be intentional. Loading first would decode the source even when the result is empty, which is a performance trade-off.

If metadata preservation is preferred, this worked in the scoped check:

Suggested change
return _new_empty(image, size)
image.load()
return _new_empty(image, size)

I checked the exact PR Python methods against an existing Pillow 13.0.0 runtime, including 144 in-memory mode/size/factor controls; I did not build the current C extensions or run the full suite. OpenAI Codex (GPT-6) assisted with the verification and review.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a tradeoff for the sake of performance, and one that I'm willing to live with for now.

When a PR made sure that we were preserving palette and transparency here, my comment was #10299 (comment),

Creating an empty image in ImageOps contain() is pretty niche, and doing anything afterwards that would use palettes or transparency is even more niche.

I think opening a PNG, scaling it, and then trying to read the comment from after load() is still more obscure. If the user had strong expectations of correctly reading the comment, they would have to know about load(). So your scenario requires them to expect ImageOps to load the image? Or to think that they could load() after scaling to get the same effect?

Overall, there are some operations that you can only run on the original image in Pillow. You can't open a TIFF image, copy it, and then try to read the original TIFF tags

>>> from PIL import Image
>>> im = Image.open("ests/images/hopper.tif") 
>>> im.tag_v2
<PIL.TiffImagePlugin.ImageFileDirectory_v2 object at 0x10997fed0>
>>> im.copy().tag_v2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Image' object has no attribute 'tag_v2'

If anyone comes in saying that this feature would help them, then we can reconsider.

Comment thread Tests/test_imageops.py Resolved
@radarhere
radarhere merged commit 8a0e434 into python-pillow:main Oct 31, 2026
50 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants