From 5d618ec02a4b81d6c418132e97c45bf02979b3ed Mon Sep 17 00:00:00 2001 From: chuenchen309 <48723787+chuenchen309@users.noreply.github.com> Date: Sun, 19 Jul 2026 09:45:17 +0800 Subject: [PATCH] Derive PCX plane count from the image, not the line width When decoding a multi-plane (e.g. RGB) PCX scanline, the decoder recovered the per-plane stride by guessing the band count as state->bytes / xsize. state->bytes is planes * stride, so when the padded stride differs from the width (an odd-width line padded to an even stride) that quotient is wrong: for a width-3 RGB line it gives 4 bands / stride 3, the plane-compaction step is skipped, and the R/G/B planes stay interleaved. The pixels decode to the wrong colours with no error raised. Use the known band count (im->bands) to split the line instead. Added an RGB round-trip regression at a width that triggers the padding. Co-Authored-By: Claude Opus 4.8 (1M context) --- Tests/test_file_pcx.py | 8 ++++++++ src/libImaging/PcxDecode.c | 10 ++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py index 76fd09dac9b..4cba9ac857b 100644 --- a/Tests/test_file_pcx.py +++ b/Tests/test_file_pcx.py @@ -118,6 +118,14 @@ def test_1px_width(tmp_path: Path) -> None: _roundtrip(tmp_path, im) +def test_rgb_odd_width(tmp_path: Path) -> None: + # An RGB (multi-plane) width whose even-padded stride differs from the + # width must still separate the colour planes correctly. Distinct channel + # values expose a misaligned split. + im = Image.new("RGB", (3, 3), (0x11, 0x22, 0x33)) + _roundtrip(tmp_path, im) + + def test_large_count(tmp_path: Path) -> None: im = Image.new("L", (256, 1)) px = im.load() diff --git a/src/libImaging/PcxDecode.c b/src/libImaging/PcxDecode.c index a65952fb1da..f29feaa701c 100644 --- a/src/libImaging/PcxDecode.c +++ b/src/libImaging/PcxDecode.c @@ -69,10 +69,12 @@ ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt stride = state->bytes / state->bits; } else { xsize = state->xsize; - bands = state->bytes / state->xsize; - if (bands != 0) { - stride = state->bytes / bands; - } + // state->bytes is planes * stride; derive the stride from the + // known band count rather than guessing it from state->xsize, + // which picks the wrong split when xsize != stride (e.g. an + // odd-width RGB line padded to an even stride). + bands = im->bands; + stride = state->bytes / bands; } if (stride > xsize) { int i;