Skip to content

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

Open

Description

@usmanmateen

What did you do?

Converted a float (F) image with large or non-finite values to I, for example a float TIFF or a NumPy result with inf or NaN pixels, or an I image that went through F for processing:

import numpy as np
from PIL import Image

im = Image.fromarray(np.array([[1.5, 70000, 3e9, np.inf, -np.inf, np.nan]], np.float32), "F")
print("I", np.asarray(im.convert("I")).tolist())
print("L", np.asarray(im.convert("L")).tolist())

print(Image.new("I", (1, 1), 2**31 - 1).convert("F").convert("I").getpixel((0, 0)))

What did you expect to happen?

Values outside the I range clipped to it, the way F to L and F to I;16 clip to theirs, and the round trip to keep 2147483647:

I [[1, 70000, 2147483647, 2147483647, -2147483648, 0]]
L [[1, 255, 255, 255, 0, 0]]
2147483647

What actually happened?

Every value above the INT32 range, inf and NaN become -2147483648, the most negative value:

I [[1, 70000, -2147483648, -2147483648, -2147483648, -2147483648]]
L [[1, 255, 255, 255, 0, 0]]
-2147483648

The round trip fails because 2147483647 isn't representable as a float32 and rounds up to 2147483648.0, which is just past the INT32 range.

f2i in Convert.c casts with i = f; and no range check. Converting an out-of-range float or NaN to an integer is undefined behavior in C (-fsanitize=float-cast-overflow reports it), and the result depends on the CPU: x86 gives INT_MIN, while ARM saturates and gives INT32_MAX for large values and 0 for NaN. clip32() in Filter.c already guards the same overflow to INT_MIN.

I can open a PR that clips to the INT32 range in f2i and maps NaN to 0.

What are your OS, Python and Pillow versions?

  • OS: Ubuntu 26.04 (WSL2), x86-64
  • Python: 3.12.13
  • Pillow: 13.0.0

Activity

  1. radarhere commented on Oct 31, 2026

    @radarhere
    Member

    If you're concerned about nan, then I think a more direct approach would be to address the F value?

    >>> from PIL import Image
    >>> im = Image.new("F", (1, 1), float("nan"))
    >>> im.getpixel((0, 0))
    nan
  2. usmanmateen commented on Oct 31, 2026

    @usmanmateen
    ContributorAuthor

    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.

    NaN is also only part of it. Finite values above the range and inf give -2147483648 too, including this round trip, which has no NaN in it:

    >>> Image.new("I", (1, 1), 2**31 - 1).convert("F").convert("I").getpixel((0, 0))
    -2147483648

    The PR maps NaN to 0 because that's what F to L and F to I;16 already give, but I'm happy to change that if you'd prefer a different value.

  3. radarhere commented on Nov 1, 2026

    @radarhere
    Member

    NaN is valid float data in those cases (it's often the nodata value in float TIFFs)

    If you have an image that demonstrates this, that would be cool, even just as an attachment here.

    I don't think rejecting it would be right

    Oh, I wasn't thinking about raising an error. I was thinking about setting it to zero immediately, rather than waiting until conversion.

    ...if you're saying that Pillow should allow F images to store NaN values, I'm not sold. Maybe NumPy or SciPy would do that, but I think Pillow is here for image processing, and while NaN might have meaning as data, I don't think it has meaning as part of an image.

  4. usmanmateen commented on Nov 2, 2026

    @usmanmateen
    ContributorAuthor

    Setting NaN to 0 is fine with me, and it's what the PR already does at conversion. The clip has to put NaN somewhere to avoid the undefined cast, so 0 it is. I don't have a sample file to share; NaN isn't what prompted this.

    The main problem is the finite and infinite values. inf, anything above the INT32 range, and the I → F → I round trip of 2147483647 all become -2147483648, so f2i needs the clip whatever happens to NaN on load.

  5. radarhere commented on Nov 7, 2026

    @radarhere
    Member

    #10373 has been merged, and the original code here now gives

    I [[1, 70000, 2147483647, 2147483647, -2147483648, -2147483648]]
    L [[1, 255, 255, 255, 0, 0]]
    2147483647
    

    So everything but the NaN matches expectations. I'm not sure how much of a dealbreaker that is.

    Does anyone have an example TIFF image with NaN or infinite pixel values?

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions