Skip to content

Fix PSD layer tiles reading from the wrong file offset - #10089

Merged
radarhere merged 4 commits into
python-pillow:mainfrom
Arthur031221:fix/psd-layer-tile-offset
Oct 2, 2026
Merged

radarhere merged 4 commits into
python-pillow:mainfrom
Arthur031221:fix/psd-layer-tile-offset

Conversation

@Arthur031221

Copy link
Copy Markdown
Contributor

Fixes # .

Changes proposed in this pull request:

  • Anyone who opens a multi-layer PSD file and calls seek() to read an
    individual layer gets back the wrong pixel data. PsdImageFile.layers
    parses each layer's channel offsets from a BytesIO copy of the layer
    info block, so the offsets it records start at 0, relative to that copy.
    seek() then loads tiles from the original file object using those
    same numbers as absolute file positions, so the decoder reads whatever
    bytes happen to sit near the start of the real file (the header and
    image resources) instead of the layer's actual pixel data.
  • Shift each tile's offset by self._layers_position, the real file
    position the copy was read from, so the bytes decoded for a layer are
    the bytes that belong to it.
  • Added a regression test that seeks to a layer known to be pixel-identical
    to the flattened image loaded at open time, and checks the two match.
    Before this fix the loaded layer differs from the flattened image on
    every pixel; on main, this is Tests/images/hopper.psd's "Background"
    layer, reached by seeking away and back to frame 1 so that seek()
    actually re-reads the layer's tile instead of reusing the tile already
    set up when the file was opened.

Verified locally: python3 -m pytest Tests/test_file_psd.py passes (26
tests). Reverting only src/PIL/PsdImagePlugin.py to main and rerunning
the new test alone fails with "got different content"; restoring the fix
makes it pass again. Full suite on main before this change: 4951 passed,
576 skipped, 3 xfailed. After this change: 4952 passed (the one new test),
576 skipped, 3 xfailed; no other test changed status.

Arthur031221 and others added 2 commits October 1, 2026 07:47
PsdImageFile.layers() parses each layer's channel data from a BytesIO
copy of the layer info block, so the tile offsets it builds are
positions within that copy, starting at 0. seek() then loads those
tiles from the original file object, so the bytes actually decoded
come from wherever that small offset lands in the real file, not from
the layer's pixel data.

Add self._layers_position, the real file offset the copy was read
from, back onto each tile offset so seek() reads the correct bytes.
@radarhere radarhere added the 🤖-assisted AI-assisted label Oct 1, 2026
@radarhere

radarhere commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

I've created https://github.com/Arthur031221/Pillow/pull/1 with a suggestion to simplify this code.

Applies radarhere's suggestion: read the layer info from the file
directly so the tile offsets are already absolute, and use the test
form from the same suggestion.
@Arthur031221

Copy link
Copy Markdown
Contributor Author

Thanks, applied your suggestion in d8511d1. I ran Tests/test_file_psd.py on that tree: 26 passed.

Pass original file pointer to _layerinfo()
@radarhere
radarhere merged commit d401c61 into python-pillow:main Oct 2, 2026
56 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🤖-assisted AI-assisted

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants