From c4bdd8eca16cb2325a9a1b64f0bf6f76f7e99e38 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 3 Aug 2026 11:56:25 +0100 Subject: [PATCH] Make sixel images grid resident Replace screen-coordinate image placements with immutable pixel images referenced by ordinary grid cells. Draw visible marker runs through the redraw scene using per-client sixel or text backends so images follow history, copy mode, clipping and overwrites. --- Makefile.am | 6 +- configure.ac | 22 +- format.c | 14 + grid.c | 139 +++++++++- image-ascii.c | 40 +++ image-sixel.c | 191 +++++++++++++- image.c | 542 ++++++++++++++++++++++++++++----------- regress/image-support.sh | 68 +++++ screen-redraw.c | 12 +- screen-write.c | 147 +++-------- screen.c | 50 ---- style.c | 6 +- tmux.h | 127 ++++++--- tty-draw.c | 56 +++- tty-features.c | 6 +- tty.c | 159 +++--------- window-copy.c | 4 +- 17 files changed, 1082 insertions(+), 507 deletions(-) create mode 100644 image-ascii.c create mode 100755 regress/image-support.sh diff --git a/Makefile.am b/Makefile.am index cbdb4e863..3ab3989c3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -246,9 +246,9 @@ if HAVE_UTF8PROC nodist_tmux_SOURCES += compat/utf8proc.c endif -# Enable sixel support. -if ENABLE_SIXEL -dist_tmux_SOURCES += image.c image-sixel.c +# Enable image support. +if ENABLE_IMAGES +dist_tmux_SOURCES += image.c image-sixel.c image-ascii.c endif # Add bits for fuzzing if enabled. diff --git a/configure.ac b/configure.ac index b09bc5d4e..91778d752 100644 --- a/configure.ac +++ b/configure.ac @@ -530,15 +530,25 @@ if test "x$enable_cgroups" = xyes; then fi fi -# Enable sixel support. +# Enable protocol-neutral image support. +AC_ARG_WITH( + image-support, + AS_HELP_STRING(--with-image-support, enable SIXEL and ASCII images) +) + +# Keep the old option as an alias for one compatibility cycle. AC_ARG_ENABLE( sixel, - AS_HELP_STRING(--enable-sixel, enable sixel images) + AS_HELP_STRING(--enable-sixel, deprecated alias for --with-image-support) ) if test "x$enable_sixel" = xyes; then + with_image_support=yes +fi +if test "x$with_image_support" = xyes; then + AC_DEFINE(ENABLE_IMAGES) AC_DEFINE(ENABLE_SIXEL) fi -AM_CONDITIONAL(ENABLE_SIXEL, [test "x$enable_sixel" = xyes]) +AM_CONDITIONAL(ENABLE_IMAGES, [test "x$with_image_support" = xyes]) # Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well. AC_MSG_CHECKING(for b64_ntop) @@ -1137,10 +1147,10 @@ if test "x$enable_asan" = xyes; then else AC_MSG_NOTICE([ASAN: off]) fi -if test "x$enable_sixel" = xyes; then - AC_MSG_NOTICE([SIXEL: on]) +if test "x$with_image_support" = xyes; then + AC_MSG_NOTICE([images: on]) else - AC_MSG_NOTICE([SIXEL: off]) + AC_MSG_NOTICE([images: off]) fi if test "x$enable_debug" = xyes; then AC_MSG_NOTICE([debug: on]) diff --git a/format.c b/format.c index 0ac1a8f38..97abc0b85 100644 --- a/format.c +++ b/format.c @@ -2964,6 +2964,17 @@ format_cb_sixel_support(__unused struct format_tree *ft) #endif } +/* Callback for image_support. */ +static void * +format_cb_image_support(__unused struct format_tree *ft) +{ +#ifdef ENABLE_IMAGES + return (xstrdup("1")); +#else + return (xstrdup("0")); +#endif +} + /* Callback for active_window_index. */ static void * format_cb_active_window_index(struct format_tree *ft) @@ -3627,6 +3638,9 @@ static const struct format_table_entry format_table[] = { { "host_short", FORMAT_TABLE_STRING, format_cb_host_short }, + { "image_support", FORMAT_TABLE_STRING, + format_cb_image_support + }, { "insert_flag", FORMAT_TABLE_STRING, format_cb_insert_flag }, diff --git a/grid.c b/grid.c index 45f400bee..fb358dd06 100644 --- a/grid.c +++ b/grid.c @@ -41,6 +41,9 @@ /* Default grid cell data. */ const struct grid_cell grid_default_cell = { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 8, 0 +#ifdef ENABLE_IMAGES + , 0, 0, 0 +#endif }; /* @@ -49,16 +52,38 @@ const struct grid_cell grid_default_cell = { */ static const struct grid_cell grid_padding_cell = { { { '!' }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 8, 8, 8, 0 +#ifdef ENABLE_IMAGES + , 0, 0, 0 +#endif }; /* Cleared grid cell data. */ static const struct grid_cell grid_cleared_cell = { { { ' ' }, 0, 1, 1 }, 0, GRID_FLAG_CLEARED, 8, 8, 8, 0 +#ifdef ENABLE_IMAGES + , 0, 0, 0 +#endif }; static const struct grid_cell_entry grid_cleared_entry = { { .data = { 0, 8, 8, ' ' } }, GRID_FLAG_CLEARED }; +#ifdef ENABLE_IMAGES +/* Return the image referenced by a stored cell, or zero. */ +static u_int +grid_entry_image(struct grid_line *gl, struct grid_cell_entry *gce) +{ + struct grid_extd_entry *gee; + + if (~gce->flags & GRID_FLAG_EXTENDED || gce->offset >= gl->extdsize) + return (0); + gee = &gl->extddata[gce->offset]; + if (~gee->flags & GRID_FLAG_IMAGE) + return (0); + return (gee->image_id); +} +#endif + #ifdef __APPLE__ void grid_check_is_clear(struct grid *gd) @@ -134,6 +159,10 @@ grid_need_extended_cell(const struct grid_cell_entry *gce, return (1); if (gc->flags & GRID_FLAG_TAB) return (1); +#ifdef ENABLE_IMAGES + if (gc->flags & GRID_FLAG_IMAGE) + return (1); +#endif return (0); } @@ -181,6 +210,11 @@ grid_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce, gee->bg = gc->bg; gee->us = gc->us; gee->link = gc->link; +#ifdef ENABLE_IMAGES + gee->image_id = gc->image_id; + gee->image_x = gc->image_x; + gee->image_y = gc->image_y; +#endif return (gee); } @@ -268,6 +302,12 @@ grid_clear_cell(struct grid *gd, u_int px, u_int py, u_int bg, int moved) struct grid_extd_entry *gee; u_int old_offset = gce->offset; int had_extd = (gce->flags & GRID_FLAG_EXTENDED); +#ifdef ENABLE_IMAGES + u_int image_id = grid_entry_image(gl, gce); + + if (!moved && image_id != 0) + image_free(image_id); +#endif memcpy(gce, &grid_cleared_entry, sizeof *gce); if (!moved && had_extd && old_offset < gl->extdsize) { @@ -314,6 +354,13 @@ grid_cells_look_equal(const struct grid_cell *gc1, const struct grid_cell *gc2) return (0); if (gc1->link != gc2->link) return (0); +#ifdef ENABLE_IMAGES + if ((gc1->flags & GRID_FLAG_IMAGE) && + (gc1->image_id != gc2->image_id || + gc1->image_x != gc2->image_x || + gc1->image_y != gc2->image_y)) + return (0); +#endif return (1); } @@ -346,6 +393,10 @@ static void grid_free_line(struct grid *gd, u_int py) { struct grid_line *gl = &gd->linedata[py]; +#ifdef ENABLE_IMAGES + struct grid_cell_entry *gce; + u_int image_id, px; +#endif #ifdef __APPLE__ assert(gl->cellused <= gl->cellsize); @@ -353,6 +404,14 @@ grid_free_line(struct grid *gd, u_int py) assert(gl->cellsize == 0 || gl->celldata != NULL); #endif +#ifdef ENABLE_IMAGES + for (px = 0; px < gl->cellsize; px++) { + gce = &gl->celldata[px]; + image_id = grid_entry_image(gl, gce); + if (image_id != 0) + image_free(image_id); + } +#endif free(gl->celldata); free(gl->extddata); memset(gl, 0, sizeof *gl); @@ -624,6 +683,11 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc) gc->bg = gee->bg; gc->us = gee->us; gc->link = gee->link; +#ifdef ENABLE_IMAGES + gc->image_id = gee->image_id; + gc->image_x = gee->image_x; + gc->image_y = gee->image_y; +#endif if (gc->flags & GRID_FLAG_TAB) grid_set_tab(gc, gee->data); @@ -644,6 +708,11 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc) gc->us = 8; utf8_set(&gc->data, gce->data.data); gc->link = 0; +#ifdef ENABLE_IMAGES + gc->image_id = 0; + gc->image_x = 0; + gc->image_y = 0; +#endif } /* Get cell for reading. */ @@ -663,6 +732,9 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc) { struct grid_line *gl; struct grid_cell_entry *gce; +#ifdef ENABLE_IMAGES + u_int old_id, new_id = 0; +#endif if (grid_check_y(gd, __func__, py) != 0) return; @@ -674,6 +746,17 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc) gl->cellused = px + 1; gce = &gl->celldata[px]; +#ifdef ENABLE_IMAGES + old_id = grid_entry_image(gl, gce); + if (gc->flags & GRID_FLAG_IMAGE) + new_id = gc->image_id; + if (old_id != new_id) { + if (old_id != 0) + image_free(old_id); + if (new_id != 0) + image_ref(new_id); + } +#endif if (grid_need_extended_cell(gce, gc)) grid_extended_cell(gl, gce, gc); else @@ -708,6 +791,20 @@ 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]; +#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; + + if (old_id != new_id) { + if (old_id != 0) + image_free(old_id); + if (new_id != 0) + image_ref(new_id); + } + } +#endif if (grid_need_extended_cell(gce, gc)) { gee = grid_extended_cell(gl, gce, gc); gee->data = utf8_build_one(s[i]); @@ -827,6 +924,9 @@ grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx, { struct grid_line *gl; u_int xx; +#ifdef ENABLE_IMAGES + u_int image_id; +#endif if (nx == 0 || px == dx) return; @@ -837,6 +937,16 @@ grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx, grid_expand_line(gd, py, px + nx, 8); grid_expand_line(gd, py, dx + nx, 8); +#ifdef ENABLE_IMAGES + /* References in the source range are transferred, not duplicated. */ + for (xx = dx; xx < dx + nx; xx++) { + if (xx >= px && xx < px + nx) + continue; + image_id = grid_entry_image(gl, &gl->celldata[xx]); + if (image_id != 0) + image_free(image_id); + } +#endif memmove(&gl->celldata[dx], &gl->celldata[px], nx * sizeof *gl->celldata); if (dx + nx > gl->cellused) @@ -1209,6 +1319,13 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx, grid_get_cell(gd, xx, py, &gc); if (gc.flags & GRID_FLAG_PADDING) continue; +#ifdef ENABLE_IMAGES + if (gc.flags & GRID_FLAG_IMAGE) { + utf8_set(&gc.data, ' '); + gc.flags &= ~GRID_FLAG_IMAGE; + gc.image_id = gc.image_x = gc.image_y = 0; + } +#endif if (lastgc != NULL && (flags & GRID_STRING_WITH_SEQUENCES)) { grid_string_cells_code(*lastgc, &gc, code, sizeof code, @@ -1276,6 +1393,9 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy, { struct grid_line *dstl, *srcl; u_int yy; +#ifdef ENABLE_IMAGES + u_int px, image_id; +#endif if (dy + ny > dst->hsize + dst->sy) ny = dst->hsize + dst->sy - dy; @@ -1304,6 +1424,13 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy, } else dstl->extddata = NULL; +#ifdef ENABLE_IMAGES + for (px = 0; px < dstl->cellsize; px++) { + image_id = grid_entry_image(dstl, &dstl->celldata[px]); + if (image_id != 0) + image_ref(image_id); + } +#endif sy++; dy++; } @@ -1439,8 +1566,7 @@ grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy, /* Remove the lines that were completely consumed. */ for (i = yy + 1; i < yy + 1 + lines; i++) { - free(gd->linedata[i].celldata); - free(gd->linedata[i].extddata); + grid_free_line(gd, i); grid_reflow_dead(&gd->linedata[i]); } @@ -1496,6 +1622,15 @@ grid_reflow_split(struct grid *target, struct grid *gd, u_int sx, u_int yy, } width += gc.data.width; grid_set_cell(target, xx, line, &gc); +#ifdef ENABLE_IMAGES + /* + * The tail of the original line is discarded below. Transfer + * its image reference to the new cell rather than duplicating + * it. + */ + if (gc.flags & GRID_FLAG_IMAGE) + image_free(gc.image_id); +#endif xx++; } if (flags & GRID_LINE_WRAPPED) diff --git a/image-ascii.c b/image-ascii.c new file mode 100644 index 000000000..d1598a26f --- /dev/null +++ b/image-ascii.c @@ -0,0 +1,40 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2026 Michael Grant + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include + +#include "tmux.h" + +void +image_get_text_cell(__unused struct tty *tty, struct image *im, u_int x, + u_int y, const struct grid_cell *gc, struct grid_cell *out, + __unused const struct tty_style_ctx *style_ctx) +{ + static const char ramp[] = " .:-=+*#%@"; + const struct image_cell *cell; + u_int level = 0; + + memcpy(out, gc, sizeof *out); + cell = image_get_cell(im, x, y); + if (cell != NULL) + level = cell->whole.brightness * (sizeof ramp - 2) / 255; + utf8_set(&out->data, ramp[level]); + out->flags &= ~GRID_FLAG_IMAGE; +} diff --git a/image-sixel.c b/image-sixel.c index a004d78f8..577ba51a1 100644 --- a/image-sixel.c +++ b/image-sixel.c @@ -1,7 +1,7 @@ /* $OpenBSD$ */ /* - * Copyright (c) 2019 Nicholas Marriott + * Copyright (c) 2026 Michael Grant * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,6 +18,7 @@ #include +#include #include #include @@ -397,16 +398,99 @@ sixel_log(struct sixel_image *si) void sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y) { - if ((si->x % si->xpixel) == 0) - *x = (si->x / si->xpixel); - else - *x = 1 + (si->x / si->xpixel); - if ((si->y % si->ypixel) == 0) - *y = (si->y / si->ypixel); - else - *y = 1 + (si->y / si->ypixel); + if (si->xpixel == 0) + si->xpixel = 8; + if (si->ypixel == 0) + si->ypixel = 16; + image_size_in_cells(si->x, si->y, si->xpixel, si->ypixel, x, y); } +#ifdef ENABLE_IMAGES +static double +sixel_hue(double p, double q, double t) +{ + if (t < 0) + t += 1; + if (t > 1) + t -= 1; + if (t < 1.0 / 6) + return (p + (q - p) * 6 * t); + if (t < 1.0 / 2) + return (q); + if (t < 2.0 / 3) + return (p + (q - p) * (2.0 / 3 - t) * 6); + return (p); +} + +static void +sixel_colour_to_rgb(u_int colour, u_char *r, u_char *g, u_char *b) +{ + u_int type = colour >> 25; + double h, l, s, p, q; + + if (type == 2) { + *r = ((colour >> 16) & 0x1ff) * 255 / 100; + *g = ((colour >> 8) & 0xff) * 255 / 100; + *b = (colour & 0xff) * 255 / 100; + return; + } + if (type != 1) { + *r = *g = *b = 0; + return; + } + + h = ((colour >> 16) & 0x1ff) / 360.0; + l = ((colour >> 8) & 0xff) / 100.0; + s = (colour & 0xff) / 100.0; + if (s == 0) { + *r = *g = *b = l * 255; + return; + } + q = l < 0.5 ? l * (1 + s) : l + s - l * s; + p = 2 * l - q; + *r = sixel_hue(p, q, h + 1.0 / 3) * 255; + *g = sixel_hue(p, q, h) * 255; + *b = sixel_hue(p, q, h - 1.0 / 3) * 255; +} + +/* Convert decoded SIXEL data into the protocol-neutral immutable image. */ +struct image * +sixel_to_image(struct sixel_image *si) +{ + u_char *pixels, *pixel, r, g, b; + u_int x, y, c, sx, sy; + struct image *im; + + if ((uint64_t)si->x * si->y * 4 > SIZE_MAX) + return (NULL); + pixels = xcalloc(si->x * si->y, 4); + for (y = 0; y < si->y; y++) { + for (x = 0; x < si->x; x++) { + c = sixel_get_pixel(si, x, y); + pixel = pixels + ((size_t)y * si->x + x) * 4; + if (c == 0) { + pixel[3] = si->p2 == 1 ? 0 : 255; + continue; + } + c--; + if (c < si->ncolours) + sixel_colour_to_rgb(si->colours[c], &r, &g, &b); + else + r = g = b = 0; + pixel[0] = r; + pixel[1] = g; + pixel[2] = b; + pixel[3] = 255; + } + } + sixel_size_in_cells(si, &sx, &sy); + im = image_create(si->x, si->y, sx, sy, pixels); + if (im == NULL) + free(pixels); + return (im); +} +#endif + struct sixel_image * sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox, u_int oy, u_int sx, u_int sy, int colours) @@ -649,6 +733,95 @@ sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size) return (buf); } +static struct sixel_image * +sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells_x, + u_int cells_y, u_int xpixel, u_int ypixel) +{ + struct sixel_image *si; + const u_char *pixel; + u_int x, y, sx, sy, sourcex, sourcey; + u_int sourcex0, sourcey0, sourcewidth, sourceheight; + u_int r, g, b, colour, i; + + if ((uint64_t)cells_x * xpixel > UINT_MAX || + (uint64_t)cells_y * ypixel > UINT_MAX) + return (NULL); + sx = cells_x * xpixel; + sy = cells_y * ypixel; + if (sx == 0 || sy == 0 || sx > SIXEL_WIDTH_LIMIT || + sy > SIXEL_HEIGHT_LIMIT) + return (NULL); + image_get_pixel_rectangle(im, ox, oy, cells_x, cells_y, &sourcex0, + &sourcey0, &sourcewidth, &sourceheight); + if (sourcewidth == 0 || sourceheight == 0) + return (NULL); + + si = xcalloc(1, sizeof *si); + si->xpixel = xpixel; + si->ypixel = ypixel; + si->p2 = 1; + si->set_ra = 1; + si->ra_x = sx; + si->ra_y = sy; + si->ncolours = si->used_colours = 216; + si->colours = xcalloc(si->ncolours, sizeof *si->colours); + for (i = 0; i < si->ncolours; i++) { + r = (i / 36) * 20; + g = ((i / 6) % 6) * 20; + b = (i % 6) * 20; + si->colours[i] = (2U << 25)|(r << 16)|(g << 8)|b; + } + + for (y = 0; y < sy; y++) { + sourcey = sourcey0 + (uint64_t)y * sourceheight / sy; + if (sourcey >= im->height) + sourcey = im->height - 1; + for (x = 0; x < sx; x++) { + sourcex = sourcex0 + (uint64_t)x * sourcewidth / sx; + if (sourcex >= im->width) + sourcex = im->width - 1; + pixel = im->pixels + sourcey * im->stride + sourcex * 4; + if (pixel[3] < 128) + continue; + r = (pixel[0] * 5 + 127) / 255; + g = (pixel[1] * 5 + 127) / 255; + b = (pixel[2] * 5 + 127) / 255; + colour = r * 36 + g * 6 + b + 1; + if (sixel_set_pixel(si, x, y, colour) != 0) { + sixel_free(si); + return (NULL); + } + } + } + return (si); +} + +void +sixel_draw_rectangle(struct tty *tty, const struct image_rectangle *rectangle, + __unused const struct tty_style_ctx *style_ctx) +{ + struct sixel_image *si; + char *data; + size_t size; + + si = sixel_from_image(rectangle->image, rectangle->source_x, + rectangle->source_y, rectangle->width, rectangle->height, + tty->xpixel, tty->ypixel); + if (si == NULL) + return; + data = sixel_print(si, NULL, &size); + sixel_free(si); + if (data == NULL) + return; + tty_region_off(tty); + tty_margin_off(tty); + tty_cursor(tty, rectangle->destination_x, rectangle->destination_y); + tty->flags |= TTY_NOBLOCK; + tty_putn(tty, data, size, 0); + tty_invalidate(tty); + free(data); +} + struct screen * sixel_to_screen(struct sixel_image *si) { diff --git a/image.c b/image.c index f0543dc4f..a87980176 100644 --- a/image.c +++ b/image.c @@ -1,7 +1,8 @@ /* $OpenBSD$ */ /* - * Copyright (c) 2007 Nicholas Marriott + * Copyright (c) 2019 Nicholas Marriott + * Copyright (c) 2026 Michael Grant * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,205 +19,434 @@ #include +#include #include #include #include "tmux.h" -static struct images all_images = TAILQ_HEAD_INITIALIZER(all_images); -static u_int all_images_count; -#define MAX_IMAGE_COUNT 20 +static struct images images = RB_INITIALIZER(&images); +static u_int image_next_id; -static void printflike(3, 4) -image_log(struct image *im, const char* from, const char* fmt, ...) +#define IMAGE_BACKEND_GRAPHICAL 0x1 +#define IMAGE_BACKEND_SCROLLS 0x2 + +struct image_backend { + const char *name; + int flags; + void (*draw_rectangle)(struct tty *, + const struct image_rectangle *, const struct tty_style_ctx *); +}; + +static const struct image_backend image_backend_ascii = { + "ascii", IMAGE_BACKEND_SCROLLS, NULL +}; +static const struct image_backend image_backend_sixel = { + "sixel", IMAGE_BACKEND_GRAPHICAL, sixel_draw_rectangle +}; + +static const struct image_backend * +image_tty_find_backend(struct tty *tty) { - va_list ap; - char s[128]; - - if (log_get_level() == 0) - return; - - if (fmt == NULL) { - log_debug("%s: %p (%ux%u %u,%u)", from, im, im->sx, im->sy, - im->px, im->py); - return; - } - - va_start(ap, fmt); - vsnprintf(s, sizeof s, fmt, ap); - va_end(ap); - - log_debug("%s: %p (%ux%u %u,%u): %s", from, im, im->sx, im->sy, - im->px, im->py, s); + if (tty->term != NULL && tty->term->flags & TERM_SIXEL && + tty->xpixel != 0 && tty->ypixel != 0) + return (&image_backend_sixel); + return (&image_backend_ascii); } -static void -image_free(struct image *im) +void +image_tty_update(struct tty *tty) { - image_log(im, __func__, NULL); + const struct image_backend *backend; - TAILQ_REMOVE(&all_images, im, all_entry); - all_images_count--; + backend = image_tty_find_backend(tty); + if (tty->image_backend == backend) + return; - TAILQ_REMOVE(im->list, im, entry); - sixel_free(im->data); - free(im->fallback); - free(im); + tty->image_backend = backend; + log_debug("%s: %s image backend is %s", __func__, + tty->client->name, backend->name); } int -image_free_all(struct screen *s) +image_tty_is_graphical(struct tty *tty) { - struct image *im, *im1; - int redraw = !TAILQ_EMPTY(&s->images); - - if (redraw) - log_debug ("%s", __func__); - TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) - image_free(im); - return (redraw); + image_tty_update(tty); + return (!!(tty->image_backend->flags & IMAGE_BACKEND_GRAPHICAL)); } -/* Create text placeholder for an image. */ +int +image_tty_scrolls(struct tty *tty) +{ + image_tty_update(tty); + return (!!(tty->image_backend->flags & IMAGE_BACKEND_SCROLLS)); +} + +void +image_tty_geometry_changed(struct tty *tty) +{ + image_tty_update(tty); +} + +static int +image_cmp(struct image *a, struct image *b) +{ + if (a->id < b->id) + return (-1); + if (a->id > b->id) + return (1); + return (0); +} +RB_GENERATE_STATIC(images, image, entry, image_cmp); + static void -image_fallback(char **ret, u_int sx, u_int sy) +image_sample(struct image *im, uint64_t sample_x, uint64_t sample_y, + uint64_t sample_columns, uint64_t sample_rows, struct image_sample *sample) { - char *buf, *label; - u_int py, size, lsize; + const u_char *pixel; + uint64_t red = 0, green = 0, blue = 0, alpha = 0; + uint64_t brightness = 0, count = 0; + u_int x, y, x0, x1, y0, y1; - /* Allocate first line. */ - lsize = xasprintf(&label, "SIXEL IMAGE (%ux%u)\r\n", sx, sy) + 1; - if (sx < lsize - 3) - size = lsize - 1; - else - size = sx + 2; + x0 = sample_x * im->width / sample_columns; + x1 = (sample_x + 1) * im->width / sample_columns; + y0 = sample_y * im->height / sample_rows; + y1 = (sample_y + 1) * im->height / sample_rows; + if (x1 <= x0) + x1 = x0 + 1; + if (y1 <= y0) + y1 = y0 + 1; + if (x1 > im->width) + x1 = im->width; + if (y1 > im->height) + y1 = im->height; - /* Remaining lines. Every placeholder line has \r\n at the end. */ - size += (sx + 2) * (sy - 1) + 1; - *ret = buf = xmalloc(size); - - /* Render first line. */ - if (sx < lsize - 3) { - memcpy(buf, label, lsize); - buf += lsize - 1; - } else { - memcpy(buf, label, lsize - 3); - buf += lsize - 3; - memset(buf, '+', sx - lsize + 3); - buf += sx - lsize + 3; - snprintf(buf, 3, "\r\n"); - buf += 2; + for (y = y0; y < y1; y++) { + for (x = x0; x < x1; x++) { + pixel = im->pixels + y * im->stride + x * 4; + red += pixel[0] * pixel[3] / 255; + green += pixel[1] * pixel[3] / 255; + blue += pixel[2] * pixel[3] / 255; + alpha += pixel[3]; + brightness += ((2126ULL * pixel[0] + + 7152ULL * pixel[1] + 722ULL * pixel[2]) / 10000) * + pixel[3] / 255; + count++; + } } - - /* Remaining lines. */ - for (py = 1; py < sy; py++) { - memset(buf, '+', sx); - buf += sx; - snprintf(buf, 3, "\r\n"); - buf += 2; - } - - free(label); + if (count == 0) + return; + sample->red = red / count; + sample->green = green / count; + sample->blue = blue / count; + sample->alpha = alpha / count; + sample->brightness = brightness / count; } -struct image* -image_store(struct screen *s, struct sixel_image *si) +static void +image_make_cells(struct image *im) +{ + struct image_cell *cell; + uint64_t columns, rows; + u_int x, y, sample_x, sample_y; + + columns = (uint64_t)im->sx * IMAGE_SAMPLE_COLUMNS; + rows = (uint64_t)im->sy * IMAGE_SAMPLE_ROWS; + im->cells = xcalloc((size_t)im->sx * im->sy, sizeof *im->cells); + for (y = 0; y < im->sy; y++) { + for (x = 0; x < im->sx; x++) { + cell = &im->cells[(size_t)y * im->sx + x]; + image_sample(im, x, y, im->sx, im->sy, &cell->whole); + for (sample_y = 0; sample_y < IMAGE_SAMPLE_ROWS; + sample_y++) { + for (sample_x = 0; + sample_x < IMAGE_SAMPLE_COLUMNS; sample_x++) { + image_sample(im, + (uint64_t)x * IMAGE_SAMPLE_COLUMNS + sample_x, + (uint64_t)y * IMAGE_SAMPLE_ROWS + sample_y, + columns, rows, + &cell->samples[sample_y][sample_x]); + } + } + } + } +} + +struct image * +image_find(u_int id) +{ + struct image find; + + find.id = id; + return (RB_FIND(images, &images, &find)); +} + +struct image * +image_create(u_int width, u_int height, u_int sx, u_int sy, u_char *pixels) { struct image *im; + if (width == 0 || height == 0 || sx == 0 || sy == 0 || pixels == NULL) + return (NULL); + if ((uint64_t)width * height * 4 > SIZE_MAX) + return (NULL); + if ((uint64_t)sx * sy > SIZE_MAX / sizeof *im->cells) + return (NULL); + im = xcalloc(1, sizeof *im); - im->s = s; - im->data = si; + do { + if (++image_next_id == 0) + image_next_id++; + im->id = image_next_id; + } while (image_find(im->id) != NULL); - im->px = s->cx; - im->py = s->cy; - sixel_size_in_cells(si, &im->sx, &im->sy); - - image_fallback(&im->fallback, im->sx, im->sy); - - image_log(im, __func__, NULL); - im->list = &s->images; - TAILQ_INSERT_TAIL(im->list, im, entry); - - TAILQ_INSERT_TAIL(&all_images, im, all_entry); - if (++all_images_count == MAX_IMAGE_COUNT) - image_free(TAILQ_FIRST(&all_images)); + im->references = 1; + im->width = width; + im->height = height; + im->sx = sx; + im->sy = sy; + im->stride = (size_t)width * 4; + im->size = im->stride * height; + im->pixels = pixels; + RB_INSERT(images, &images, im); + log_debug("%s: image %u is %ux%u pixels, %ux%u cells", __func__, + im->id, width, height, sx, sy); return (im); } -int -image_check_line(struct screen *s, u_int py, u_int ny) +void +image_ref(u_int id) { - struct image *im, *im1; - int redraw = 0, in; + struct image *im = image_find(id); - TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { - in = (py + ny > im->py && py < im->py + im->sy); - image_log(im, __func__, "py=%u, ny=%u, in=%d", py, ny, in); - if (in) { - image_free(im); - redraw = 1; - } - } - return (redraw); + if (im == NULL) + fatalx("reference to missing image %u", id); + if (im->references == UINT_MAX) + fatalx("too many references to image %u", id); + im->references++; } -int +void +image_free(u_int id) +{ + struct image *im = image_find(id); + + if (im == NULL) + fatalx("free of missing image %u", id); + if (--im->references != 0) + return; + + log_debug("%s: freeing image %u", __func__, id); + RB_REMOVE(images, &images, im); + free(im->pixels); + free(im->cells); + free(im); +} + +const struct image_cell * +image_get_cell(struct image *im, u_int x, u_int y) +{ + if (im == NULL || x >= im->sx || y >= im->sy) + return (NULL); + if (im->cells == NULL) + image_make_cells(im); + return (&im->cells[(size_t)y * im->sx + x]); +} + +void +image_set_cell(struct grid_cell *gc, struct image *im, u_int x, u_int y) +{ + memcpy(gc, &grid_default_cell, sizeof *gc); + gc->flags |= GRID_FLAG_IMAGE; + gc->image_id = im->id; + gc->image_x = x; + gc->image_y = y; +} + +/* Convert a cell-aligned image rectangle into source pixel coordinates. */ +void +image_get_pixel_rectangle(const struct image *im, u_int x, u_int y, + u_int width, u_int height, u_int *px, u_int *py, u_int *pwidth, + u_int *pheight) +{ + u_int x1, y1; + + *px = *py = *pwidth = *pheight = 0; + if (im == NULL || x >= im->sx || y >= im->sy || width == 0 || + height == 0) + return; + if (width > im->sx - x) + width = im->sx - x; + if (height > im->sy - y) + height = im->sy - y; + + *px = (uint64_t)x * im->width / im->sx; + *py = (uint64_t)y * im->height / im->sy; + x1 = (uint64_t)(x + width) * im->width / im->sx; + y1 = (uint64_t)(y + height) * im->height / im->sy; + if (*px >= im->width) + *px = im->width - 1; + if (*py >= im->height) + *py = im->height - 1; + if (x1 <= *px) + x1 = *px + 1; + if (y1 <= *py) + y1 = *py + 1; + if (x1 > im->width) + x1 = im->width; + if (y1 > im->height) + y1 = im->height; + *pwidth = x1 - *px; + *pheight = y1 - *py; +} + +void +image_size_in_cells(u_int width, u_int height, u_int xpixel, u_int ypixel, + u_int *sx, u_int *sy) +{ + if (xpixel == 0) + xpixel = 8; + if (ypixel == 0) + ypixel = 16; + *sx = ((uint64_t)width + xpixel - 1) / xpixel; + *sy = ((uint64_t)height + ypixel - 1) / ypixel; +} + +static int image_check_area(struct screen *s, u_int px, u_int py, u_int nx, u_int ny) { - struct image *im, *im1; - int redraw = 0, in; + struct grid_cell gc; + u_int x, y, ex, ey; + u_int sx = screen_size_x(s), sy = screen_size_y(s); - TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { - in = (py < im->py + im->sy && - py + ny > im->py && - px < im->px + im->sx && - px + nx > im->px); - image_log(im, __func__, "py=%u, ny=%u, in=%d", py, ny, in); - if (in) { - image_free(im); - redraw = 1; + if (px >= sx || py >= sy || nx == 0 || ny == 0) + return (0); + if (nx > sx - px) + ex = sx; + else + ex = px + nx; + if (ny > sy - py) + ey = sy; + else + ey = py + ny; + for (y = py; y < ey; y++) { + for (x = px; x < ex; x++) { + grid_view_get_cell(s->grid, x, y, &gc); + if (gc.flags & GRID_FLAG_IMAGE) + return (1); } } - return (redraw); + return (0); } -int -image_scroll_up(struct screen *s, u_int lines) +void +image_damage_area(struct screen_write_ctx *ctx, u_int px, u_int py, u_int nx, + u_int ny) { - struct image *im, *im1; - int redraw = 0; - u_int sx, sy; - struct sixel_image *new; - - TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) { - if (im->py >= lines) { - image_log(im, __func__, "1, lines=%u", lines); - im->py -= lines; - redraw = 1; - continue; - } - if (im->py + im->sy <= lines) { - image_log(im, __func__, "2, lines=%u", lines); - image_free(im); - redraw = 1; - continue; - } - sx = im->sx; - sy = (im->py + im->sy) - lines; - image_log(im, __func__, "3, lines=%u, sy=%u", lines, sy); - - new = sixel_scale(im->data, 0, 0, 0, im->sy - sy, sx, sy, 1); - sixel_free(im->data); - im->data = new; - - im->py = 0; - sixel_size_in_cells(im->data, &im->sx, &im->sy); - - free(im->fallback); - image_fallback(&im->fallback, im->sx, im->sy); - redraw = 1; - } - return (redraw); + if (ctx->wp != NULL && image_check_area(ctx->s, px, py, nx, ny)) + ctx->wp->flags |= PANE_REDRAW; +} + +void +image_damage_all(struct screen_write_ctx *ctx) +{ + image_damage_area(ctx, 0, 0, screen_size_x(ctx->s), + screen_size_y(ctx->s)); +} + +void +image_damage_scroll(struct screen_write_ctx *ctx, __unused u_int lines) +{ + image_damage_all(ctx); +} + +/* Draw the graphical image marker runs in one visible scene span. */ +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) +{ + const struct image_backend *backend; + struct image_rectangle rectangle; + struct grid_cell gc, next; + struct image *im; + u_int i, run; + + image_tty_update(tty); + backend = tty->image_backend; + if (~backend->flags & IMAGE_BACKEND_GRAPHICAL) + return; + + for (i = 0; i < nx; i += run) { + grid_view_get_cell(s->grid, px + i, py, &gc); + if (~gc.flags & GRID_FLAG_IMAGE) { + run = 1; + continue; + } + im = image_find(gc.image_id); + if (im == NULL) { + run = 1; + continue; + } + for (run = 1; i + run < nx; run++) { + grid_view_get_cell(s->grid, px + i + run, py, &next); + if (~next.flags & GRID_FLAG_IMAGE || + next.image_id != gc.image_id || + next.image_y != gc.image_y || + next.image_x != gc.image_x + run) + break; + } + rectangle.image = im; + memcpy(&rectangle.cell, &gc, sizeof rectangle.cell); + rectangle.source_x = gc.image_x; + rectangle.source_y = gc.image_y; + rectangle.width = run; + rectangle.height = 1; + rectangle.destination_x = atx + i; + rectangle.destination_y = aty; + backend->draw_rectangle(tty, &rectangle, style_ctx); + } +} + +/* + * Put image marker cells at the cursor. The pixel object is immutable; only + * the marker rectangle is clipped to the available pane cells. + */ +void +image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg) +{ + struct screen *s = ctx->s; + struct grid *gd = s->grid; + struct grid_cell gc; + u_int cx = s->cx, cy = s->cy; + u_int x, y, sx, sy, lines; + + sx = im->sx; + if (sx > screen_size_x(s) - cx) + sx = screen_size_x(s) - cx; + sy = im->sy; + if (sy > screen_size_y(s) - 1) + sy = screen_size_y(s) - 1; + if (sx == 0 || sy == 0) + return; + + if (screen_size_y(s) - cy <= sy) { + lines = sy - (screen_size_y(s) - cy) + 1; + screen_write_scrollup(ctx, lines, bg); + if (lines > cy) + screen_write_cursormove(ctx, -1, 0, 0); + else + screen_write_cursormove(ctx, -1, cy - lines, 0); + cy = s->cy; + } + + for (y = 0; y < sy; y++) { + for (x = 0; x < sx; x++) { + image_set_cell(&gc, im, x, y); + gc.bg = bg; + grid_view_set_cell(gd, cx + x, cy + y, &gc); + } + } + image_damage_area(ctx, cx, cy, sx, sy); + screen_write_cursormove(ctx, 0, cy + sy, 0); } diff --git a/regress/image-support.sh b/regress/image-support.sh new file mode 100755 index 000000000..cc897e5a7 --- /dev/null +++ b/regress/image-support.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +# Grid-resident SIXEL image markers: history, copy, overwrite and fallback. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Limage$$ -f/dev/null" +TMUX2="$TEST_TMUX -Limage-client$$ -f/dev/null" +$TMUX kill-server 2>/dev/null +$TMUX2 kill-server 2>/dev/null + +TMP=$(mktemp) +trap "$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null; rm -f $TMP" 0 1 15 + +$TMUX new-session -d -x 20 -y 8 " + printf '\033Pq\"1;1;8;16#0;2;100;100;100#0~~~~~~~~\044-~~~~~~~~\033\\' + i=0 + while [ \$i -lt 10 ]; do printf '\nline-%d' \$i; i=\$((i + 1)); done + sleep 10" + +sleep 1 +[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0 + +# Images scroll as grid cells, while capture output contains ordinary spaces. +[ "$($TMUX display-message -p '#{history_size}')" -gt 0 ] || exit 1 +$TMUX capture-pane -pS- >$TMP || exit 1 +grep 'Pq' $TMP >/dev/null && exit 1 + +# Copy mode duplicates the backing grid, including image marker references. +$TMUX copy-mode || exit 1 +$TMUX send-keys -X history-top || exit 1 +$TMUX capture-pane -p >$TMP || exit 1 +$TMUX send-keys -X cancel || exit 1 + +# Ordinary text overwrites marker cells through the normal grid write path. +$TMUX new-window -d " + printf '\033Pq\"1;1;8;16#0;2;100;100;100#0~~~~~~~~\044-~~~~~~~~\033\\' + printf '\033[HXY' + sleep 10" +sleep 1 +[ "$($TMUX capture-pane -pt:1 -S0 -E0)" = "XY" ] || exit 1 + +# A client without an image feature gets the brightness-based text backend. +$TMUX kill-server 2>/dev/null +$TMUX2 new-session -d -x 10 -y 4 " + printf '\033Pq\"1;1;8;16#0;2;100;100;100#0~~~~~~~~\044-~~~~~~~~\033\\' + sleep 10" || exit 1 +$TMUX2 set -g status off || exit 1 +$TMUX new-session -d -x 10 -y 4 || exit 1 +$TMUX set -g status off || exit 1 +$TMUX send-keys -l "$TMUX2 attach-session" || exit 1 +$TMUX send-keys Enter || exit 1 +sleep 1 +$TMUX capture-pane -pS0 -E0 >$TMP || exit 1 +grep -q '[.:-=+*#%@]' $TMP || exit 1 + +# Selection redraws must leave text image cells visible. +$TMUX2 copy-mode || exit 1 +$TMUX2 send-keys -X history-top || exit 1 +$TMUX2 send-keys -X start-of-line || exit 1 +$TMUX2 send-keys -X begin-selection || exit 1 +sleep 1 +$TMUX capture-pane -pS0 -E0 >$TMP || exit 1 +grep -q '[.:-=+*#%@]' $TMP || exit 1 + +exit 0 diff --git a/screen-redraw.c b/screen-redraw.c index 7ea30a8a4..2f4e78283 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1125,6 +1125,9 @@ 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); +#endif } /* Get default border style for spans without a pane. */ @@ -1817,15 +1820,6 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags) tty_reset(tty); tty_sync_end(tty); -#ifdef ENABLE_SIXEL - if (wp != NULL) - tty_draw_images(c, wp); - else { - TAILQ_FOREACH(loop, &scene->w->panes, entry) - tty_draw_images(c, loop); - } -#endif - log_debug("%s: finished @%u redraw", c->name, scene->w->id); } diff --git a/screen-write.c b/screen-write.c index 9d303a551..d574af323 100644 --- a/screen-write.c +++ b/screen-write.c @@ -1308,9 +1308,8 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx) memcpy(&gc, &grid_default_cell, sizeof gc); utf8_set(&gc.data, 'E'); -#ifdef ENABLE_SIXEL - if (image_free_all(s) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_all(ctx); #endif for (yy = 0; yy < screen_size_y(s); yy++) { @@ -1355,9 +1354,8 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) if (s->cx > screen_size_x(s) - 1) return; -#ifdef ENABLE_SIXEL - if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1); #endif screen_write_initctx(ctx, &ttyctx, 0, 1); @@ -1396,9 +1394,8 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) if (s->cx > screen_size_x(s) - 1) return; -#ifdef ENABLE_SIXEL - if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1); #endif screen_write_initctx(ctx, &ttyctx, 0, 1); @@ -1437,9 +1434,8 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg) if (s->cx > screen_size_x(s) - 1) return; -#ifdef ENABLE_SIXEL - if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1); #endif screen_write_initctx(ctx, &ttyctx, 0, 1); @@ -1472,9 +1468,8 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) ny = 1; -#ifdef ENABLE_SIXEL - if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy); #endif if (s->cy < s->rupper || s->cy > s->rlower) { @@ -1540,9 +1535,8 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg) if (ny == 0) ny = 1; -#ifdef ENABLE_SIXEL - if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy); #endif if (s->cy < s->rupper || s->cy > s->rlower) { @@ -1613,9 +1607,8 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg) if (gl->cellsize == 0 && COLOUR_DEFAULT(bg)) return; -#ifdef ENABLE_SIXEL - if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1); #endif flags = gl->flags & GRID_LINE_OSC133_FLAGS; @@ -1652,9 +1645,8 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg) if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg))) return; -#ifdef ENABLE_SIXEL - if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1); #endif grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg); @@ -1679,9 +1671,8 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg) return; } -#ifdef ENABLE_SIXEL - if (image_check_line(s, s->cy, 1) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1); #endif if (s->cx > sx - 1) @@ -1733,9 +1724,8 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg) return; } -#ifdef ENABLE_SIXEL - if (image_free_all(s) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_all(ctx); #endif grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg); @@ -1785,9 +1775,6 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg) struct screen *s = ctx->s; struct grid *gd = s->grid; struct grid_line *gl; -#ifdef ENABLE_SIXEL - int redraw = 0; -#endif u_int rupper = s->rupper, rlower = s->rlower; gl = grid_get_line(gd, gd->hsize + s->cy); @@ -1808,13 +1795,12 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg) return; } -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES if (rlower == screen_size_y(s) - 1) - redraw = image_scroll_up(s, 1); + image_damage_scroll(ctx, 1); else - redraw = image_check_line(s, rupper, rlower - rupper); - if (redraw && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; + image_damage_area(ctx, 0, rupper, screen_size_x(s), + rlower - rupper); #endif grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg); @@ -1840,9 +1826,8 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg) ctx->bg = bg; } -#ifdef ENABLE_SIXEL - if (image_scroll_up(s, lines) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_scroll(ctx, lines); #endif for (i = 0; i < lines; i++) { @@ -1869,9 +1854,8 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg) else if (lines > s->rlower - s->rupper + 1) lines = s->rlower - s->rupper + 1; -#ifdef ENABLE_SIXEL - if (image_free_all(s) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_all(ctx); #endif for (i = 0; i < lines; i++) @@ -1910,9 +1894,8 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg) struct visible_ranges *r; struct visible_range *ri; -#ifdef ENABLE_SIXEL - if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy); #endif screen_write_initctx(ctx, &ttyctx, 1, 1); @@ -1991,9 +1974,8 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg) struct visible_ranges *r; struct visible_range *ri; -#ifdef ENABLE_SIXEL - if (image_check_line(s, 0, s->cy - 1) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_area(ctx, 0, 0, screen_size_x(s), s->cy + 1); #endif screen_write_initctx(ctx, &ttyctx, 1, 1); @@ -2064,9 +2046,8 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg) struct visible_ranges *r; struct visible_range *ri; -#ifdef ENABLE_SIXEL - if (image_free_all(s) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_all(ctx); #endif screen_write_initctx(ctx, &ttyctx, 1, 1); @@ -2531,9 +2512,8 @@ screen_write_collect_end(struct screen_write_ctx *ctx) } } -#ifdef ENABLE_SIXEL - if (image_check_area(s, s->cx, s->cy, ci->used, 1) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; +#ifdef ENABLE_IMAGES + image_damage_area(ctx, s->cx, s->cy, ci->used, 1); #endif grid_view_set_cells(s->grid, s->cx, s->cy, &ci->gc, cl->data + ci->x, @@ -3046,57 +3026,14 @@ void screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si, u_int bg) { - struct screen *s = ctx->s; - struct grid *gd = s->grid; - struct tty_ctx ttyctx; - u_int x, y, sx, sy, cx = s->cx, cy = s->cy, i, lines; - struct sixel_image *new; + struct image *im; - if (screen_size_y(s) == 1) + im = sixel_to_image(si); + sixel_free(si); + if (im == NULL) return; - - sixel_size_in_cells(si, &x, &y); - if (x > screen_size_x(s) || y > screen_size_y(s) - 1) { - if (x > screen_size_x(s) - cx) - sx = screen_size_x(s) - cx; - else - sx = x; - if (y > screen_size_y(s) - 1) - sy = screen_size_y(s) - 1; - else - sy = y; - new = sixel_scale(si, 0, 0, 0, y - sy, sx, sy, 1); - sixel_free(si); - if (new == NULL) - return; - sixel_size_in_cells(new, &x, &y); - si = new; - } - - sy = screen_size_y(s) - cy; - if (sy <= y) { - lines = y - sy + 1; - if (image_scroll_up(s, lines) && ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; - for (i = 0; i < lines; i++) { - grid_view_scroll_region_up(gd, 0, screen_size_y(s) - 1, - bg); - screen_write_collect_scroll(ctx, bg); - } - ctx->scrolled += lines; - if (lines > cy) - screen_write_cursormove(ctx, -1, 0, 0); - else - screen_write_cursormove(ctx, -1, cy - lines, 0); - } - screen_write_collect_flush(ctx, 0, __func__); - - screen_write_initctx(ctx, &ttyctx, 0, 0); - ttyctx.image = image_store(s, si); - - tty_write(tty_cmd_sixelimage, &ttyctx); - - screen_write_cursormove(ctx, 0, cy + y, 0); + image_write(ctx, im, bg); + image_free(im->id); } #endif diff --git a/screen.c b/screen.c index f9ca89af5..a06f1e26c 100644 --- a/screen.c +++ b/screen.c @@ -93,11 +93,6 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) s->tabs = NULL; s->sel = NULL; -#ifdef ENABLE_SIXEL - TAILQ_INIT(&s->images); - TAILQ_INIT(&s->saved_images); -#endif - s->write_list = NULL; s->hyperlinks = NULL; @@ -131,11 +126,6 @@ screen_reinit(struct screen *s, int check) screen_clear_selection(s); screen_free_titles(s); - -#ifdef ENABLE_SIXEL - image_free_all(s); -#endif - screen_set_progress_bar(s, PROGRESS_BAR_HIDDEN, 0); screen_reset_hyperlinks(s); } @@ -154,10 +144,6 @@ screen_reset_hyperlinks(struct screen *s) void screen_free(struct screen *s) { -#ifdef ENABLE_SIXEL - struct image *im; -#endif - free(s->sel); free(s->tabs); free(s->path); @@ -174,17 +160,6 @@ screen_free(struct screen *s) hyperlinks_free(s->hyperlinks); screen_free_titles(s); -#ifdef ENABLE_SIXEL - /* - * Images saved when entering the alternate screen stay linked in the - * global list; move them back so they are freed and unlinked here, or - * a later eviction would write through this freed screen. - */ - TAILQ_CONCAT(&s->images, &s->saved_images, entry); - TAILQ_FOREACH(im, &s->images, entry) - im->list = &s->images; - image_free_all(s); -#endif } /* Reset tabs to default, eight spaces apart. */ @@ -377,10 +352,6 @@ screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow, if (sy != screen_size_y(s)) screen_resize_y(s, sy, eat_empty, &cy); -#ifdef ENABLE_SIXEL - image_free_all(s); -#endif - if (reflow) screen_reflow(s, sx, &cx, &cy, cursor); @@ -692,10 +663,6 @@ int screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor) { u_int sx, sy; -#ifdef ENABLE_SIXEL - struct image *im; -#endif - if (SCREEN_IS_ALTERNATE(s)) return 0; sx = screen_size_x(s); @@ -709,12 +676,6 @@ screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor) } memcpy(&s->saved_cell, gc, sizeof s->saved_cell); -#ifdef ENABLE_SIXEL - TAILQ_CONCAT(&s->saved_images, &s->images, entry); - TAILQ_FOREACH(im, &s->saved_images, entry) - im->list = &s->saved_images; -#endif - grid_view_clear(s->grid, 0, 0, sx, sy, 8); s->saved_flags = s->grid->flags; @@ -728,10 +689,6 @@ int screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) { u_int sx = screen_size_x(s), sy = screen_size_y(s); -#ifdef ENABLE_SIXEL - struct image *im; -#endif - /* * If the current size is different, temporarily resize to the old size * before copying back. @@ -774,13 +731,6 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor) grid_destroy(s->saved_grid); s->saved_grid = NULL; -#ifdef ENABLE_SIXEL - image_free_all(s); - TAILQ_CONCAT(&s->images, &s->saved_images, entry); - TAILQ_FOREACH(im, &s->images, entry) - im->list = &s->images; -#endif - if (s->cx > screen_size_x(s) - 1) s->cx = screen_size_x(s) - 1; if (s->cy > screen_size_y(s) - 1) diff --git a/style.c b/style.c index 55d08fe75..ed79f99c5 100644 --- a/style.c +++ b/style.c @@ -30,7 +30,11 @@ /* Default style. */ static struct style style_default = { - { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0 }, + { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0 +#ifdef ENABLE_IMAGES + , 0, 0, 0 +#endif + }, 0, 0, diff --git a/tmux.h b/tmux.h index 1cceb748c..9e76fce21 100644 --- a/tmux.h +++ b/tmux.h @@ -75,6 +75,11 @@ struct screen_write_cline; struct screen_write_ctx; struct session; +#ifdef ENABLE_IMAGES +struct image; +struct image_backend; +struct image_rectangle; +#endif #ifdef ENABLE_SIXEL struct sixel_image; #endif @@ -803,6 +808,7 @@ struct colour_palette { #define GRID_FLAG_NOPALETTE 0x20 #define GRID_FLAG_CLEARED 0x40 #define GRID_FLAG_TAB 0x80 +#define GRID_FLAG_IMAGE 0x100 /* Grid line flags. */ #define GRID_LINE_WRAPPED 0x1 @@ -858,22 +864,32 @@ struct colour_palette { struct grid_cell { struct utf8_data data; u_short attr; - u_char flags; + u_short flags; int fg; int bg; int us; u_int link; +#ifdef ENABLE_IMAGES + u_int image_id; + u_int image_x; + u_int image_y; +#endif }; /* Grid extended cell entry. */ struct grid_extd_entry { utf8_char data; u_short attr; - u_char flags; + u_short flags; int fg; int bg; int us; u_int link; +#ifdef ENABLE_IMAGES + u_int image_id; + u_int image_x; + u_int image_y; +#endif } __packed; /* Grid cell entry. */ @@ -887,7 +903,7 @@ struct grid_cell_entry { u_char data; } data; }; - u_char flags; + u_short flags; } __packed; /* OSC 133 data for a grid line. */ @@ -1021,24 +1037,54 @@ struct style { u_int link; }; -#ifdef ENABLE_SIXEL -/* Image. */ -struct image { - struct screen *s; - struct sixel_image *data; - char *fallback; +#ifdef ENABLE_IMAGES +/* A protocol-neutral average of part of an image cell. RGB is premultiplied. */ +struct image_sample { + u_char red; + u_char green; + u_char blue; + u_char alpha; + u_char brightness; +}; - u_int px; - u_int py; +/* Half blocks, quadrants and sextants all divide evenly into a 2 by 6 grid. */ +#define IMAGE_SAMPLE_COLUMNS 2 +#define IMAGE_SAMPLE_ROWS 6 +struct image_cell { + struct image_sample whole; + struct image_sample samples[IMAGE_SAMPLE_ROWS][IMAGE_SAMPLE_COLUMNS]; +}; + +/* Immutable protocol-neutral image placement. */ +struct image { + u_int id; + u_int references; + u_int width; + u_int height; u_int sx; u_int sy; + size_t stride; + size_t size; + u_char *pixels; + struct image_cell *cells; /* lazily generated text samples */ - struct images *list; - TAILQ_ENTRY (image) entry; - - TAILQ_ENTRY (image) all_entry; + RB_ENTRY(image) entry; }; -TAILQ_HEAD(images, image); +RB_HEAD(images, image); + +/* A cell-aligned part of an image to draw at a terminal position. */ +struct image_rectangle { + struct image *image; + struct grid_cell cell; + u_int source_x; + u_int source_y; + u_int width; + u_int height; + u_int destination_x; + u_int destination_y; +}; + +#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024) #endif /* Cursor style. */ @@ -1097,11 +1143,6 @@ struct screen { bitstr_t *tabs; struct screen_sel *sel; -#ifdef ENABLE_SIXEL - struct images images; - struct images saved_images; -#endif - struct screen_write_cline *write_list; struct hyperlinks *hyperlinks; @@ -1832,6 +1873,9 @@ struct tty { struct event key_timer; struct tty_key *key_tree; +#ifdef ENABLE_IMAGES + const struct image_backend *image_backend; +#endif }; /* Terminal command context. */ @@ -2984,10 +3028,6 @@ void tty_cmd_scrolldown(struct tty *, const struct tty_ctx *); void tty_cmd_reverseindex(struct tty *, const struct tty_ctx *); void tty_cmd_setselection(struct tty *, const struct tty_ctx *); void tty_cmd_rawstring(struct tty *, const struct tty_ctx *); -#ifdef ENABLE_SIXEL -void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *); -void tty_draw_images(struct client *, struct window_pane *); -#endif void tty_cmd_syncstart(struct tty *, const struct tty_ctx *); void tty_default_colours(struct grid_cell *, struct window_pane *, u_int *); @@ -4211,14 +4251,38 @@ void spawn_editor_finish(struct window_pane *); /* regsub.c */ char *regsub(const char *, const char *, const char *, int); -#ifdef ENABLE_SIXEL +#ifdef ENABLE_IMAGES /* image.c */ -int image_free_all(struct screen *); -struct image *image_store(struct screen *, struct sixel_image *); -int image_check_line(struct screen *, u_int, u_int); -int image_check_area(struct screen *, u_int, u_int, u_int, u_int); -int image_scroll_up(struct screen *, u_int); +struct image *image_create(u_int, u_int, u_int, u_int, u_char *); +struct image *image_find(u_int); +void image_ref(u_int); +void image_free(u_int); +void image_set_cell(struct grid_cell *, struct image *, u_int, + u_int); +void image_write(struct screen_write_ctx *, struct image *, u_int); +void image_get_pixel_rectangle(const struct image *, u_int, u_int, + u_int, u_int, u_int *, u_int *, u_int *, u_int *); +void image_size_in_cells(u_int, u_int, u_int, u_int, u_int *, + u_int *); +void image_damage_area(struct screen_write_ctx *, u_int, u_int, + u_int, u_int); +void image_damage_all(struct screen_write_ctx *); +void image_damage_scroll(struct screen_write_ctx *, u_int); +int image_tty_is_graphical(struct tty *); +int image_tty_scrolls(struct tty *); +void image_tty_update(struct tty *); +void image_tty_geometry_changed(struct tty *); +void image_draw_line(struct tty *, struct screen *, u_int, u_int, + u_int, u_int, u_int, const struct tty_style_ctx *); +const struct image_cell *image_get_cell(struct image *, u_int, u_int); +void image_get_text_cell(struct tty *, struct image *, u_int, + u_int, const struct grid_cell *, struct grid_cell *, + const struct tty_style_ctx *); +void sixel_draw_rectangle(struct tty *, + const struct image_rectangle *, const struct tty_style_ctx *); +#endif +#ifdef ENABLE_SIXEL /* image-sixel.c */ #define SIXEL_COLOUR_REGISTERS 1024 struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int); @@ -4230,6 +4294,7 @@ struct sixel_image *sixel_scale(struct sixel_image *, u_int, u_int, u_int, char *sixel_print(struct sixel_image *, struct sixel_image *, size_t *); struct screen *sixel_to_screen(struct sixel_image *); +struct image *sixel_to_image(struct sixel_image *); #endif /* server-acl.c */ diff --git a/tty-draw.c b/tty-draw.c index 06d520e11..51d2d7dcd 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -29,6 +29,7 @@ enum tty_draw_line_state { TTY_DRAW_LINE_NEW1, TTY_DRAW_LINE_NEW2, TTY_DRAW_LINE_EMPTY, + TTY_DRAW_LINE_IMAGE, TTY_DRAW_LINE_SAME, TTY_DRAW_LINE_DONE }; @@ -38,6 +39,7 @@ static const char* tty_draw_line_states[] = { "NEW1", "NEW2", "EMPTY", + "IMAGE", "SAME", "DONE" }; @@ -123,10 +125,14 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, struct grid *gd = s->grid; const struct grid_cell *gcp; struct grid_cell gc, ngc, last; +#ifdef ENABLE_IMAGES + struct grid_cell image_gc; + struct image *im; +#endif struct grid_line *gl; u_int i, j, last_i, cx, ex, width; u_int cellsize, bg; - int flags, empty, wrapped = 0; + int flags, empty, skip, wrapped = 0; char buf[1000]; size_t len; enum tty_draw_line_state current_state, next_state; @@ -231,6 +237,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, width = 0; current_state = TTY_DRAW_LINE_FIRST; for (;;) { + skip = 0; + /* Work out the next state. */ if (i == nx) { /* @@ -253,14 +261,43 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, /* Get the current cell. */ grid_view_get_cell(gd, px + i, py, &gc); - /* Work out empty cells. */ - empty = tty_draw_line_get_empty(&gc, &last, - nx - i); - if (empty != 0) +#ifdef ENABLE_IMAGES + /* Text terminals render image blocks as ASCII. */ + if (gc.flags & GRID_FLAG_IMAGE) { + im = image_find(gc.image_id); + if (image_tty_is_graphical(tty)) { + memcpy(&image_gc, &gc, + sizeof image_gc); + utf8_set(&image_gc.data, ' '); + image_gc.flags &= + ~GRID_FLAG_IMAGE; + skip = 1; + } else { + image_get_text_cell(tty, im, + gc.image_x, gc.image_y, &gc, + &image_gc, style_ctx); + } + if (skip) + image_gc.flags &= + ~GRID_FLAG_SELECTED; + gcp = &image_gc; + } else gcp = &gc; +#else + gcp = &gc; +#endif + + /* Work out empty cells. */ + if (skip) + empty = 0; + else + empty = tty_draw_line_get_empty(gcp, + &last, nx - i); + if (empty != 0 || skip) + ; else { /* Update for codeset if needed. */ - gcp = tty_check_codeset(tty, &gc); + gcp = tty_check_codeset(tty, gcp); /* And for selection. */ if (gcp->flags & GRID_FLAG_SELECTED) { @@ -273,7 +310,9 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, } /* Work out the next state. */ - if (empty != 0) + if (skip) + next_state = TTY_DRAW_LINE_IMAGE; + else if (empty != 0) next_state = TTY_DRAW_LINE_EMPTY; else if (current_state == TTY_DRAW_LINE_FIRST) next_state = TTY_DRAW_LINE_SAME; @@ -320,7 +359,8 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, } /* Append the cell if it is not empty and not padding. */ - if (next_state != TTY_DRAW_LINE_EMPTY) { + if (next_state != TTY_DRAW_LINE_EMPTY && + next_state != TTY_DRAW_LINE_IMAGE) { memcpy(buf + len, gcp->data.data, gcp->data.size); len += gcp->data.size; width += gcp->data.width; diff --git a/tty-features.c b/tty-features.c index 656100ba9..fa9d5ccb5 100644 --- a/tty-features.c +++ b/tty-features.c @@ -224,9 +224,11 @@ static const struct tty_feature tty_feature_strikethrough = { 0 }; +#define TTY_FEATURE_SYNC "Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;" + /* Terminal supports synchronized updates. */ static const char *const tty_feature_sync_capabilities[] = { - "Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;", + TTY_FEATURE_SYNC, NULL }; static const struct tty_feature tty_feature_sync = { @@ -349,6 +351,7 @@ static const struct tty_feature tty_feature_ignorefkeys = { /* Terminal has sixel capability. */ static const char *const tty_feature_sixel_capabilities[] = { "Sxl", + TTY_FEATURE_SYNC, NULL }; static const struct tty_feature tty_feature_sixel = { @@ -581,6 +584,7 @@ tty_default_features(int *feat, const char *name, u_int version) "extkeys," "focus," "hyperlinks," + "sixel," "usstyle" }, { .name = "ghostty", diff --git a/tty.c b/tty.c index c5b443cee..9ae63d0e5 100644 --- a/tty.c +++ b/tty.c @@ -68,11 +68,6 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code, static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int); static int tty_check_overlay(struct tty *, u_int, u_int); -#ifdef ENABLE_SIXEL -static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *), - struct client *, struct tty_ctx *); -#endif - #define tty_use_margin(tty) \ (tty->term->flags & TERM_DECSLRM) #define tty_full_width(tty, ctx) \ @@ -163,10 +158,21 @@ tty_resize(struct tty *tty) void tty_set_size(struct tty *tty, u_int sx, u_int sy, u_int xpixel, u_int ypixel) { +#ifdef ENABLE_IMAGES + int geometry_changed; + + geometry_changed = (tty->xpixel != xpixel || tty->ypixel != ypixel); +#endif tty->sx = sx; tty->sy = sy; tty->xpixel = xpixel; tty->ypixel = ypixel; +#ifdef ENABLE_IMAGES + if (geometry_changed) { + image_tty_geometry_changed(tty); + server_redraw_client(tty->client); + } +#endif } static void @@ -527,7 +533,6 @@ void tty_free(struct tty *tty) { tty_close(tty); - free(tty->r.ranges); } @@ -538,6 +543,9 @@ tty_update_features(struct tty *tty) if (tty_apply_features(tty->term, c->term_features)) tty_term_apply_overrides(tty->term); +#ifdef ENABLE_IMAGES + image_tty_update(tty); +#endif if (tty_use_margin(tty)) tty_putcode(tty, TTYC_ENMG); @@ -1522,60 +1530,6 @@ tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx) return (c->overlay_check(c, c->overlay_data, px, py, nx)); } -#ifdef ENABLE_SIXEL -/* Update context for client. */ -static int -tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c) -{ - struct window_pane *wp = ttyctx->arg; - - if (c->session->curw->window != wp->window) - return (0); - if (wp->layout_cell == NULL) - return (0); - - if (tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, &ttyctx->wsx, - &ttyctx->wsy)) - ttyctx->flags |= TTY_CTX_WINDOW_BIGGER; - else - ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER; - - ttyctx->yoff = ttyctx->ryoff = wp->yoff; - if (status_at_line(c) == 0) - ttyctx->yoff += status_line_size(c); - - return (1); -} - -void -tty_draw_images(struct client *c, struct window_pane *wp) -{ - struct image *im; - struct tty_ctx ttyctx; - - TAILQ_FOREACH(im, &wp->screen->images, entry) { - memset(&ttyctx, 0, sizeof ttyctx); - - /* Set the client independent properties. */ - ttyctx.ocx = im->px; - ttyctx.ocy = im->py; - - ttyctx.orlower = wp->screen->rlower; - ttyctx.orupper = wp->screen->rupper; - - ttyctx.xoff = ttyctx.rxoff = wp->xoff; - ttyctx.sx = wp->sx; - ttyctx.sy = wp->sy; - - ttyctx.image = im; - ttyctx.arg = wp; - ttyctx.set_client_cb = tty_set_client_cb; - ttyctx.flags |= TTY_CTX_INVISIBLE_PANES; - tty_write_one(tty_cmd_sixelimage, c, &ttyctx); - } -} -#endif - void tty_sync_start(struct tty *tty) { @@ -1649,19 +1603,6 @@ tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *), } } -#ifdef ENABLE_SIXEL -/* Only write to the incoming tty instead of every client. */ -static void -tty_write_one(void (*cmdfn)(struct tty *, const struct tty_ctx *), - struct client *c, struct tty_ctx *ctx) -{ - if (ctx->set_client_cb == NULL) - return; - if ((ctx->set_client_cb(ctx, c)) == 1) - cmdfn(&c->tty, ctx); -} -#endif - void tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx) { @@ -2155,58 +2096,6 @@ tty_cmd_rawstring(struct tty *tty, const struct tty_ctx *ctx) tty_invalidate(tty); } -#ifdef ENABLE_SIXEL -void -tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx) -{ - struct image *im = ctx->image; - struct sixel_image *si = im->data; - struct sixel_image *new; - char *data; - size_t size; - u_int cx = ctx->ocx, cy = ctx->ocy, sx, sy; - u_int i, j, x, y, rx, ry; - int fallback = 0; - - if ((~tty->term->flags & TERM_SIXEL) && - !tty_term_has(tty->term, TTYC_SXL)) - fallback = 1; - if (tty->xpixel == 0 || tty->ypixel == 0) - fallback = 1; - - sixel_size_in_cells(si, &sx, &sy); - log_debug("%s: image is %ux%u", __func__, sx, sy); - if (!tty_clamp_area(tty, ctx, cx, cy, sx, sy, &i, &j, &x, &y, &rx, &ry)) - return; - log_debug("%s: clamping to %u,%u-%u,%u", __func__, i, j, rx, ry); - - if (fallback == 1) { - data = xstrdup(im->fallback); - size = strlen(data); - } else { - new = sixel_scale(si, tty->xpixel, tty->ypixel, i, j, rx, ry, 0); - if (new == NULL) - return; - - data = sixel_print(new, si, &size); - } - if (data != NULL) { - log_debug("%s: %zu bytes: %s", __func__, size, data); - tty_region_off(tty); - tty_margin_off(tty); - tty_cursor(tty, x, y); - - tty->flags |= TTY_NOBLOCK; - tty_add(tty, data, size); - tty_invalidate(tty); - free(data); - } - - if (fallback == 0) - sixel_free(new); -} -#endif - void tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx) { @@ -2234,6 +2123,10 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, const struct tty_style_ctx *style_ctx) { const struct grid_cell *gcp; +#ifdef ENABLE_IMAGES + struct grid_cell image_gc; + struct image *im; +#endif /* Skip last character if terminal is stupid. */ if ((tty->term->flags & TERM_NOAM) && @@ -2249,6 +2142,22 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, if (!tty_check_overlay(tty, tty->cx, tty->cy)) return; +#ifdef ENABLE_IMAGES + /* + * Graphical image cells are drawn by the redraw scene. In particular, + * do not erase one with a selected space while copy mode is moving the + * selection; the unchanged image would then need to be drawn again. + */ + if (gc->flags & GRID_FLAG_IMAGE) { + if (image_tty_is_graphical(tty)) + return; + im = image_find(gc->image_id); + image_get_text_cell(tty, im, gc->image_x, gc->image_y, gc, + &image_gc, style_ctx); + gc = &image_gc; + } +#endif + /* Check the output codeset and apply attributes. */ gcp = tty_check_codeset(tty, gc); tty_attributes(tty, gcp, style_ctx); diff --git a/window-copy.c b/window-copy.c index dabb2a99c..805123dd4 100644 --- a/window-copy.c +++ b/window-copy.c @@ -6151,7 +6151,9 @@ window_copy_copy_line(struct window_mode_entry *wme, char **buf, size_t *off, grid_get_cell(gd, i, sy, &gc); if (gc.flags & GRID_FLAG_PADDING) continue; - if (gc.flags & GRID_FLAG_TAB) + if (gc.flags & GRID_FLAG_IMAGE) + utf8_set(&ud, ' '); + else if (gc.flags & GRID_FLAG_TAB) utf8_set(&ud, '\t'); else utf8_copy(&ud, &gc.data);