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

Inconsistent size when using thumbnail #6528

Closed
0phoff opened this issue Aug 23, 2022 · 8 comments
Closed

Inconsistent size when using thumbnail #6528

0phoff opened this issue Aug 23, 2022 · 8 comments

Comments

@0phoff
Copy link

0phoff commented Aug 23, 2022

What did you do?

I want to use the thumbnail function to resize images to a certain width, whilst keeping the aspect ratio.
I thus computed a rescaling factor by dividing my target width by the actual width, and then used that scale to compute a new size for the thumbnail function (See code below).

The thumbnail function recomputes the width & height to match the aspect ratio as close as possible.
However, you cannot choose whether it adapts the width or the height in order to achieve this.
Looking at the code, it seems to be adapting the width if the new aspect ratio is bigger than the old, and the height otherwise.
I might by wrong on this, but isn't this pretty random?
Couldn't you preserve the aspect ratio by only changing either width or height.

It would be nice if the thumbnail method would allow you to either:

  • Pass a scale instead of a width & height. That way we simply compute the new size as: floor(x * scale), floor(y * scale).
  • Give only a target width or a target height. Let the method compute the other value.

What are your OS, Python and Pillow versions?

  • OS: Linux
  • Python: 3.8
  • Pillow: 8.2.0
from PIL import Image

# Image 1 : correct
img1 = Image.new('L', (256, 162))
scale = 250 / img1.width
img1.thumbnail((img1.width * scale, img1.height * scale))
print(img1.size)
# (250, 158)

# Image 2 : wrong
img2 = Image.new('L', (260, 162))
scale = 250 / img2.width
img2.thumbnail((img2.width * scale, img2.height * scale))
print(img2.size)
# (249, 155) -> but I wanted (250, 155)
@radarhere
Copy link
Member

radarhere commented Aug 24, 2022

https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.thumbnail

This method modifies the image to contain a thumbnail version of itself, no larger than the given size.

I don't think it's random. I think it's resizing the image to fit within the size specified.

@radarhere
Copy link
Member

radarhere commented Aug 24, 2022

If you want a method to reduce an image by an integer factor, we have the reduce() method - https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.reduce

@radarhere
Copy link
Member

but I wanted (250, 155)

>>> 162 / 260 * 250
155.76923076923077

So I don't think you should ever get (250, 155), if the aspect ratio is being strictly kept. If you want (250, 155) anyway, then I think you should resize() the image, so that the aspect ratio isn't required.

If you want to scale an image by only one dimension, then I think that using ImageOps.contain with a high value for the other dimension would suit.

from PIL import Image, ImageOps
img2 = Image.new('L', (260, 162))
scale = 250 / img2.width
x = img2.width * scale
img3 = ImageOps.contain(img2, (int(x), 1000000))
print(img3.size)  # (250, 156)

@0phoff does any of that answer your question?

@0phoff
Copy link
Author

0phoff commented Aug 26, 2022

Thanks for the expansive answer!

I am currently optimizing my data loading pipeline for a deep learning application and wanted to use thumbnail because some of my images are JPEG and thumbnail should allow to load them more efficiently (using draft, which only reads the lower DCT coefficients IIRC).

My main frustration comes from the fact that for rescaling while maintaining aspect ratio, you should not have to pass both a width and height. Only one of them is required and the other can be automatically computed. What's more, the current API does not easily allow to control whether you keep a fixed width or height. I could not figure out how to build a pipeline that thumbnails my images to a size of 250x... Depending on the original image size, the thumbnail function would either modify the given width or height.

Would it be a possibility to expand the API so users can pass None for either width or height and let the function compute it automatically ? That way you can force thumbnail to always return images with eg. a width of 250px.


FYI, I might be completely mistaken because I am not familiar with the pillow codebase, but thumbnail starts by calling the load function. Doesn't this defeat the purpose of the draft method ?
Unless this is completely normal, I am willing to investigate this and potentially open another issue ;)

@radarhere
Copy link
Member

FYI, I might be completely mistaken because I am not familiar with the pillow codebase, but thumbnail starts by calling the load function. Doesn't this defeat the purpose of the draft method ? Unless this is completely normal, I am willing to investigate this and potentially open another issue ;)

You make a good point. You can workaround this by calling draft() directly, but this is not the correct behaviour, no. I've created #6539 to fix this.

@radarhere
Copy link
Member

I could not figure out how to build a pipeline that thumbnails my images to a size of 250x...

I've been presuming you want this API just for convenience, but if you do mean that you can't figure out how to do it at present, then you could just use a high value for the other dimension.

from PIL import Image
img2 = Image.new('L', (260, 162))
scale = 250 / img2.width
img2.thumbnail((img2.width * scale, 100000))
print(img2.size)  # (250, 156)

I made this comment about ImageOps.contain() earlier, but I now realise you can also do it with thumbnail() itself.

@radarhere
Copy link
Member

Would it be a possibility to expand the API so users can pass None for either width or height and let the function compute it automatically ? That way you can force thumbnail to always return images with eg. a width of 250px.

If this change were added to thumbnail, then why not resize? Or ImageOps.fit, ImageOps.contain or ImageOps.pad? I think I've demonstrated that you can achieve your goal with the current API. Even if you couldn't, you could calculate the size with your own code, and then pass it to Pillow. I don't see a compelling case to change the API for five methods.

@radarhere
Copy link
Member

PR #6539 has been merged.

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

No branches or pull requests

2 participants