Pillow icon indicating copy to clipboard operation
Pillow copied to clipboard

ImageOps.exif_transpose() raises struct.error on invalid EXIF rational

Open epou opened this issue 7 months ago • 4 comments

What did you do?

Attempted to process an image with ImageOps.exif_transpose(), which contains an EXIF tag with a value of (-1, 0).

What did you expect to happen?

The image should be processed without raising an error, with invalid EXIF values (like (-1, 0)) handled gracefully as float('nan') as a result of (1, 0).

What actually happened?

>>> from PIL import Image, ImageOps
>>> img = Image.open('/path/to/file.jpg')
>>> ImageOps.exif_transpose(img)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/user/venv_pillow/lib/python3.12/site-packages/PIL/ImageOps.py", line 712, in exif_transpose
    exif_image.info["exif"] = exif.tobytes()
                              ^^^^^^^^^^^^^^
  File "/home/user/venv_pillow/lib/python3.12/site-packages/PIL/Image.py", line 4043, in tobytes
    return b"Exif\x00\x00" + head + ifd.tobytes(offset)
                                    ^^^^^^^^^^^^^^^^^^^
  File "/home/user/venv_pillow/lib/python3.12/site-packages/PIL/TiffImagePlugin.py", line 983, in tobytes
    data = ifd.tobytes(offset)
           ^^^^^^^^^^^^^^^^^^^
  File "/home/user/venv_pillow/lib/python3.12/site-packages/PIL/TiffImagePlugin.py", line 986, in tobytes
    data = self._write_dispatch[typ](self, *values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/user/venv_pillow/lib/python3.12/site-packages/PIL/TiffImagePlugin.py", line 841, in write_rational
    return b"".join(
           ^^^^^^^^^
  File "/home/user/venv_pillow/lib/python3.12/site-packages/PIL/TiffImagePlugin.py", line 842, in <genexpr>
    self._pack("2L", *_limit_rational(frac, 2**32 - 1)) for frac in values
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/user/venv_pillow/lib/python3.12/site-packages/PIL/TiffImagePlugin.py", line 782, in _pack
    return struct.pack(self._endian + fmt, *values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
struct.error: 'L' format requires 0 <= number <= 4294967295

What are your OS, Python and Pillow versions?

  • OS: Ubuntu 22.04
  • Python: 3.12.3
  • Pillow: 11.1.0
(venv_pillow)  user@thinkpad  ~  python3 -m PIL.report
--------------------------------------------------------------------
Pillow 11.1.0
Python 3.12.3 (main, Feb  4 2025, 14:48:35) [GCC 13.3.0]
--------------------------------------------------------------------
Python executable is /home/user/venv_pillow/bin/python3
Environment Python files loaded from /home/user/venv_pillow
System Python files loaded from /usr
--------------------------------------------------------------------
Python Pillow modules loaded from /home/user/venv_pillow/lib/python3.12/site-packages/PIL
Binary Pillow modules loaded from /home/user/venv_pillow/lib/python3.12/site-packages/PIL
--------------------------------------------------------------------
--- PIL CORE support ok, compiled for 11.1.0
--- TKINTER support ok, loaded 8.6
--- FREETYPE2 support ok, loaded 2.13.2
--- LITTLECMS2 support ok, loaded 2.16
--- WEBP support ok, loaded 1.5.0
--- JPEG support ok, compiled for libjpeg-turbo 3.1.0
--- OPENJPEG (JPEG2000) support ok, loaded 2.5.3
--- ZLIB (PNG/ZIP) support ok, loaded 1.3, compiled for zlib-ng 2.2.2
--- LIBTIFF support ok, loaded 4.6.0
--- RAQM (Bidirectional Text) support ok, loaded 0.10.1, fribidi 1.0.13, harfbuzz 10.1.0
*** LIBIMAGEQUANT (Quantization method) support not installed
--- XCB (X protocol) support ok

epou avatar May 21 '25 13:05 epou

Are you able to attach a copy of the image were you found this?

radarhere avatar May 22 '25 03:05 radarhere

Sure, here it is :) Image

epou avatar May 22 '25 06:05 epou

Thanks.

I've created #8970 as another possible solution. See what you think.

radarhere avatar May 22 '25 10:05 radarhere

Here is an overview of the possible solutions suggested so far.

  1. When setting ImageFileDirectory_v2 values, instead of just comparing an IFDRational against zero https://github.com/python-pillow/Pillow/blob/086e05f42ff459747c2ceb0e8e046f4b54e4d779/src/PIL/TiffImagePlugin.py#L691 replace it with if v < 0 or (math.isnan(v) and float(v.numerator) < 0): - #8966
  2. Replacing it with if (float(v.numerator) < 0) != (float(v.denominator) < 0): - original idea of #8970
  3. Replacing it with if v < IFDRational(0): - the current idea of #8970
  4. Changing IFDRational so that 0/0 is nan, positive numerators over a zero denominator are positive infinity, and negative numerators over a zero denominator is negative infinity - #8978

radarhere avatar May 27 '25 09:05 radarhere