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
Do not premultiply alpha when resizing with Image.NEAREST resampling #5304
Conversation
So, in what I presume is an extreme situation to allow for ease of testing, your test demonstrates that converting the image to pre-multiplied alpha and then back to normal alpha causes a loss of information when the alpha is zero - because any color with zero alpha becomes black when converted to pre-multiplied alpha. It may not be related, but I find that if I take one of the values from the issue, and convert it to RGBa and then back again, >>> from PIL import Image
>>> im = Image.new("RGBA", (1, 1), (2, 2, 0, 64))
>>> im.convert("RGBa").load()[0, 0]
(1, 1, 0, 64)
>>> im.convert("RGBa").convert("RGBA").load()[0, 0]
(3, 3, 0, 64) the roundtrip changes the value. That's odd. |
It's not really that odd when you consider what the operation is doing. Premultiplying with alpha 64, you are rescaling each color from 0-255 to 0-64 range, effectively losing two least significant bits. Therefore there are four different values for each RGBA color band that all map to the same value in RGBa. Converting back to RGBA then chooses the "middle" value. >>> from PIL import Image
>>> Image.new("RGBA", (1,1), (1,1,0,64)).convert("RGBa").load()[0,0]
(0, 0, 0, 64)
>>> Image.new("RGBA", (1,1), (2,2,0,64)).convert("RGBa").load()[0,0]
(1, 1, 0, 64)
>>> Image.new("RGBA", (1,1), (3,3,0,64)).convert("RGBa").load()[0,0]
(1, 1, 0, 64)
>>> Image.new("RGBA", (1,1), (4,4,0,64)).convert("RGBa").load()[0,0]
(1, 1, 0, 64)
>>> Image.new("RGBA", (1,1), (5,5,0,64)).convert("RGBa").load()[0,0]
(1, 1, 0, 64)
>>> Image.new("RGBA", (1,1), (6,6,0,64)).convert("RGBa").load()[0,0]
(2, 2, 0, 64)
>>> Image.new("RGBa", (1,1), (1,1,0,64)).convert("RGBA").load()[0,0]
(3, 3, 0, 64) |
Thanks. So that's why NEAREST should not roundtrip through pre-multiply conversion - since it changes the non-alpha channel values. But have you figured out what the original purpose of it was? In other words, why it's done for any resampling methods? It's not just a shortcut method, as it actually produces different results. I would like to understand the reason so that I know why it's ok to disregard that reason in this case. |
The reason to use RGBa is explained by When using NEAREST downsampling, the conversion only discards some pixels keeping the ones nearest to the result image grid, while upsampling duplicates pixels. Edit: There is also an awesome blog post with excellent animations demonstrating this issue with BILINEAR resampling: http://www.adriancourreges.com/blog/2017/05/09/beware-of-transparent-pixels/ |
https://pillow.readthedocs.io/en/stable/handbook/concepts.html#PIL.Image.BOX
So that makes BOX an exception, yes? |
Ah, yes, but only for upsampling. Downsampling produces an average of multiple pixels, with each included pixel having the same weight. |
Fixes #5300.
Test is based on the previous tests that make sure premultiplied alpha is used with BILINEAR resampling.
From https://pillow.readthedocs.io/en/stable/handbook/concepts.html#PIL.Image.NEAREST:
I would interpret that as meaning that each pixel in the output will be equal to one of the original pixels, i.e. not introducing new values.