Repository navigation
Allow ImageOps.scale() to return zero dimension images - #10364
Conversation
| else: | ||
| size = (round(factor * image.width), round(factor * image.height)) | ||
| if min(size) == 0: | ||
| return _new_empty(image, size) |
There was a problem hiding this comment.
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) # FalseA 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:
| 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.
There was a problem hiding this comment.
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.
Changes proposed in this pull request:
ImageOps.scale()raisedValueError: height and width must be > 0when the new size had a zero dimension, e.g. scaling a 5x0 image, or a 10x1 image by 0.4.contain(),cover()andfit()do since Allow ImageOps to create zero dimension images #9673 and Preserve palette and transparency in zero dimension ImageOps results #10299.