From efeb4aead06fb5a68df923cc9198a45db3c1cb72 Mon Sep 17 00:00:00 2001 From: Jeff Quast Date: Sun, 12 Jul 2026 18:10:37 -0400 Subject: [PATCH] Fix erroneous scroll when using X/Y offsets in images Problem ------- In update_dest_rect() when both num_cols and num_rows are 0 (auto-computed, ``a=T``), the first block computes num_cols with cell_x_offset, but, then the second block mismatches num_cols, causing it to compute width_px from num_cols and adds cell_x_offset a second time. The returned value is large enough that it often causes scrolling, depending on size and location of the screen. A sample visual program: ```python import zlib, base64, time from functools import partial echo = partial(print, end='', flush=True) echo('\033[H\033[J') base = bytes([0, 128, 0, 255]) * 640 * 576 k = zlib.compress(keyframe, 3) echo(f'\033[8;60H\033_Ga=T,i=1,q=1,f=32,s=640,v=576,o=z,N=1;{base64.b64encode(k).decode()}\033\\') time.sleep(1) delta = bytes([200, 0, 0, 255]) * 32 * 512 d = zlib.compress(delta, 3) echo(f'\033[9;67H\033_Ga=T,i=100,q=1,f=32,s=32,v=512,o=z,N=1,X=9,Y=25;{base64.b64encode(d).decode()}\033\\') time.sleep(1) print() ``` Solution -------- Add 'auto_cols' and 'auto_rows' to remember the original read-only values before exercising their auto-calculated size. 'auto_rows' isn't technically necessary given the logic branching but it describes in code better. Before and After video ---------------------- A video will be attached shortly, here. --- kitty/graphics.c | 9 +++++---- kitty_tests/graphics.py | 7 +++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/kitty/graphics.c b/kitty/graphics.c index 43d23acf5..4db4f8174 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -830,8 +830,9 @@ update_src_rect(ImageRef *ref, Image *img) { static void update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows, CellPixelSize cell) { uint32_t t; - if (num_cols == 0) { - if (num_rows == 0) { + const bool auto_cols = num_cols == 0, auto_rows = num_rows == 0; + if (auto_cols) { + if (auto_rows) { t = (uint32_t)(ref->src_width + ref->cell_x_offset); num_cols = t / cell.width; if (t > num_cols * cell.width) num_cols += 1; @@ -841,8 +842,8 @@ update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows, CellPixelS num_cols = (uint32_t)ceil(width_px / cell.width); } } - if (num_rows == 0) { - if (num_cols == 0) { + if (auto_rows) { + if (auto_cols) { t = (uint32_t)(ref->src_height + ref->cell_y_offset); num_rows = t / cell.height; if (t > num_rows * cell.height) num_rows += 1; diff --git a/kitty_tests/graphics.py b/kitty_tests/graphics.py index ec6a0444d..24cbfb5b7 100644 --- a/kitty_tests/graphics.py +++ b/kitty_tests/graphics.py @@ -647,6 +647,13 @@ class TestGraphics(BaseTest): self.ae((s.cursor.x, s.cursor.y), (3, 2)) rect_eq(layers(s)[0]['dest_rect'], -1, 1, -1 + 3 * dx, 1 - 3*dy) + def test_graphics_put_with_pixel_offsets(self): + cw, ch = 10, 20 + # Image 10x20 placed with 5px X and Y pixel offsets + s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch) + self.ae(put_image(s, 10, 20, cell_x_off=5, cell_y_off=5)[1], 'OK') + self.ae((s.cursor.x, s.cursor.y), (2, 1)) + def test_image_layer_grouping(self): cw, ch = 10, 20 s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch)