Skip to content

Fix loading transparent XPM images - #9848

Open
nyxst4ck wants to merge 1 commit into
python-pillow:mainfrom
nyxst4ck:xpm-transparency
Open

Fix loading transparent XPM images#9848
nyxst4ck wants to merge 1 commit into
python-pillow:mainfrom
nyxst4ck:xpm-transparency

Conversation

@nyxst4ck

@nyxst4ck nyxst4ck commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Any XPM that declares a transparent colour via a c None entry — the standard way GIMP and ImageMagick store transparency — fails to load since 11.3.0:

ValueError: tuple.index(x): x not in tuple

Root cause

#8967's palette refactor (7b459a8) replaced the fixed 256-entry black-filled palette list with a dict of only the declared colours, but the c None branch stores the character key in info["transparency"] without adding a palette entry. Decoding then calls palette_keys.index(key) for the transparent character and raises. The >256-colour RGB path (added in 395bd6b) hits the same missing entry.

Before 11.3.0 this worked, if partly by accident: the transparent character's ordinal was a valid index into the black-filled palette, so the image loaded and info["transparency"] was a usable integer index. Verified: 11.2.1 loads (transparency=32), 11.3.0 / 12.2.0 / current main all raise.

Fix

Give the transparent colour a real palette entry of black — matching pre-11.3.0 rendering — and store a usable value in info["transparency"]: the palette index in P mode, the colour tuple in RGB mode.

One design point I'd like your view on: for the RGB path, info["transparency"] = (0, 0, 0) follows the usual colour-key convention but would also key out genuine black pixels in a >256-colour transparent XPM. If you'd rather only fix the crash there and leave transparency unreported in RGB mode, I'm happy to drop that line and the corresponding assertion.

Tests

Two tests added to Tests/test_file_xpm.py, building XPM data in memory with BytesIO (same style as test_truncated_header) — no new binary fixtures:

  • test_transparency: P mode; asserts the palette index in info["transparency"], and that convert("RGBA") yields (0, 0, 0, 0) for the transparent pixel and the correct opaque colours. Transparency also survives a PNG save/reload round-trip.
  • test_transparency_rgb: 301 colours → RGB mode; asserts mode, transparency tuple and pixel values.

Red on main (both fail with the error above), green with the fix (10 passed). ruff and black clean on both files; mypy src/PIL/XpmImagePlugin.py clean.

The 11.3.0 palette refactor stopped adding an entry for 'c None'
colours, so palette.index(key) raised ValueError and any XPM with a
transparent colour failed to load. Give the transparent colour a real
palette entry (black, matching pre-11.3.0 rendering) and store its
palette index (P mode) or colour tuple (RGB mode) in
info['transparency'].
@radarhere radarhere changed the title Fixed loading transparent XPM images Fix loading transparent XPM images Aug 5, 2026
@radarhere

Copy link
Copy Markdown
Member

#8967 wasn't merged. I believe you meant #8874.

for the RGB path, info["transparency"] = (0, 0, 0) follows the usual colour-key convention but would also key out genuine black pixels in a >256-colour transparent XPM

If the palette in a P mode image can't accommodate a new entry purely for transparency, then I'd be inclined to load the image as PA.

Just my personal feeling, I don't love RGB images with transparency. For some reason it seems unintuitive to me. I'd rather load an RGB image with transparency as an RGBA image.

Any XPM that declares a transparent colour via a c None entry

For reference, this is mentioned in the specification.

None can be given as a colorname to mean “transparent”

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants