Skip to content

Clip F to I conversion to the INT32 range - #10373

Merged
radarhere merged 3 commits into
python-pillow:mainfrom
usmanmateen:fix/f-to-i-overflow
Nov 6, 2026
Merged

radarhere merged 3 commits into
python-pillow:mainfrom
usmanmateen:fix/f-to-i-overflow

Conversation

@usmanmateen

Copy link
Copy Markdown
Contributor

Fixes #10372.

Changes proposed in this pull request:

  • Clip F to I conversion to the INT32 range in f2i, and map NaN to 0. The cast had no range check, so on x86 every value above the range, inf and NaN became -2147483648, and an I image at 2147483647 converted to F and back did too. These casts are undefined behavior in C, so the result also depended on the CPU.
  • Add a test with values at and past both ends of the range, inf, -inf and NaN.
>>> Image.new("F", (1, 1), float("inf")).convert("I").getpixel((0, 0))
-2147483648

With this change, it is 2147483647, the same clipping clip32() in Filter.c does. NaN becomes 0, which is what F to L and F to I;16 give.

f2i cast floats to INT32 with no range check. Out-of-range values and NaN are undefined behavior in C, and on x86 they all became -2147483648. Clip to the INT32 range like clip32() in Filter.c, and map NaN to 0 like the F to L and F to I;16 converters.
@radarhere

Copy link
Copy Markdown
Member

At the moment in main, NaN is rejected by L mode images, but not F.

>>> from PIL import Image
>>> Image.new("L", (1, 1), float("nan"))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "PIL/Image.py", line 3277, in new
    return im._new(core.fill(mode, size, color))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: color must be int or single-element tuple
>>> Image.new("F", (1, 1), float("nan"))
<PIL.Image.Image image mode=F size=1x1 at 0x105DCE3D0>

I've pushed a commit to reject it for F mode as well, and also rearranged the code to be more in line with

*out = (UINT8)(v <= 0 ? 0 : (v >= 255 ? 255 : v));

See what you think.

@radarhere
radarhere merged commit 681e9c4 into python-pillow:main Nov 6, 2026
60 checks passed
@radarhere

radarhere commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

While my last commit added helpful validation, somehow, I missed that it did not actually raise an error for NaN. I've created #10441 to follow up.

@radarhere

Copy link
Copy Markdown
Member

#10372 (comment)

NaN mostly comes in with the data rather than from Image.new(). Image.fromarray(), Image.frombytes() and opening a float TIFF with NaN pixels all keep it, and all three convert to I as -2147483648. NaN is valid float data in those cases (it's often the nodata value in float TIFFs), so I don't think rejecting it would be right.

Ah, sorry, forgot about this. Just as well that I missed it then.

@radarhere

Copy link
Copy Markdown
Member

Here are the current results with the merged form of this PR. Let me know what you think.

>>> from PIL import Image
>>> Image.new("F", (1, 1), float("inf")).convert("I").getpixel((0, 0))
2147483647
>>> Image.new("F", (1, 1), float("nan")).convert("I").getpixel((0, 0))
-2147483648
>>> Image.new("F", (1, 1), float("-inf")).convert("I").getpixel((0, 0))
-2147483648

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Converting F images to I turns large values, inf and NaN into -2147483648

2 participants