diff options
| author | user <user@clank> | 2026-09-07 16:21:56 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-07 16:21:56 +0200 |
| commit | 83ca4072aa1934be3456638d3b2597876aa7d2e2 (patch) | |
| tree | 7ce023a85d263690c9da21c4e8476dd5fd43a3d6 /tests | |
| parent | graphics: a retransmit invalidates every placement of the image (diff) | |
| download | slosh-83ca4072aa1934be3456638d3b2597876aa7d2e2.tar.gz slosh-83ca4072aa1934be3456638d3b2597876aa7d2e2.tar.xz slosh-83ca4072aa1934be3456638d3b2597876aa7d2e2.zip | |
graphics: no cell arithmetic at the far edge of an uncut image
A smooth bounce shimmered: anything placed with sub-cell X=/Y= offsets
had its source rectangle shaved by a different sliver every frame, so
the picture's edges breathed as it moved. dvd.py made it obvious --
the logo's crop wobbled between w=124 and w=130 around a true 142.
The pixels-per-cell a placement reports are its own pixels over its
own grid, floored, and the grid includes the cell the sub-cell offset
spills into, so the figure runs a shade small -- 142px over 19 cells
says 7 when the terminal's cell is 8. That was harmless while the
number was only used proportionally at real cut edges. The pieces
rewrite ran it over every placement: axis_crop() clamped even an
uncut image's source to cols * cell_px - offset, and as the offset
moved the clamp moved with it.
The far edge of a piece that reaches the placement's last cell is
where the source runs out; only an interior cut needs the cell
arithmetic. With that, an uncut placement's source rect passes
through untouched, which is what the old emitter always sent.
Two companions in the same sum. A scaled piece so narrow its share
of the source rounds to nothing now keeps the pixel it is inside,
because w=0 on the wire means "the whole image", which is worse than
wrong. And a cut piece whose source is genuinely empty is cells the
image never reaches, so it is not placed at all -- the uncut
placement stays exempt, since the pane-edge crop has always used 0
for "the rest of it".
The headless check places with X=5: the whole source rect must
survive the offset, where the clamped code sent w=0.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_graphics.py | 20 |
1 files changed, 20 insertions, 0 deletions
diff --git a/tests/test_graphics.py b/tests/test_graphics.py index 7430224..9d7b9a0 100644 --- a/tests/test_graphics.py +++ b/tests/test_graphics.py @@ -309,6 +309,25 @@ def test_a_placement_says_it_sits_above_the_text(): ) +def test_a_sub_cell_offset_does_not_shave_the_source_rect(): + """The pixels-per-cell a placement reports are its own pixels over its own + grid, floored -- and the grid includes the cell the sub-cell offset spills + into, so the figure runs a shade small. Clamping an uncut placement's + source rectangle by it shaved the crop by up to a cell, and by a different + amount each frame for anything moving sub-cell (a bouncing logo placed + with X=/Y=), so smooth motion shimmered. The far edge of an uncut image + is where its source runs out; no cell arithmetic has any business there.""" + with Session(transmits_then_places("p,i=7,p=1,q=2,X=5"), cols=44, rows=10) as s: + s.settle(200) + raw = s.api("graphics", format="bytes")["bytes"] + place = [c for c in raw.split("\x1b") if c.startswith("_Ga=p")] + check( + "the whole source rect survives the offset", + place and "w=4,h=2" in place[0], + str(place), + ) + + def test_a_placement_with_no_offset_emits_none(): with Session(transmits_then_places("p,i=7,p=1,q=2,c=6,r=2"), cols=44, rows=10) as s: s.settle(200) @@ -758,6 +777,7 @@ if __name__ == "__main__": test_a_screen_clear_still_removes_what_is_on_screen() test_sub_cell_offsets_survive_to_the_client() test_a_placement_says_it_sits_above_the_text() + test_a_sub_cell_offset_does_not_shave_the_source_rect() test_a_placement_with_no_offset_emits_none() test_a_natural_image_is_never_rescaled_as_it_moves() test_a_scaled_image_keeps_the_cell_count_it_asked_for() |
