From b6dbf10057236eb8bb840fe1f879b496181cf978 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sun, 9 Aug 2026 08:32:25 +0100 Subject: [PATCH] Redraw SIXEL images before or after text depending on which was placed first in the cell. --- image-sixel.c | 24 +++++----- image.c | 33 +++++++++----- input.c | 7 ++- resize.c | 6 ++- screen-redraw.c | 117 +++++++++++++++++++++++++++++------------------- tmux.h | 5 ++- tty-keys.c | 1 + 7 files changed, 118 insertions(+), 75 deletions(-) diff --git a/image-sixel.c b/image-sixel.c index eaa512099..a66d5d1a4 100644 --- a/image-sixel.c +++ b/image-sixel.c @@ -50,6 +50,7 @@ struct sixel_image { u_int *colours; u_int ncolours; u_int used_colours; + u_int p1; u_int p2; u_int dx; @@ -356,7 +357,8 @@ sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end) /* Parse SIXEL data into an indexed image. */ struct sixel_image * -sixel_parse(const char *buf, size_t len, u_int p2, u_int xpixel, u_int ypixel) +sixel_parse(const char *buf, size_t len, u_int p1, u_int p2, u_int xpixel, + u_int ypixel) { struct sixel_image *si; const char *cp = buf, *end = buf + len; @@ -370,6 +372,7 @@ sixel_parse(const char *buf, size_t len, u_int p2, u_int xpixel, u_int ypixel) si = xcalloc (1, sizeof *si); si->xpixel = xpixel; si->ypixel = ypixel; + si->p1 = p1; si->p2 = p2; while (cp != end) { @@ -609,18 +612,13 @@ sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox, new = xcalloc (1, sizeof *si); new->xpixel = xpixel; new->ypixel = ypixel; + new->p1 = si->p1; new->p2 = si->p2; new->set_ra = si->set_ra; - /* subtract offset */ - new->ra_x = si->ra_x > pox ? si->ra_x - pox : 0; - new->ra_y = si->ra_y > poy ? si->ra_y - poy : 0; - /* clamp to size */ - new->ra_x = new->ra_x < psx ? new->ra_x : psx; - new->ra_y = new->ra_y < psy ? new->ra_y : psy; - /* resize */ - new->ra_x = new->ra_x * xpixel / si->xpixel; - new->ra_y = new->ra_y * ypixel / si->ypixel; + /* The raster attributes describe the scaled output rectangle. */ + new->ra_x = tsx; + new->ra_y = tsy; new->used_colours = si->used_colours; for (y = 0; y < tsy; y++) { @@ -753,7 +751,7 @@ sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size) len = 8192; buf = xmalloc(len); - tmplen = xsnprintf(tmp, sizeof tmp, "\033P9;%uq", si->p2); + tmplen = xsnprintf(tmp, sizeof tmp, "\033P%u;%uq", si->p1, si->p2); sixel_print_add(&buf, &len, &used, tmp, tmplen); if (si->set_ra) { @@ -1133,6 +1131,7 @@ sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells_x, si = xcalloc(1, sizeof *si); si->xpixel = xpixel; si->ypixel = ypixel; + si->p1 = 9; si->p2 = 1; si->set_ra = 1; si->ra_x = sx; @@ -1417,7 +1416,8 @@ sixel_draw_rect(struct tty *tty, const struct image_rect *rectangle, tty_cursor(tty, destination_x, destination_y); tty->flags |= TTY_NOBLOCK; tty_putn(tty, data, size, 0); - tty_invalidate(tty); + /* SIXEL moves the cursor, but does not change terminal attributes. */ + tty->cx = tty->cy = UINT_MAX; free(data); } diff --git a/image.c b/image.c index 7e48623a0..09774552a 100644 --- a/image.c +++ b/image.c @@ -570,6 +570,11 @@ image_get_draw_cell(struct tty *tty, const struct grid_cell *gc, GRID_FLAG_SELECTED); return (1); } + if (gc->flags & GRID_FLAG_IMAGE_DAMAGED) { + memcpy(out, gc, sizeof *out); + out->flags &= ~(GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED); + return (0); + } image_get_fallback_cell(tty, im, gc->image_x, gc->image_y, gc, out, style_ctx); return (0); @@ -787,10 +792,11 @@ image_redraw_scroll(struct screen_write_ctx *ctx, __unused u_int lines) image_redraw_all(ctx); } -/* Draw the graphical image marker runs in one visible scene span. */ +/* Draw a span's graphical image markers before or after its text. */ void image_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, - u_int nx, u_int atx, u_int aty, const struct tty_style_ctx *style_ctx) + u_int nx, u_int atx, u_int aty, int before, + const struct tty_style_ctx *style_ctx) { const struct image_backend *backend; struct image_rect rectangle; @@ -802,6 +808,8 @@ image_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, backend = tty->image_backend; if (~backend->flags & IMAGE_BACKEND_GRAPHICAL) return; + if (before && (~backend->flags & IMAGE_BACKEND_TEMPORAL)) + return; for (i = 0; i < nx; i += run) { grid_view_get_cell(s->grid, px + i, py, &gc); @@ -809,10 +817,12 @@ 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; + if (backend->flags & IMAGE_BACKEND_TEMPORAL) { + if ((before && (~gc.flags & GRID_FLAG_IMAGE_DAMAGED)) || + (!before && (gc.flags & GRID_FLAG_IMAGE_DAMAGED))) { + run = 1; + continue; + } } im = image_find(gc.image_id); if (im == NULL) { @@ -821,10 +831,13 @@ 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 || + if (~next.flags & GRID_FLAG_IMAGE) + break; + if ((backend->flags & IMAGE_BACKEND_TEMPORAL) && + ((before && (~next.flags & GRID_FLAG_IMAGE_DAMAGED)) || + (!before && (next.flags & GRID_FLAG_IMAGE_DAMAGED)))) + break; + if (next.image_id != gc.image_id || next.image_y != gc.image_y || next.image_x != gc.image_x + run) break; diff --git a/input.c b/input.c index 2f633df1c..6565fed47 100644 --- a/input.c +++ b/input.c @@ -2638,7 +2638,7 @@ input_dcs_dispatch(struct input_ctx *ictx) #ifdef ENABLE_IMAGES struct window *w; struct sixel_image *si; - int p2; + int p1, p2; #endif if (wp == NULL) @@ -2656,10 +2656,13 @@ input_dcs_dispatch(struct input_ctx *ictx) w = wp->window; if (input_split(ictx) != 0) return (0); + p1 = input_get(ictx, 0, 0, 0); + if (p1 == -1) + p1 = 0; p2 = input_get(ictx, 1, 0, 0); if (p2 == -1) p2 = 0; - si = sixel_parse(buf, len, p2, w->xpixel, w->ypixel); + si = sixel_parse(buf, len, p1, p2, w->xpixel, w->ypixel); if (si != NULL) screen_write_sixelimage(sctx, si, ictx->cell.cell.bg); } diff --git a/resize.c b/resize.c index 67562522d..74ee1a202 100644 --- a/resize.c +++ b/resize.c @@ -386,10 +386,12 @@ recalculate_size(struct window *w, int now) * got a resize scheduled, then use the new size; otherwise the old. */ if (w->flags & WINDOW_RESIZE) { - if (!now && changed && w->new_sx == sx && w->new_sy == sy) + if (!now && changed && w->new_sx == sx && w->new_sy == sy && + w->new_xpixel == xpixel && w->new_ypixel == ypixel) changed = 0; } else { - if (!now && changed && w->sx == sx && w->sy == sy) + if (!now && changed && w->sx == sx && w->sy == sy && + w->xpixel == xpixel && w->ypixel == ypixel) changed = 0; } diff --git a/screen-redraw.c b/screen-redraw.c index e387c3fb0..4c0e62cbf 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -99,6 +99,12 @@ enum redraw_span_type { #define REDRAW_ALL 0x7fffffff #define REDRAW_IS_ALL(flags) ((flags) == REDRAW_ALL) +enum redraw_image_phase { + REDRAW_IMAGES_BEFORE, + REDRAW_TEXT, + REDRAW_IMAGES_AFTER +}; + /* UTF-8 isolate characters. */ #define REDRAW_START_ISOLATE "\342\201\246" #define REDRAW_END_ISOLATE "\342\201\251" @@ -1237,7 +1243,8 @@ redraw_get_scene(struct client *c) /* Draw a pane span. */ static void redraw_draw_pane_span(struct redraw_draw_ctx *dctx, - struct redraw_span *span, u_int x, u_int y, u_int n) + struct redraw_span *span, u_int x, u_int y, u_int n, + enum redraw_image_phase phase) { struct redraw_scene *scene = dctx->scene; struct client *c = scene->c; @@ -1255,10 +1262,14 @@ redraw_draw_pane_span(struct redraw_draw_ctx *dctx, px = span->data.p.px + (x - span->x); py = span->data.p.py; - tty_draw_line(tty, s, px, py, n, x, y, &style_ctx); #ifdef ENABLE_IMAGES - image_draw_line(tty, s, px, py, n, x, y, &style_ctx); + if (phase != REDRAW_TEXT) + image_draw_line(tty, s, px, py, n, x, y, + phase == REDRAW_IMAGES_BEFORE, &style_ctx); + else #endif + if (phase == REDRAW_TEXT) + tty_draw_line(tty, s, px, py, n, x, y, &style_ctx); } /* Get default border style for spans without a pane. */ @@ -1518,7 +1529,7 @@ redraw_draw_menu_span(struct redraw_draw_ctx *dctx, */ static void redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span, - u_int y, u_int clip_x, u_int clip_n) + u_int y, u_int clip_x, u_int clip_n, enum redraw_image_phase phase) { struct redraw_scene *scene = dctx->scene; struct redraw_span_data *data = &span->data; @@ -1539,10 +1550,12 @@ redraw_draw_span(struct redraw_draw_ctx *dctx, struct redraw_span *span, continue; x = rr->px; n = rr->nx; + if (phase != REDRAW_TEXT && type != REDRAW_SPAN_PANE) + continue; switch (span->data.type) { case REDRAW_SPAN_PANE: - redraw_draw_pane_span(dctx, span, x, y, n); + redraw_draw_pane_span(dctx, span, x, y, n, phase); break; case REDRAW_SPAN_BORDER: case REDRAW_SPAN_EMPTY: @@ -1607,27 +1620,29 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp, } #endif - for (y = top; y < bottom; y++) { - line = &scene->lines[y]; - if (dctx->flags & REDRAW_STATUS_TOP) - cy = dctx->status_lines + y; - else - cy = y; - if (flags & REDRAW_PANE) { - spans = &line->spans[REDRAW_SPAN_PANE]; - TAILQ_FOREACH(span, spans, entry) { - if (span->data.p.wp == wp) { - redraw_draw_span(dctx, span, cy, - span->x, span->width); + for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE; + phase <= REDRAW_IMAGES_AFTER; phase++) { + for (y = top; y < bottom; y++) { + line = &scene->lines[y]; + if (dctx->flags & REDRAW_STATUS_TOP) + cy = dctx->status_lines + y; + else + cy = y; + if (flags & REDRAW_PANE) { + spans = &line->spans[REDRAW_SPAN_PANE]; + TAILQ_FOREACH(span, spans, entry) { + if (span->data.p.wp == wp) + redraw_draw_span(dctx, span, cy, span->x, + span->width, phase); } } - } - if (flags & REDRAW_PANE_SCROLLBAR) { - spans = &line->spans[REDRAW_SPAN_SCROLLBAR]; - TAILQ_FOREACH(span, spans, entry) { - if (span->data.sb.wp == wp) { - redraw_draw_span(dctx, span, cy, - span->x, span->width); + if (phase == REDRAW_TEXT && + (flags & REDRAW_PANE_SCROLLBAR)) { + spans = &line->spans[REDRAW_SPAN_SCROLLBAR]; + TAILQ_FOREACH(span, spans, entry) { + if (span->data.sb.wp == wp) + redraw_draw_span(dctx, span, cy, span->x, + span->width, phase); } } } @@ -1644,13 +1659,17 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags) struct redraw_span *span; u_int y, cy, type; - for (y = 0; y < scene->sy; y++) { - line = &scene->lines[y]; - if (dctx->flags & REDRAW_STATUS_TOP) - cy = dctx->status_lines + y; - else - cy = y; - for (type = 0; type < REDRAW_SPAN_TYPES; type++) { + for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE; + phase <= REDRAW_IMAGES_AFTER; phase++) { + for (y = 0; y < scene->sy; y++) { + line = &scene->lines[y]; + if (dctx->flags & REDRAW_STATUS_TOP) + cy = dctx->status_lines + y; + else + cy = y; + for (type = 0; type < REDRAW_SPAN_TYPES; type++) { + if (phase != REDRAW_TEXT && type != REDRAW_SPAN_PANE) + continue; if (!REDRAW_IS_ALL(flags)) { switch (type) { case REDRAW_SPAN_PANE: @@ -1686,9 +1705,9 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags) } } spans = &line->spans[type]; - TAILQ_FOREACH(span, spans, entry) { - redraw_draw_span(dctx, span, cy, span->x, - span->width); + TAILQ_FOREACH(span, spans, entry) + redraw_draw_span(dctx, span, cy, span->x, span->width, + phase); } } } @@ -1709,10 +1728,9 @@ redraw_draw_menu_lines(struct redraw_draw_ctx *dctx) cy = dctx->status_lines + y; else cy = y; - TAILQ_FOREACH(span, &line->spans[REDRAW_SPAN_MENU], entry) { - redraw_draw_span(dctx, span, cy, span->x, - span->width); - } + TAILQ_FOREACH(span, &line->spans[REDRAW_SPAN_MENU], entry) + redraw_draw_span(dctx, span, cy, span->x, span->width, + REDRAW_TEXT); } } @@ -2191,13 +2209,17 @@ redraw_draw_damage_rect(struct redraw_draw_ctx *dctx, u_int x, u_int y, if (sx == 0 || sy == 0) return; - for (yy = y; yy < y + sy; yy++) { - line = &scene->lines[yy]; - if (dctx->flags & REDRAW_STATUS_TOP) - cy = dctx->status_lines + yy; - else - cy = yy; - for (type = 0; type < REDRAW_SPAN_TYPES; type++) { + for (enum redraw_image_phase phase = REDRAW_IMAGES_BEFORE; + phase <= REDRAW_IMAGES_AFTER; phase++) { + for (yy = y; yy < y + sy; yy++) { + line = &scene->lines[yy]; + if (dctx->flags & REDRAW_STATUS_TOP) + cy = dctx->status_lines + yy; + else + cy = yy; + for (type = 0; type < REDRAW_SPAN_TYPES; type++) { + if (phase != REDRAW_TEXT && type != REDRAW_SPAN_PANE) + continue; spans = &line->spans[type]; TAILQ_FOREACH(span, spans, entry) { clip_x = (span->x > x) ? span->x : x; @@ -2212,14 +2234,15 @@ redraw_draw_damage_rect(struct redraw_draw_ctx *dctx, u_int x, u_int y, redraw_damage_grow_span_clip(span, &clip_x, &clip_end); redraw_draw_span(dctx, span, cy, clip_x, - clip_end - clip_x); - if (type == REDRAW_SPAN_PANE) { + clip_end - clip_x, phase); + if (phase == REDRAW_TEXT && type == REDRAW_SPAN_PANE) { redraw_damage_draw_pane_prompt(dctx, span, cy, clip_x, clip_end - clip_x); } } } + } } } diff --git a/tmux.h b/tmux.h index 8e7b5c1db..1c7043d59 100644 --- a/tmux.h +++ b/tmux.h @@ -4279,7 +4279,7 @@ void image_tty_update(struct tty *); void image_tty_geometry_changed(struct tty *); void image_tty_free(struct tty *, int); void image_draw_line(struct tty *, struct screen *, u_int, u_int, - u_int, u_int, u_int, const struct tty_style_ctx *); + u_int, u_int, u_int, int, const struct tty_style_ctx *); void image_get_fallback_cell(struct tty *, struct image *, u_int, u_int, const struct grid_cell *, struct grid_cell *, const struct tty_style_ctx *); @@ -4312,7 +4312,8 @@ void sixel_draw_rect(struct tty *, const struct image_rect *, const struct tty_style_ctx *); void sixel_free_output(struct tty *, int); void sixel_geometry_changed(struct tty *); -struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int); +struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int, + u_int); void sixel_free(struct sixel_image *); void sixel_log(struct sixel_image *); void sixel_size_in_cells(struct sixel_image *, u_int *, u_int *); diff --git a/tty-keys.c b/tty-keys.c index 8462e0d9b..76e9a8fcf 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -729,6 +729,7 @@ tty_keys_winsz(struct tty *tty, const char *buf, size_t len, size_t *size) char_y = (ypixel && tty->sy) ? ypixel / tty->sy : 0; tty_set_size(tty, tty->sx, tty->sy, char_x, char_y); tty_invalidate(tty); + recalculate_sizes(); tty->flags &= ~TTY_WINSIZEQUERY; *size = end + 1;