Sun raster files with RLE encoding and odd-width rows decode shifted
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 72/100
Research direction
Start with the Image.open() reproduction script and locate the sun_rle decoder that handles type 2 Sun raster data. Trace how the stored row stride is calculated and compare the RLE path with the raw path, which already receives the padded stride. The work is done when odd-width RLE images decode each row without including padding bytes, with a regression test covering the reported 3x2 grayscale case.
Written by the indexing model from the issue text.
Description
What did you do?
I opened a run-length-encoded (type 2) Sun raster image whose rows hold an odd number of bytes (an 8-bit grayscale image 3 pixels wide) using Image.open() on an in-memory file created by the self-contained script below.
In this format every stored row is padded to an even number of bytes, and the RLE stream encodes the padding bytes too (SUN_RAS.TXT: "Regardless of width, the stored scanlines are rounded up to multiples of 16 bits", "A run is not terminated at the end of a scan line").
What did you expect to happen?
row 0: 16 32 48
row 1: 64 80 96
What actually happened?
The padding byte at the end of row 0 was decoded as the first pixel of row 1, and every following row is shifted one more byte, without any error:
row 0: 16 32 48
row 1: 0 64 80
Any image whose pixels take an odd number of bytes per row is affected (8-bit and 24-bit images of odd width; 1-bit images 1–8, 17–24, … pixels wide; 4-bit images 1–2, 5–6, … pixels wide). Images with even-length rows decode correctly as well as uncompressed (type 1) Sun raster files.
What are your OS, Python and Pillow versions?
- OS: Ubuntu 24.04, x86_64
- Python: 3.12.3
- Pillow: 13.0.0; also reproduced with 12.3.0 and current main (13.1.0.dev0, 1c5bdeb2c)
--------------------------------------------------------------------
Pillow 13.0.0
Python 3.12.3 (main, Aug 31 2026, 10:18:26) [GCC 13.3.0]
--------------------------------------------------------------------
--- PIL CORE support ok, compiled for 13.0.0
*** TKINTER support not installed
--- FREETYPE2 support ok, loaded 2.14.3
--- LITTLECMS2 support ok, loaded 2.19
--- WEBP support ok, loaded 1.6.0
--- AVIF support ok, loaded 1.4.2
--- JPEG support ok, compiled for libjpeg-turbo 3.2.0
--- OPENJPEG (JPEG2000) support ok, loaded 2.5.4
--- ZLIB (PNG/ZIP) support ok, loaded 1.3, compiled for zlib-ng 2.3.3
--- LIBTIFF support ok, loaded 4.7.2
--- RAQM (Bidirectional Text) support ok, loaded 0.11.0, fribidi 1.0.13, harfbuzz 14.5.0
*** LIBIMAGEQUANT (Quantization method) support not installed
--- XCB (X protocol) support ok
--------------------------------------------------------------------
import io
import struct
from PIL import Image
# 8-bit grayscale, 3x2 pixels, type 2 (run-length encoded).
# Each stored row is 3 pixel bytes + 1 padding byte.
header = struct.pack(">8I", 0x59A66A95, 3, 2, 8, 8, 2, 0, 0)
data = bytes([0x10, 0x20, 0x30, 0x00, # row 0: pixels 16 32 48, padding
0x40, 0x50, 0x60, 0x00]) # row 1: pixels 64 80 96, padding
with Image.open(io.BytesIO(header + data)) as im:
rows = [[im.getpixel((x, y)) for x in range(3)] for y in range(2)]
print("decoded :", rows)
print("expected:", [[0x10, 0x20, 0x30], [0x40, 0x50, 0x60]])
Output:
decoded : [[16, 32, 48], [0, 64, 80]]
expected: [[16, 32, 48], [64, 80, 96]]
Passing the padded stride to the sun_rle decoder (the same stride the raw path already computes) fixes it in my testing — happy to send a PR.
Found by genproof.com Formal Verification Agent. Reviewed and approved by a human.
- Dominant language
- Python
- Stars
- 13.9k
- Forks
- 2.6k
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 176
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from python-pillow/Pillow
-
Warn that pre-applying arabic_reshaper + python-bidi corrupts text on a Raqm buildPossibly taken @Syamjith-NK claimed this 63 days ago. OpenDocumentation Font Rendering PNG Third Party Framework
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
python-pillow/Pillow#9907 · 2 comments ·
Maintainers usually reply within 1 day
-
Image.putpixel incorrectly labels supported arguments in type annotationsPossibly taken @radarhere claimed this 191 days ago. OpenPNG
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
python-pillow/Pillow#9584 · 1 comment ·
Maintainers usually reply within 1 day
-
`ImageDraw.arc()` segfaults on a tuple subclass whose `__len__` is too largePossibly taken @Parsiffall1 claimed this 2 days ago. Open
Difficulty 4/5 1-2 days Newbie friendliness 58/100
python-pillow/Pillow#10358 · 2 comments ·
Maintainers usually reply within 1 day
-
JPEG saved with keep_rgb=True and dpi reopens with incorrect colorsPossibly taken @Muguett-DBY claimed this 3 days ago. Open🤖-assisted JPEG
Difficulty 4/5 1-2 days Newbie friendliness 25/100
python-pillow/Pillow#10344 ·
Maintainers usually reply within 1 day
-
16-bit PNG tRNS key: convert("RGBA") misses the keyed pixel and makes another transparentPossibly taken @Zhuyuyangyy claimed this today. OpenConversion
Difficulty 3/5 1-2 days Newbie friendliness 58/100
python-pillow/Pillow#10330 · 4 comments ·
Maintainers usually reply within 1 day
All issues in python-pillow/Pillow
Similar issues
-
_chunk_scan_fwd launches its Triton kernel without a device guard: NaN when a model is split across GPUsPossibly taken @toddwbucy claimed this today. Open
Difficulty 1/5 Under an hour Newbie friendliness 85/100
state-spaces/mamba#1072 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 85/100
loopj/aiovantage#414 ·
Maintainers usually reply within 1 day
-
power_mismatch_deline docstring: the rear-to-total RMAD conversion has an extra bifaciality factorOpen
Difficulty 1/5 Under an hour Newbie friendliness 85/100
pvlib/pvlib-python#2929 ·
Maintainers usually reply within 1 day
-
data good first issue launch-day unverified
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
BradGroux/wow-forever-mission-control#8 ·
Maintainers usually reply within 1 day
-
check bilibili falsely reports read/subtitle as broken: live_test fixture BV1GJ411x7h7 is gone (404)Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Panniantong/Agent-Reach#889 ·
Maintainers usually reply within 2 days