From d68e56f130ba44d13ae5f1f353526c7ce604099f Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 14 Oct 2026 11:04:00 +0100 Subject: [PATCH] Some of SIXEL output path. --- image-fallback.c | 11 +++- image-grid.c | 106 +++++++++++++++++++++++++++++++++++++- image-sixel.c | 129 +++++++++++++++++++++++++++++++++++++++++++++++ tmux-image.h | 37 +++++++++++++- tmux.h | 7 ++- tty-keys.c | 6 ++- tty.c | 20 +++++++- 7 files changed, 307 insertions(+), 9 deletions(-) diff --git a/image-fallback.c b/image-fallback.c index 1f0443d20..f974049c6 100644 --- a/image-fallback.c +++ b/image-fallback.c @@ -34,7 +34,14 @@ static const struct utf8_data fallback_half_lower = { "\342\226\204", 3, 3, 1 }; -/* Return the average colour and brightness of an area. */ +/* Fallback output is available on every terminal. */ +int +fallback_select(__unused struct tty *tty) +{ + return (1); +} + +/* Get the average colour and brightness of an area. */ static int fallback_sample(struct image *im, uint64_t px, uint64_t py, uint64_t pw, uint64_t ph, u_char *brightness) @@ -142,7 +149,7 @@ fallback_get_cell(struct tty *tty, struct image *im, u_int x, u_int y, out->attr |= GRID_ATTR_BRIGHT; } -/* Return a text cell for an image grid cell, if there is one. */ +/* Get a text cell for an image grid cell, if there is one. */ const struct grid_cell * fallback_map_cell(struct tty *tty, struct grid *gd, u_int x, u_int y, const struct grid_cell *gc, struct grid_cell *out, int *empty) diff --git a/image-grid.c b/image-grid.c index 72a9e3272..51350b8e5 100644 --- a/image-grid.c +++ b/image-grid.c @@ -83,6 +83,105 @@ struct image_recover_row { u_int source_y; }; +/* A backend to draw an image. */ +struct image_backend { + const char *name; + int flags; + int (*select)(struct tty *); + void (*draw_rect)(struct tty *, struct image_rect *); + void (*start_draw)(struct tty *, u_int, u_int, u_int, u_int); + void (*flush)(struct tty *); + void (*free)(struct tty *); +}; + +/* Backend definitions. */ +static struct image_backend image_backend_fallback = { + "fallback", + IMAGE_BACKEND_SCROLLS, + fallback_select, + NULL, + NULL, + NULL, + NULL +}; +static struct image_backend image_backend_sixel = { + "sixel", + IMAGE_BACKEND_GRAPHICAL, + sixel_select, + sixel_draw_rect, + sixel_start_draw, + sixel_flush_pending, + sixel_free_pending +}; + +/* Backends in order of preference. */ +static struct image_backend *image_backends[] = { + &image_backend_sixel, + &image_backend_fallback +}; + +/* Update the image backend. */ +int +image_tty_update(struct tty *tty) +{ + struct image_backend *backend; + u_int i; + + for (i = 0; i < nitems(image_backends); i++) { + backend = image_backends[i]; + if (backend->select(tty)) + break; + } + if (tty->image_backend == backend) + return (0); + + if (tty->image_backend != NULL && tty->image_backend->free != NULL) + tty->image_backend->free(tty); + tty->image_data = NULL; + tty->image_backend = backend; + return (1); +} + +/* Clear old image pixels before redrawing a terminal area. */ +void +image_redraw_start(struct tty *tty, u_int x, u_int y, u_int w, u_int h) +{ + image_tty_update(tty); + if (tty->image_backend->start_draw != NULL) + tty->image_backend->start_draw(tty, x, y, w, h); +} + +/* Write pending image output to the terminal. */ +void +image_draw_flush(struct tty *tty) +{ + image_tty_update(tty); + if (tty->image_backend->flush != NULL) + tty->image_backend->flush(tty); +} + +/* Discard image backend state after a terminal size change. */ +void +image_tty_size_changed(struct tty *tty) +{ + if (!image_tty_update(tty)) { + if (tty->image_backend->free != NULL) + tty->image_backend->free(tty); + } +} + +/* Free image backend state for a terminal. */ +void +image_tty_free(struct tty *tty) +{ + if (tty->image_backend != NULL) { + if (tty->image_backend->free != NULL) + tty->image_backend->free(tty); + tty->image_backend = NULL; + tty->image_data = NULL; + } +} + /* Get the placement store for a grid. */ static struct image_store * image_store_get(struct grid *gd) @@ -401,6 +500,7 @@ image_grid_copy_area(struct grid *dst, u_int dx, u_int dy, struct grid *src, struct image_copy_map *maps = NULL; struct image_placement *plmt; struct image_line *src_line, *dst_line; + struct grid_line *gl; struct image_span *span; u_int nmaps = 0, row, start, end, span_end; @@ -428,8 +528,10 @@ image_grid_copy_area(struct grid *dst, u_int dx, u_int dy, struct grid *src, continue; if (span_end > end) span_end = end; - if (dst_line == NULL) - dst_line = image_line_get(&dst->linedata[dy + row]); + if (dst_line == NULL) { + gl = &dst->linedata[dy + row]; + dst_line = image_line_get(gl); + } plmt = image_copy_placement(dst, span->placement, &maps, &nmaps); plmt->recover_from = 0; diff --git a/image-sixel.c b/image-sixel.c index 9b657672f..55267170b 100644 --- a/image-sixel.c +++ b/image-sixel.c @@ -100,6 +100,23 @@ struct sixel_plane { char *stream; }; +/* Adjacent placement rows buffered for one SIXEL output sequence. */ +struct sixel_pending { + struct image *image; + + /* Source in image in cells. */ + u_int src_x; + u_int src_y; + + /* Size in cells. */ + u_int w; + u_int h; + + /* Destination on terminal in cells. */ + u_int dst_x; + u_int dst_y; +}; + /* RGB histogram entry. */ struct sixel_hgram { u_int count; @@ -1272,3 +1289,115 @@ sixel_from_image(struct image *im, u_int x, u_int y, u_int sx, u_int sy, sixel_free(si); return (data); } + +/* Return whether the terminal supports SIXEL. */ +int +sixel_select(struct tty *tty) +{ + if (tty->term != NULL && + (tty->term->flags & TERM_SIXEL) && + tty->xpixel != 0 && + tty->ypixel != 0) + return (1); + return (0); +} + +/* Get pending SIXEL rows for a terminal. */ +static struct sixel_pending * +sixel_get_pending(struct tty *tty) +{ + struct sixel_pending *sp = tty->image_data; + + if (sp == NULL) { + sp = xcalloc(1, sizeof *sp); + tty->image_data = sp; + } + return (sp); +} + +/* Free pending SIXEL rows for a terminal. */ +void +sixel_free_pending(struct tty *tty) +{ + struct sixel_pending *sp = tty->image_data; + + if (sp == NULL) + return; + if (sp->image != NULL) + image_free(sp->image); + free(sp); + tty->image_data = NULL; +} + +/* Write the rows held in the pending run as a single SIXEL. */ +void +sixel_flush_pending(struct tty *tty) +{ + struct sixel_pending *sp = tty->image_data; + struct image *im; + char *data; + size_t size; + + if (sp == NULL || sp->image == NULL) + return; + im = sp->image; + sp->image = NULL; + + data = sixel_from_image(im, sp->src_x, sp->src_y, sp->w, sp->h, + tty->xpixel, tty->ypixel, &size); + if (data != NULL) { + tty_region_off(tty); + tty_margin_off(tty); + tty_cursor(tty, sp->dst_x, sp->dst_y); + + tty->flags |= TTY_NOBLOCK; + tty_putn(tty, data, size, 0); + tty->cx = tty->cy = UINT_MAX; + + free(data); + } + image_free(im); +} + +/* Queue adjacent placement rows for one SIXEL output sequence. */ +void +sixel_draw_rect(struct tty *tty, struct image_rect *rect) +{ + struct sixel_pending *sp = sixel_get_pending(tty); + + if (sp->image == rect->image && + sp->src_x == rect->src_x && + sp->w == rect->sx && + sp->dst_x == rect->dst_x && + sp->src_y + sp->h == rect->src_y && + sp->dst_y + sp->h == rect->dst_y) { + sp->h += rect->sy; + return; + } + + image_ref(rect->image); + sixel_flush_pending(tty); + + sp->image = rect->image; + sp->src_x = rect->src_x; + sp->src_y = rect->src_y; + sp->w = rect->sx; + sp->h = rect->sy; + sp->dst_x = rect->dst_x; + sp->dst_y = rect->dst_y; +} + +/* Remove old SIXEL pixels before drawing an image area. */ +void +sixel_start_draw(struct tty *tty, u_int x, u_int y, u_int sx, u_int sy) +{ + u_int yy; + + for (yy = y; yy < y + sy; yy++) { + tty_cursor(tty, x, yy); + if (tty_term_has(tty->term, TTYC_ECH)) + tty_putcode_i(tty, TTYC_ECH, sx); + else + tty_repeat_space(tty, sx); + } +} diff --git a/tmux-image.h b/tmux-image.h index 66fb5f295..9616e739b 100644 --- a/tmux-image.h +++ b/tmux-image.h @@ -21,12 +21,13 @@ #define TMUX_IMAGE_H #ifdef ENABLE_IMAGES -#error image support is not ready yet +//#error image support is not ready yet struct grid; struct grid_cell; struct grid_line; struct image; +struct image_backend; struct image_line; struct image_store; struct screen; @@ -37,6 +38,12 @@ struct tty; /* Maximum image size in bytes. */ #define IMAGE_SIZE_LIMIT (64 * 1024 * 1024) +/* Backend draws images as graphics. */ +#define IMAGE_BACKEND_GRAPHICAL 0x1 + +/* Backend images move with terminal scrolling. */ +#define IMAGE_BACKEND_SCROLLS 0x2 + /* View of all or part of an image. */ struct image_view { /* Position and size in the image, in pixels. */ @@ -60,6 +67,23 @@ struct image_view { u_int sy; }; +/* A part of an image to draw. */ +struct image_rect { + struct image *image; + + /* Size in cells. */ + u_int sx; + u_int sy; + + /* Source in image in cells. */ + u_int src_x; + u_int src_y; + + /* Destination on terminal in cells. */ + u_int dst_x; + u_int dst_y; +}; + /* image.c */ struct image *image_create(u_int, u_int, u_int, u_int, u_int, u_int, u_char *); @@ -102,13 +126,20 @@ void image_damage_screen(struct screen_write_ctx *, u_int, u_int, void image_write(struct screen_write_ctx *, struct image *, u_int); int image_grid_get_at(struct grid *, u_int, u_int, struct image **, u_int *, u_int *); +void image_redraw_start(struct tty *, u_int, u_int, u_int, u_int); +void image_draw_flush(struct tty *); +int image_tty_update(struct tty *); +void image_tty_size_changed(struct tty *); +void image_tty_free(struct tty *); /* image-fallback.c */ +int fallback_select(struct tty *); const struct grid_cell *fallback_map_cell(struct tty *, struct grid *, u_int, u_int, const struct grid_cell *, struct grid_cell *, int *); /* image-sixel.c */ #define SIXEL_COLOUR_REGISTERS 1024 +int sixel_select(struct tty *); struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int, u_int); void sixel_free(struct sixel_image *); @@ -118,6 +149,10 @@ void sixel_write(struct screen_write_ctx *, struct sixel_image *, u_int); char *sixel_from_image(struct image *, u_int, u_int, u_int, u_int, u_int, u_int, size_t *); +void sixel_draw_rect(struct tty *, struct image_rect *); +void sixel_start_draw(struct tty *, u_int, u_int, u_int, u_int); +void sixel_free_pending(struct tty *); +void sixel_flush_pending(struct tty *); #endif /* ENABLE_IMAGES */ diff --git a/tmux.h b/tmux.h index 747adf741..7c86555e1 100644 --- a/tmux.h +++ b/tmux.h @@ -1852,6 +1852,11 @@ struct tty { struct event key_timer; struct tty_key *key_tree; + +#ifdef ENABLE_IMAGES + struct image_backend *image_backend; + void *image_data; +#endif }; /* Terminal command context. */ @@ -2941,7 +2946,7 @@ void tty_cell(struct tty *, const struct grid_cell *, const struct tty_style_ctx *); int tty_init(struct tty *, struct client *); void tty_resize(struct tty *); -void tty_set_size(struct tty *, u_int, u_int, u_int, u_int); +int tty_set_size(struct tty *, u_int, u_int, u_int, u_int); void tty_invalidate(struct tty *); void tty_start_tty(struct tty *); void tty_send_requests(struct tty *); diff --git a/tty-keys.c b/tty-keys.c index b9ca2f019..122786e1d 100644 --- a/tty-keys.c +++ b/tty-keys.c @@ -1914,7 +1914,8 @@ tty_keys_winsz(struct tty *tty, const char *buf, size_t len, size_t *size, return (0); /* Window size in characters. */ - tty_set_size(tty, sx, sy, tty->xpixel, tty->ypixel); + if (tty_set_size(tty, sx, sy, tty->xpixel, tty->ypixel)) + server_redraw_client(c); return (0); } else if (sscanf(tmp, "4;%u;%u", &ypixel, &xpixel) == 2) { *size = end + 1; @@ -1924,7 +1925,8 @@ tty_keys_winsz(struct tty *tty, const char *buf, size_t len, size_t *size, /* Window size in pixels. */ char_x = (xpixel && tty->sx) ? xpixel / tty->sx : 0; char_y = (ypixel && tty->sy) ? ypixel / tty->sy : 0; - tty_set_size(tty, tty->sx, tty->sy, char_x, char_y); + if (tty_set_size(tty, tty->sx, tty->sy, char_x, char_y)) + server_redraw_client(c); tty_invalidate(tty); tty->flags &= ~TTY_WINSIZEQUERY; diff --git a/tty.c b/tty.c index ad2853d42..6251b991d 100644 --- a/tty.c +++ b/tty.c @@ -155,13 +155,22 @@ tty_resize(struct tty *tty) tty_invalidate(tty); } -void +int tty_set_size(struct tty *tty, u_int sx, u_int sy, u_int xpixel, u_int ypixel) { + if (tty->sx == sx && + tty->sy == sy && + tty->xpixel == xpixel && + tty->ypixel == ypixel) + return (0); tty->sx = sx; tty->sy = sy; tty->xpixel = xpixel; tty->ypixel = ypixel; +#ifdef ENABLE_IMAGES + image_tty_size_changed(tty); +#endif + return (1); } static void @@ -523,6 +532,9 @@ tty_close(struct tty *tty) tty_stop_tty(tty); if (tty->flags & TTY_OPENED) { +#ifdef ENABLE_IMAGES + image_tty_free(tty); +#endif evbuffer_free(tty->in); event_del(&tty->event_in); evbuffer_free(tty->out); @@ -539,6 +551,9 @@ void tty_free(struct tty *tty) { tty_close(tty); +#ifdef ENABLE_IMAGES + image_tty_free(tty); +#endif } void @@ -548,6 +563,9 @@ tty_update_features(struct tty *tty) if (tty_apply_features(tty->term)) tty_term_apply_overrides(tty->term); +#ifdef ENABLE_IMAGES + image_tty_update(tty); +#endif if (tty_use_margin(tty)) tty_putcode(tty, TTYC_ENMG);