PSD: layers with a "real user supplied layer mask" (channel -3) stored as Raw fail to load ("unsupported compression")

Chiusa
#5,659 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 2 giorni

@linsen458-spec ci sta già lavorando.

Dal 5/11/2026.

  • #5666 di @linsen458-spec — integrata

Valutazione

Difficoltà
3/5
Tempo stimato
1-2 giorni
Idoneità per principianti
72/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
cpp
Ambito
backend

Direzione di ricerca

Start with src/psd.imageio/psdinput.cpp, especially PSDInput::load_layer_channel() and its handling of channel ID -3 and declared channel lengths. Use the attached repro_raw.psd and repro_rle.psd files with oiiotool --info to compare behavior. Done means the Raw file opens without misreading the next channel, while existing mask handling continues to work.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Environment

  • OpenImageIO 3.1.7.0 (as bundled with Blender 5.0 / 5.1 / 5.2.2), macOS
  • Also reproduced with the PyPI wheels 3.1.7.0, 3.1.19.0 and 3.2.2.0 (Linux)

Description

A PSD fails to open when all of the following are true:

  • A layer has both a layer mask (channel ID -2) and a "real user supplied
    layer mask" (channel ID -3).
  • The -3 channel is stored uncompressed (compression = 0).
  • The -3 mask rectangle differs in size from the layer's bounds.
  • At least one more layer follows it.

The file is valid per the PSD spec (all channel lengths in the layer records
are correct). Photoshop itself writes this structure, e.g. for a shape layer
(vector mask) that also has a pixel layer mask.

Steps to reproduce

oiiotool repro_raw.psd --info

Expected

The file opens, like repro_rle.psd.

Actual

[Layer Channel] unsupported compression 65535
failed to open "repro_raw.psd": failed load_layers

With real Photoshop files, the reported compression value varies from file to
file (e.g. 41616).

Attachments

repro_psd.zip contains two minimal files generated with Python
(64×64, RGB, 8-bit, 2 layers, no metadata):

  • repro_raw.psd: the -3 channel is Raw → fails
  • repro_rle.psd: identical, except the -3 channel is RLE → loads

Layout of "Layer 1" in repro_raw.psd:

rectangle (t, l, b, r) size declared channel length
layer bounds 8, 8, 40, 40 32×32 1026 per color/alpha channel
-2 (layer mask) 12, 12, 28, 36 24×16 386 = 2 + 384
-3 (real user mask) 16, 20, 48, 30 10×32 322 = 2 + 320

Analysis

In PSDInput::load_layer_channel() (v3.1.7.0 L1624-1653; same logic in
main around L1780 / L1814), only ChannelID_LayerMask (-2) uses its own
rectangle (mask_data). Channel -3 falls back to the layer bounds. The real
user mask rectangle exists in the layer mask data, but it is skipped
(// skip "real" fields).

For Compression_Raw, the declared channel length is then overwritten with
row_length * height, and the stream skips that many bytes:

https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/v3.1.7.0/src/psd.imageio/psdinput.cpp#L1624-L1653

For the attached file, the reader skips 32 × 32 = 1024 bytes instead of 320,
which overshoots by 704 bytes. It then reads two bytes of unrelated pixel data
(0xFFFF) as the compression of the next channel.

RLE works in 3.1.x because the skip there is based on the declared
data_length. With 3.2.2.0, the RLE case also fails when the -3 height
differs from the layer height
(RLE row lengths (...) exceed available data). In that case, the row-count
table is read with the wrong number of rows, and the new check from #5591
rejects it. This does not happen with the attached RLE file, because its -3
height equals the layer height.

I also confirmed that a variant whose -3 rectangle equals the layer bounds
loads fine with Raw. This is the same class of bug that #1714 fixed for
channel -2.

Possible fix (untested)

Masks are never exposed as image channels, so -3 could be skipped using the
declared length:

+    // The real user mask (-3) has its own rectangle (the "real" fields of the
+    // layer mask data), which is not parsed. Its pixels are never exposed,
+    // so skip it using the length declared in the layer record.
+    if (channel_info.channel_id == ChannelID_UserMask)
+        return ioseek(start_pos + channel_info.data_length);

Alternatives:

  • Parse the real user mask rectangle and use it for -3.
  • In general, never let the Raw path override the declared data_length.

repro_psd.zip

Lingua principale
C++
Stelle
2.4k
Fork
718
Merge medio
2g 18h
PR unite (30g)
85

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di AcademySoftwareFoundation/OpenImageIO

Tutte le issue di AcademySoftwareFoundation/OpenImageIO

Issue simili

Altre issue su C++

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.