From f1d7f7766d34ef2c182ba2d31b38f8234be2e4d0 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sat, 8 Aug 2026 22:35:10 +0100 Subject: [PATCH] The implementation now distinguishes a newly placed image cell from one later overwritten by text. On a SIXEL terminal, damaged cells are omitted from image output; on Kitty they remain ordinary positional placements. --- grid.c | 30 ++++++++++++++++++++++++------ image.c | 14 ++++++++++++-- screen-write.c | 17 +++++++++++++++++ tmux.h | 2 ++ 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/grid.c b/grid.c index 62dc67a1e..74aa5ed63 100644 --- a/grid.c +++ b/grid.c @@ -778,6 +778,10 @@ grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc, struct grid_line *gl; struct grid_cell_entry *gce; struct grid_extd_entry *gee; + const struct grid_cell *new_gc; +#ifdef ENABLE_IMAGES + struct grid_cell old_gc, image_gc; +#endif u_int i; if (grid_check_y(gd, __func__, py) != 0) @@ -791,11 +795,25 @@ grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc, for (i = 0; i < slen; i++) { gce = &gl->celldata[px + i]; + new_gc = gc; #ifdef ENABLE_IMAGES { u_int old_id = grid_entry_image(gl, gce); - u_int new_id = (gc->flags & GRID_FLAG_IMAGE) ? - gc->image_id : 0; + u_int new_id; + + if (old_id != 0 && (~gc->flags & GRID_FLAG_IMAGE)) { + grid_get_cell1(gl, px + i, &old_gc); + memcpy(&image_gc, gc, sizeof image_gc); + image_gc.flags |= GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED; + image_gc.image_id = old_gc.image_id; + image_gc.image_x = old_gc.image_x; + image_gc.image_y = old_gc.image_y; + new_gc = &image_gc; + } + if (new_gc->flags & GRID_FLAG_IMAGE) + new_id = new_gc->image_id; + else + new_id = 0; if (old_id != new_id) { if (old_id != 0) @@ -805,11 +823,11 @@ grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc, } } #endif - if (grid_need_extended_cell(gce, gc)) { - gee = grid_extended_cell(gl, gce, gc); + if (grid_need_extended_cell(gce, new_gc)) { + gee = grid_extended_cell(gl, gce, new_gc); gee->data = utf8_build_one(s[i]); } else - grid_store_cell(gce, gc, s[i]); + grid_store_cell(gce, new_gc, s[i]); } } @@ -1322,7 +1340,7 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx, #ifdef ENABLE_IMAGES if (gc.flags & GRID_FLAG_IMAGE) { utf8_set(&gc.data, ' '); - gc.flags &= ~GRID_FLAG_IMAGE; + gc.flags &= ~(GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED); gc.image_id = gc.image_x = gc.image_y = 0; } #endif diff --git a/image.c b/image.c index f05c93e02..aff097fc8 100644 --- a/image.c +++ b/image.c @@ -94,7 +94,8 @@ static const struct image_backend image_backend_ascii = { "ascii", IMAGE_BACKEND_SCROLLS, NULL, NULL, NULL }; static const struct image_backend image_backend_sixel = { - "sixel", IMAGE_BACKEND_GRAPHICAL, sixel_draw_rect, + "sixel", IMAGE_BACKEND_GRAPHICAL|IMAGE_BACKEND_TEMPORAL, + sixel_draw_rect, sixel_free_output, sixel_geometry_changed }; @@ -479,7 +480,8 @@ image_get_draw_cell(struct tty *tty, const struct grid_cell *gc, if (image_backend_flags(tty) & IMAGE_BACKEND_GRAPHICAL) { memcpy(out, gc, sizeof *out); - out->flags &= ~(GRID_FLAG_IMAGE|GRID_FLAG_SELECTED); + out->flags &= ~(GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED| + GRID_FLAG_SELECTED); return (1); } image_get_text_cell(tty, im, gc->image_x, gc->image_y, gc, out, @@ -492,6 +494,7 @@ void image_set_cell(struct grid_cell *gc, struct image *im, u_int x, u_int y) { /* Keep the cell contents as the underlay for transparent pixels. */ + gc->flags &= ~GRID_FLAG_IMAGE_DAMAGED; gc->flags |= GRID_FLAG_IMAGE; gc->image_id = im->id; gc->image_x = x; @@ -624,6 +627,11 @@ image_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, run = 1; continue; } + if ((backend->flags & IMAGE_BACKEND_TEMPORAL) && + (gc.flags & GRID_FLAG_IMAGE_DAMAGED)) { + run = 1; + continue; + } im = image_find(gc.image_id); if (im == NULL) { run = 1; @@ -632,6 +640,8 @@ image_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, for (run = 1; i + run < nx; run++) { grid_view_get_cell(s->grid, px + i + run, py, &next); if (~next.flags & GRID_FLAG_IMAGE || + ((backend->flags & IMAGE_BACKEND_TEMPORAL) && + (next.flags & GRID_FLAG_IMAGE_DAMAGED)) || next.image_id != gc.image_id || next.image_y != gc.image_y || next.image_x != gc.image_x + run) diff --git a/screen-write.c b/screen-write.c index ea2d1fb8f..aa0714ccf 100644 --- a/screen-write.c +++ b/screen-write.c @@ -2607,6 +2607,9 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) struct grid_line *gl; struct grid_cell_entry *gce; struct grid_cell tmp_gc, now_gc; +#ifdef ENABLE_IMAGES + struct grid_cell image_gc; +#endif struct tty_ctx ttyctx; u_int sx = screen_size_x(s), sy = screen_size_y(s); u_int width = ud->width, xx, not_wrap, i, n, vis; @@ -2651,6 +2654,20 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc) return; screen_write_initctx(ctx, &ttyctx, 0, 0); +#ifdef ENABLE_IMAGES + /* Update the text underlay without removing an image placement. */ + grid_view_get_cell(gd, s->cx, s->cy, &now_gc); + if ((now_gc.flags & GRID_FLAG_IMAGE) && + (~gc->flags & GRID_FLAG_IMAGE)) { + image_redraw_area(ctx, s->cx, s->cy, width, 1); + memcpy(&image_gc, gc, sizeof image_gc); + image_gc.flags |= GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED; + image_gc.image_id = now_gc.image_id; + image_gc.image_x = now_gc.image_x; + image_gc.image_y = now_gc.image_y; + gc = &image_gc; + } +#endif /* Handle overwriting of UTF-8 characters. */ gl = grid_get_line(s->grid, s->grid->hsize + s->cy); if (gl->flags & GRID_LINE_EXTENDED) { diff --git a/tmux.h b/tmux.h index ce1c8b86f..f19fe5df4 100644 --- a/tmux.h +++ b/tmux.h @@ -809,6 +809,7 @@ struct colour_palette { #define GRID_FLAG_CLEARED 0x40 #define GRID_FLAG_TAB 0x80 #define GRID_FLAG_IMAGE 0x100 +#define GRID_FLAG_IMAGE_DAMAGED 0x200 /* Grid line flags. */ #define GRID_LINE_WRAPPED 0x1 @@ -4210,6 +4211,7 @@ char *regsub(const char *, const char *, const char *, int); /* image.c */ #define IMAGE_BACKEND_GRAPHICAL 0x1 #define IMAGE_BACKEND_SCROLLS 0x2 +#define IMAGE_BACKEND_TEMPORAL 0x4 struct image *image_create(u_int, u_int, u_int, u_int, u_int, u_int, u_char *);