Skip to content

Fix BigTIFF strip offsets that fit within the entry - #10260

Merged
radarhere merged 2 commits into
python-pillow:mainfrom
ybai08:fix-bigtiff-inline-strip-offsets
Oct 21, 2026
Merged

radarhere merged 2 commits into
python-pillow:mainfrom
ybai08:fix-bigtiff-inline-strip-offsets

Conversation

@ybai08

@ybai08 ybai08 commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Changes proposed in this pull request:

  • After writing the auxiliary data, ImageFileDirectory_v2.tobytes() adds the data offset to the StripOffsets values. If the values fit within the entry, it did this by unpacking the whole entry as one integer of the offset size (L, or Q for BigTIFF), adding the offset and packing it again. StripOffsets is written as LONG, so in an 8-byte BigTIFF entry:
    • With two strips, the two LONG values were treated as one 8-byte integer, so the offset was only added to the first strip. The second strip pointed into the start of the file, and the image read back wrong in both Pillow and libtiff.
    • In a big-endian BigTIFF (I;16B), the offset was added to the wrong half of the entry, so the single strip offset read back as 0, and the pixels read back wrong.
  • The values that fit within the entry are now decoded and re-encoded with the tag's own type, as the branch above already does for values stored outside the entry, and left-justified in the entry. As in Fix reading and writing big-endian BigTIFF images #10255, a single strip offset that no longer fits in a LONG is written as LONG8 instead. Classic TIFF output is unchanged, since a 4-byte entry only fits a single LONG offset.
  • Tests:
    • test_bigtiff_save_two_strips saves hopper("L") as BigTIFF with RowsPerStrip 64, so there are two strips, and checks that it reads back equal.
    • test_bigtiff_save_big_endian_strip_offset saves hopper("I;16B") as BigTIFF and checks that it reads back equal. It also checks that the strip offset is written as LONG8 when the IFD is written at an offset of 2**32.

Testing:

  • On main, both new tests fail with Failed: got different content, and both pass with this change. Without the LONG8 upgrade, test_bigtiff_save_big_endian_strip_offset and the existing test_bigtiff_save_big_endian_ifd fail with struct.error: 'L' format requires 0 <= number <= 4294967295.
  • Cross-check with libtiff's tiffdump:
    • A 300x300 L image saved with big_tiff=True, tiffinfo={278: 150} gives StripOffsets (273) LONG (4) 2<212 45000> on main and 2<212 45212> with this change.
    • A 300x300 I;16B BigTIFF gives 1<0> on main and 1<212> with this change.
    • With this change, tiffcp copies of both files have the same pixels as the source.
  • python3 selftest.py passes. python3 -m pytest Tests -n 6 on macOS arm64 gives 5391 passed, 365 skipped, 3 xfailed.
  • ruff check (0.16.10) and black --check (26.10.0) on the changed files pass. mypy and the full pre-commit run --all-files were not run.
  • Not changed here: StripOffsets values of 2**32 or more still raise struct.error when there is more than one strip, as they already did on main once the values are stored outside the entry. Two such offsets within the entry were silently written wrong before, and now raise the same error.

@radarhere
radarhere force-pushed the fix-bigtiff-inline-strip-offsets branch from 1c0edc3 to d8bc325 Compare October 21, 2026 00:52
@radarhere
radarhere force-pushed the fix-bigtiff-inline-strip-offsets branch from d8bc325 to 633b15f Compare October 21, 2026 01:08
@radarhere
radarhere enabled auto-merge (squash) October 21, 2026 01:09
@radarhere
radarhere merged commit b2107bd into python-pillow:main Oct 21, 2026
50 of 51 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants