test(assets): the RLE8 rule is short of the independent decoder, in 703 of 22 620 streams

RED, 3 of 23 on assert about behaviour, 0 on import.

PM's checkpoint on 44ad845 found that four end-of-line escapes and an
end-of-bitmap carry an 8x4 frame with 32 of 32 pixels never decoded, while
Pillow -- the independent decoder this file names -- refuses the same bytes.
The sentence the last round wrote into `assets.py`, that a delta and an
end-of-line both leave pixels every decoder agrees on, is false for the
end-of-line half.

Measured here before anything is fixed, and the class is wider than the one
construction: over every opcode sequence of length 1 to 4 on a 4x3 frame
(22 620 streams), 703 are carried by this package and refused by Pillow, and
1 492 more are carried by both and drawn DIFFERENTLY. PM's recommendation on
its own -- refuse a stream that painted nothing -- leaves 512 and 1 171 of
those, so it would narrow the class a third time rather than close it.

Four new arms in CURSOR_CASES, one per clause the table could not see:
the ROW clause (a stream stopping one row early with that row complete, which
is PM's P8 mutant `height - 1` -> `height - 2`, a mutant that survived 51
tests), and three end-of-line constructions that reach the end of the frame
without closing a row. The sweep is the guard the table cannot be: a
hand-picked table holds the shapes its author imagined, and this defect has
now been closed one shape at a time twice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-19 22:46:13 +02:00
commit 7cf758dab7
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q

View file

@ -28,6 +28,7 @@ from __future__ import annotations
import hashlib
import io
import itertools
import struct
import warnings
from pathlib import Path
@ -612,12 +613,16 @@ def test_a_truncated_rle8_stream_is_refused_with_a_published_code() -> None:
assert whole.media_type == "image/png"
#: Eight RLE8 streams for one 8x4 frame, each naming what its terminator
#: Twelve RLE8 streams for one 8x4 frame, each naming what its terminator
#: leaves behind, and whether an INDEPENDENT decoder will read the file.
#: `reaches` is the property measured below: at end-of-bitmap the cursor
#: stands at or past the end of the last row, so every row was addressed.
#: The values are Pillow's, measured 2026-09-19 on the eight files this
#: module builds -- not a rule read off our own code.
#: stands at or past the end of the last row, so every row was addressed,
#: AND every end-of-line escape closed a row it had started.
#: The values are Pillow's, measured 2026-09-19 on the twelve files this
#: module builds -- not a rule read off our own code. The table is CURATED and
#: the arms are chosen so the two readers give the same verdict; the general
#: relation between them is one-way and is measured by the sweep below, not
#: here.
def _encoded(count: int, index: int) -> bytes:
return bytes((count, index))
@ -657,6 +662,36 @@ CURSOR_CASES: tuple[tuple[str, bytes, bool], ...] = (
True,
),
("the shipped fixture, which uses a delta", RLE8_STREAM, True),
# One arm per CLAUSE of the cursor rule. Until 2026-09-19 the table had
# none for the ROW clause: every short stream above stops on the last row,
# so `y < height - 1` could be off by a row (PM's P8, `height - 2`) and
# survive 51 tests. This arm stops one row early with that row complete,
# which is the only shape that separates the two readings.
(
"the row before the last painted to its end, then end-of-bitmap",
(_encoded(8, 1) + END_OF_LINE) * 2 + _encoded(8, 1) + END_OF_BITMAP,
False,
),
# The cursor can REACH the end of the frame without a pixel being painted,
# and the four arms below are that class. An end-of-line escape at column 0
# does not close a row it started -- it claims a whole row nothing wrote --
# and an independent decoder does not follow it there.
("four end-of-line escapes and not one pixel painted", END_OF_LINE * 4 + END_OF_BITMAP, False),
(
"three rows painted and closed, then a bare end-of-line",
(_encoded(8, 1) + END_OF_LINE) * 3 + END_OF_LINE + END_OF_BITMAP,
False,
),
(
"three end-of-line escapes, then the last row painted",
END_OF_LINE * 3 + _encoded(8, 1) + END_OF_BITMAP,
False,
),
(
"one pixel painted, then four end-of-line escapes",
_encoded(1, 1) + END_OF_LINE * 4 + END_OF_BITMAP,
False,
),
)
@ -671,12 +706,20 @@ def test_a_stream_that_stops_before_the_frame_is_refused() -> None:
stream can say so anywhere.
THE LINE IS THE CURSOR, NOT THE PIXELS, and it is read off an independent
decoder rather than chosen: a delta escape and an end-of-line escape both
leave pixels at index 0 and every decoder agrees on them, because the
stream stated the skip. Pixels the stream never reached have no agreed
value at all. Measured on the eight streams above, Pillow reads the five
whose cursor reaches the end of the frame and refuses the three whose does
not -- including one that is short by a single pixel.
decoder rather than chosen. A DELTA escape states its skip as a distance,
and the pixels it passes over keep index 0 in every decoder; an
END-OF-LINE escape states only "the rest of this row", so an end-of-line
at column 0 claims a row nothing wrote, and the independent decoder does
not follow it there. That is not a refinement of the previous round's
sentence, it is a correction of it: the sentence said every decoder agreed
on an end-of-line skip, and Pillow refuses four of the twelve streams
above on exactly that construction.
Measured on the twelve streams above, Pillow reads the five whose cursor
reaches the end of the frame through painted rows and stated deltas, and
refuses the seven whose does not -- including one short by a single pixel,
one stopping a whole row early, and four that move the cursor with an
end-of-line escape that closed no row.
A PIXEL coverage count would be a different rule and a wrong one: it
refuses the delta the format defines, and 0 of the 25 RLE8 BMPs
@ -715,6 +758,153 @@ def test_the_independent_decoder_draws_the_same_line() -> None:
assert read is reaches, f"{label}: the independent decoder disagrees with the table"
def test_a_delta_may_not_carry_the_cursor_out_of_its_row() -> None:
"""A delta states a distance, and a distance past the row end states nothing.
The format puts the delta's horizontal offset INSIDE the line; there is no
defined wrap. This reader's cursor keeps `x` past the row end and paints
nothing more on that row, while a flat decoder rolls the same offset into
the next row and paints there -- two different pictures from one stream.
THIS IS THE ONE DIRECTION THIS PACKAGE ALLOWS: it refuses a file the
independent decoder reads. Pillow carries this stream (its flat buffer
reaches 32 bytes); we do not, because we cannot say which of the two
pictures is the file's. The reverse direction -- carrying a file the
independent decoder refuses -- is the defect, and the sweep below measures
it at 0.
Cost measured before it was written: over every BMP reachable on this
machine (11 441 files scanned across the four raw standard deliveries and
the K2 reference corpus, 25 BMPs, all RLE8, all from one publisher), 0 of
25 use a delta escape at all, and 0 of 25 use an end-of-line at column 0.
Both clauses cost nothing measured, and the clause that WOULD have cost
something -- refusing a run that overruns its row, which closes the last
disagreement class -- is not here: 15 of those 25 files overrun a row, and
refusing them would drop 15 real figures and move a pinned bundle's bytes.
"""
stream = _encoded(4, 1) + bytes((0, 2, 8, 3)) + END_OF_BITMAP
with pytest.raises(ExtractionError) as caught:
read_image(bmp_rle8(8, 4, PALETTE, stream), name="delta.bmp")
assert caught.value.code == CODE_SAMPLES_INVALID, caught.value.code
# The known-positive on the same shape: a delta that stays inside its row
# is the shipped fixture's own construction and is still carried.
inside = _encoded(4, 1) + bytes((0, 2, 4, 3)) + END_OF_BITMAP
assert read_image(bmp_rle8(8, 4, PALETTE, inside), name="delta.bmp").media_type == "image/png"
def _overruns_a_row(stream: bytes, width: int) -> bool:
"""Does this stream paint a run that runs off the end of its row?
Written here rather than imported: it is the shape the sweep below is
allowed to disagree on, so it has to be stated independently of the reader
it judges.
"""
position, x = 0, 0
while position + 1 < len(stream):
count, value = stream[position], stream[position + 1]
position += 2
if count:
if x + count > width:
return True
x += count
elif value == 0:
x = 0
elif value == 1:
break
elif value == 2:
x += stream[position]
position += 2
else:
if x + value > width:
return True
x += value
position += value + (value & 1)
return False
def test_no_stream_this_reader_carries_is_one_the_independent_decoder_refuses() -> None:
"""The class, swept, rather than the three constructions someone thought of.
Two rounds closed this defect one construction at a time -- a truncated
stream, then an immediate end-of-bitmap -- and each time the class stayed
open one step down, because a hand-picked table can only hold the shapes
its author imagined. This enumerates every opcode sequence of length 1 to 4
over twelve opcodes on a 4x3 frame (22 620 streams: encoded runs that fit
and runs that do not, absolute blocks, end-of-line, five deltas) and asks
one question of each: if THIS reader carries it, does the independent
decoder carry it too, and draw the same picture?
Measured 2026-09-19, before the rule was written: 703 of 22 620 streams
were carried here and refused by Pillow, and 1 492 more were drawn
differently. With the end-of-line and delta clauses: 0 and 32. Every one
of the 32 is a run or absolute block that OVERRUNS its row, which this
reader clips at the row end (as the format says) and Pillow spills into the
next row; refusing those costs 15 of the 25 real files, so the residual is
stated here and not closed. The direction that matters is closed: nothing
this package carries is a file the other decoder will not read.
"""
Image = pytest.importorskip("PIL.Image")
width, height = 4, 3
ops = (
_encoded(1, 1),
_encoded(2, 2),
_encoded(4, 1),
_encoded(5, 3),
END_OF_LINE,
bytes((0, 2, 0, 1)),
bytes((0, 2, 1, 0)),
bytes((0, 2, 2, 1)),
bytes((0, 2, 0, 3)),
bytes((0, 2, 4, 0)),
bytes((0, 3, 1, 2, 3, 0)),
bytes((0, 4, 3, 2, 1, 2)),
)
carried_and_refused: list[bytes] = []
differing: list[bytes] = []
both_carry = both_refuse = 0
streams = 0
for length in (1, 2, 3, 4):
for combo in itertools.product(ops, repeat=length):
stream = b"".join(combo) + END_OF_BITMAP
data = bmp_rle8(width, height, PALETTE, stream)
streams += 1
try:
ours = read_image(data, name="sveip.bmp").data
mine: bytes | None = png_rgb(ours)[1]
except ExtractionError:
mine = None
try:
picture = Image.open(io.BytesIO(data))
picture.load()
theirs: bytes | None = picture.convert("RGB").tobytes()
except Exception:
theirs = None
if mine is not None and theirs is not None:
if mine == theirs:
both_carry += 1
else:
differing.append(stream)
elif mine is not None:
carried_and_refused.append(stream)
elif theirs is None:
both_refuse += 1
assert streams == 22620, streams
# Non-vacuity first: a sweep where nothing is carried proves nothing.
assert both_carry > 1000, both_carry
assert both_refuse > 1000, both_refuse
assert carried_and_refused == [], (
f"{len(carried_and_refused)} of {streams} streams are carried here and refused by the "
f"independent decoder, e.g. {carried_and_refused[0]!r}"
)
unexplained = [s for s in differing if not _overruns_a_row(s, width)]
assert unexplained == [], (
f"{len(unexplained)} of {streams} streams are drawn differently for a reason this file "
f"does not state, e.g. {unexplained[0]!r}"
)
def test_the_uncompressed_path_refuses_the_same_shape() -> None:
"""The twin this rule was aligned to, so the two BMP paths cannot drift."""
short = bmp_24(RGB_ROWS)[:-30]