From fe938ea657cb78cf9f545eecfbb5b05058c8cb7b Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Mon, 3 Aug 2026 09:13:45 +0100 Subject: [PATCH] Refactor to centralize image-specific rendering and lifecycle code in image.c and the image-* backends. --- .gitignore | 1 + Makefile.am | 6 +- image-ascii.c | 56 ++---- image-kitty.c | 227 +++++++++-------------- image-sixel.c | 96 ++++------ image.c | 381 ++++++++++++++++++++++++++++++++++++--- input.c | 4 +- regress/image-support.sh | 15 +- screen-redraw.c | 6 +- screen-write.c | 92 ++++------ screen.c | 5 - tmux.h | 81 +++++++-- tty-draw.c | 23 ++- tty.c | 30 +-- 14 files changed, 643 insertions(+), 380 deletions(-) diff --git a/.gitignore b/.gitignore index 31ca7add0..722a4dd43 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ regress/logs/ tags tmux tmux.1.* +tools/image-ascii-test diff --git a/Makefile.am b/Makefile.am index 7c3004d45..a73ab1fc6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5,7 +5,8 @@ CLEANFILES = tmux.1.mdoc tmux.1.man cmd-parse.c # Distribution tarball options. EXTRA_DIST = \ CHANGES README COPYING example_tmux.conf \ - osdep-*.c mdoc2man.awk tmux.1 + osdep-*.c mdoc2man.awk tmux.1 \ + tools/generate-image-diacritics.sh tools/image-ascii-test.c dist_EXTRA_tmux_SOURCES = compat/*.[ch] # Preprocessor flags. @@ -248,8 +249,7 @@ endif # Enable image support. if ENABLE_IMAGES -dist_tmux_SOURCES += image.c image-sixel.c image-kitty.c image-ascii.c \ - image-diacritics.h +dist_tmux_SOURCES += image.c image-sixel.c image-kitty.c image-ascii.c endif # Add bits for fuzzing if enabled. diff --git a/image-ascii.c b/image-ascii.c index ecc61fc8e..d1598a26f 100644 --- a/image-ascii.c +++ b/image-ascii.c @@ -18,51 +18,23 @@ #include +#include + #include "tmux.h" void -image_make_ascii(struct image *im) +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 u_char *pixel; - uint64_t total, count; - u_int x, y, px, py, x0, x1, y0, y1, level; + static const char ramp[] = " .:-=+*#%@"; + const struct image_cell *cell; + u_int level = 0; - im->ascii = xcalloc((size_t)im->sx * im->sy, 1); - for (y = 0; y < im->sy; y++) { - y0 = ((uint64_t)y * im->height) / im->sy; - y1 = ((uint64_t)(y + 1) * im->height) / im->sy; - if (y1 <= y0) - y1 = y0 + 1; - for (x = 0; x < im->sx; x++) { - x0 = ((uint64_t)x * im->width) / im->sx; - x1 = ((uint64_t)(x + 1) * im->width) / im->sx; - if (x1 <= x0) - x1 = x0 + 1; - - total = count = 0; - for (py = y0; py < y1 && py < im->height; py++) { - for (px = x0; px < x1 && px < im->width; px++) { - pixel = im->pixels + py * im->stride + px * 4; - total += ((2126ULL * pixel[0] + - 7152ULL * pixel[1] + - 722ULL * pixel[2]) / 10000) * - pixel[3] / 255; - count++; - } - } - if (count == 0) - continue; - level = (total / count) * (sizeof ramp - 2) / 255; - im->ascii[y * im->sx + x] = ramp[level]; - } - } -} - -const char * -image_get_ascii(struct image *im, u_int x, u_int y) -{ - if (im == NULL || x >= im->sx || y >= im->sy) - return (" "); - return (&im->ascii[y * im->sx + x]); + 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-kitty.c b/image-kitty.c index 3e4d7b452..f78ee943a 100644 --- a/image-kitty.c +++ b/image-kitty.c @@ -18,7 +18,6 @@ #include #include -#include #include #include #include @@ -26,7 +25,7 @@ #include "tmux.h" -/* Generated from Kitty's Unicode 6.0 rowcolumn-diacritics.txt. */ +/* Generated from Unicode 6.0.0 by tools/generate-image-diacritics.sh. */ static const uint32_t kitty_diacritics[] = { 0x0305, 0x030D, 0x030E, 0x0310, 0x0312, 0x033D, 0x033E, 0x033F, 0x0346, 0x034A, 0x034B, 0x034C, 0x0350, 0x0351, 0x0352, 0x0357, @@ -68,8 +67,6 @@ static const uint32_t kitty_diacritics[] = { 0x1D244 }; -#define KITTY_IMAGE_LIMIT (64 * 1024 * 1024) - struct kitty_state { char action; char delete; @@ -100,14 +97,32 @@ struct kitty_context { struct kitty_source *sources; }; -struct tty_image_cache { +struct kitty_image_cache { u_int server_id; u_int kitty_id; u_int xpixel; u_int ypixel; - struct tty_image_cache *next; + struct kitty_image_cache *next; }; +struct kitty_output { + struct kitty_image_cache *images; + u_int next_id; +}; + +static struct kitty_output * +kitty_get_output(struct tty *tty) +{ + struct kitty_output *ko = tty->image_data; + + if (ko == NULL) { + ko = xcalloc(1, sizeof *ko); + ko->next_id = arc4random_uniform(0xffff00) + 1; + tty->image_data = ko; + } + return (ko); +} + static size_t kitty_utf8(char *out, uint32_t value) { @@ -143,25 +158,38 @@ kitty_delete(struct tty *tty, u_int id) } void -kitty_images_free(struct tty *tty, int send) +kitty_free_output(struct tty *tty, int send) { - struct tty_image_cache *cache, *next; + struct kitty_output *ko = tty->image_data; + struct kitty_image_cache *cache, *next; - for (cache = tty->images; cache != NULL; cache = next) { + if (ko == NULL) + return; + for (cache = ko->images; cache != NULL; cache = next) { next = cache->next; if (send) kitty_delete(tty, cache->kitty_id); free(cache); } - tty->images = NULL; + free(ko); + tty->image_data = NULL; +} + +void +kitty_geometry_changed(struct tty *tty) +{ + kitty_free_output(tty, !!(tty->flags & TTY_OPENED)); } static void kitty_images_collect(struct tty *tty) { - struct tty_image_cache **pp, *cache; + struct kitty_output *ko = tty->image_data; + struct kitty_image_cache **pp, *cache; - for (pp = &tty->images; (cache = *pp) != NULL; ) { + if (ko == NULL) + return; + for (pp = &ko->images; (cache = *pp) != NULL; ) { if (image_find(cache->server_id) != NULL) { pp = &cache->next; continue; @@ -183,23 +211,12 @@ kitty_place(struct tty *tty, struct image *im, u_int id) rows = im->sy - y; if (rows > nitems(kitty_diacritics)) rows = nitems(kitty_diacritics); - py = (uint64_t)y * im->height / im->sy; - pheight = (uint64_t)(y + rows) * im->height / im->sy - py; - if (pheight == 0) { - py = im->height - 1; - pheight = 1; - } for (x = 0; x < im->sx; x += nitems(kitty_diacritics)) { columns = im->sx - x; if (columns > nitems(kitty_diacritics)) columns = nitems(kitty_diacritics); - px = (uint64_t)x * im->width / im->sx; - pwidth = (uint64_t)(x + columns) * im->width / - im->sx - px; - if (pwidth == 0) { - px = im->width - 1; - pwidth = 1; - } + image_get_pixel_rectangle(im, x, y, columns, rows, &px, + &py, &pwidth, &pheight); xsnprintf(control, sizeof control, "\033_Ga=p,U=1,i=%u,p=%u,x=%u,y=%u,w=%u,h=%u," "c=%u,r=%u,q=2\033\\", id, placement++, px, py, @@ -209,16 +226,17 @@ kitty_place(struct tty *tty, struct image *im, u_int id) } } -static struct tty_image_cache * +static struct kitty_image_cache * kitty_upload(struct tty *tty, struct image *im) { - struct tty_image_cache *cache; + struct kitty_output *ko = kitty_get_output(tty); + struct kitty_image_cache *cache; char control[128], encoded[4097]; size_t offset, size; int encodedlen; u_int id; - for (cache = tty->images; cache != NULL; cache = cache->next) { + for (cache = ko->images; cache != NULL; cache = cache->next) { if (cache->server_id != im->id) continue; if (cache->xpixel == tty->xpixel && @@ -230,11 +248,11 @@ kitty_upload(struct tty *tty, struct image *im) } if (cache == NULL) { cache = xcalloc(1, sizeof *cache); - cache->next = tty->images; - tty->images = cache; + cache->next = ko->images; + ko->images = cache; } do { - id = ++tty->image_next_id & 0xffffff; + id = ++ko->next_id & 0xffffff; } while (id == 0); cache->server_id = im->id; cache->kitty_id = id; @@ -278,7 +296,7 @@ kitty_placement(struct image *im, u_int x, u_int y) } static void -kitty_placeholder(struct tty *tty, struct tty_image_cache *cache, +kitty_placeholder(struct tty *tty, struct kitty_image_cache *cache, struct image *im, u_int x, u_int y, u_int count) { char buf[8192], sgr[96]; @@ -307,52 +325,38 @@ kitty_placeholder(struct tty *tty, struct tty_image_cache *cache, tty_puts(tty, "\033[39;59m"); } -/* - * Draw only the marker cells present in a scene-selected horizontal span. - * Overlay and pane clipping have already been applied by screen-redraw.c. - */ void -kitty_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) +kitty_draw_rectangle(struct tty *tty, const struct image_rectangle *rectangle, + const struct tty_style_ctx *style_ctx) { - struct grid_cell gc, next, draw_gc; - struct image *im; - struct tty_image_cache *cache; - u_int i, run, tile; + struct grid_cell draw_gc; + struct kitty_image_cache *cache; + struct image *im = rectangle->image; + u_int x, y, run, available; kitty_images_collect(tty); - 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; + cache = kitty_upload(tty, im); + if (cache == NULL) + return; + memcpy(&draw_gc, &rectangle->cell, sizeof draw_gc); + draw_gc.flags &= ~(GRID_FLAG_IMAGE|GRID_FLAG_SELECTED); + utf8_set(&draw_gc.data, ' '); + for (y = 0; y < rectangle->height; y++) { + for (x = 0; x < rectangle->width; x += run) { + available = nitems(kitty_diacritics) - + (rectangle->source_x + x) % + nitems(kitty_diacritics); + run = rectangle->width - x; + if (run > available) + run = available; + tty_cursor(tty, rectangle->destination_x + x, + rectangle->destination_y + y); + tty_attributes(tty, &draw_gc, style_ctx); + kitty_placeholder(tty, cache, im, + rectangle->source_x + x, + rectangle->source_y + y, run); + tty_reset(tty); } - im = image_find(gc.image_id); - if (im == NULL) { - run = 1; - continue; - } - tile = gc.image_x / nitems(kitty_diacritics); - 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 || - next.image_x / nitems(kitty_diacritics) != tile) - break; - } - cache = kitty_upload(tty, im); - if (cache == NULL) - continue; - tty_cursor(tty, atx + i, aty); - memcpy(&draw_gc, &gc, sizeof draw_gc); - draw_gc.flags &= ~(GRID_FLAG_IMAGE|GRID_FLAG_SELECTED); - utf8_set(&draw_gc.data, ' '); - tty_attributes(tty, &draw_gc, style_ctx); - kitty_placeholder(tty, cache, im, gc.image_x, gc.image_y, - run); - tty_reset(tty); } } @@ -535,8 +539,8 @@ kitty_source_get(struct kitty_context *kc, u_int id) static int kitty_append(struct kitty_state *ks, const u_char *buf, size_t len) { - if (len > KITTY_IMAGE_LIMIT || - ks->encodedlen > KITTY_IMAGE_LIMIT - len) + if (len > IMAGE_SIZE_LIMIT || + ks->encodedlen > IMAGE_SIZE_LIMIT - len) return (-1); ks->encoded = xrealloc(ks->encoded, ks->encodedlen + len + 1); memcpy(ks->encoded + ks->encodedlen, buf, len); @@ -545,54 +549,6 @@ kitty_append(struct kitty_state *ks, const u_char *buf, size_t len) return (0); } -static u_char * -kitty_base64(struct kitty_state *ks, size_t *size) -{ - u_char *out; - size_t needed; - int result; - - if (ks->encodedlen > (SIZE_MAX - 3) / 3 * 4) - return (NULL); - needed = (ks->encodedlen + 3) / 4 * 3; - out = xmalloc(needed == 0 ? 1 : needed); - result = b64_pton(ks->encoded, out, needed); - if (result < 0) { - free(out); - return (NULL); - } - *size = result; - return (out); -} - -static u_char * -kitty_png(const u_char *data, size_t size, u_int *width, u_int *height) -{ - png_image pi; - u_char *pixels; - - memset(&pi, 0, sizeof pi); - pi.version = PNG_IMAGE_VERSION; - if (!png_image_begin_read_from_memory(&pi, data, size)) - return (NULL); - pi.format = PNG_FORMAT_RGBA; - if (pi.width == 0 || pi.height == 0 || - PNG_IMAGE_SIZE(pi) > KITTY_IMAGE_LIMIT) { - png_image_free(&pi); - return (NULL); - } - pixels = xmalloc(PNG_IMAGE_SIZE(pi)); - if (!png_image_finish_read(&pi, NULL, pixels, 0, NULL)) { - free(pixels); - png_image_free(&pi); - return (NULL); - } - *width = pi.width; - *height = pi.height; - png_image_free(&pi); - return (pixels); -} - static u_char * kitty_raw(struct kitty_state *ks, u_char *data, size_t size) { @@ -603,7 +559,7 @@ kitty_raw(struct kitty_state *ks, u_char *data, size_t size) bytes = (ks->format == 24 ? 3 : 4); if (ks->width == 0 || ks->height == 0 || - (uint64_t)ks->width * ks->height * bytes > KITTY_IMAGE_LIMIT) + (uint64_t)ks->width * ks->height * bytes > IMAGE_SIZE_LIMIT) return (NULL); expected = (size_t)ks->width * ks->height * bytes; raw = data; @@ -720,13 +676,14 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel, if (ks->action != 'T' && ks->action != 't' && ks->action != 'q') goto fail; - decoded = kitty_base64(ks, &decodedlen); + decoded = image_base64_decode(ks->encoded, ks->encodedlen, + IMAGE_SIZE_LIMIT, &decodedlen); if (decoded == NULL) goto fail; if (ks->format == 100) { if (ks->compression == 'z') { if (ks->data_size == 0 || - ks->data_size > KITTY_IMAGE_LIMIT) { + ks->data_size > IMAGE_SIZE_LIMIT) { free(decoded); goto fail; } @@ -746,7 +703,8 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel, free(decoded); goto fail; } - pixels = kitty_png(decoded, decodedlen, &ks->width, &ks->height); + pixels = image_png_decode(decoded, decodedlen, IMAGE_SIZE_LIMIT, + &ks->width, &ks->height); free(decoded); } else if (ks->format == 24 || ks->format == 32) { pixels = kitty_raw(ks, decoded, decodedlen); @@ -759,16 +717,11 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel, if (pixels == NULL) goto fail; - sx = ks->columns; - sy = ks->rows; - if (xpixel == 0) - xpixel = 8; - if (ypixel == 0) - ypixel = 16; - if (sx == 0) - sx = (ks->width + xpixel - 1) / xpixel; - if (sy == 0) - sy = (ks->height + ypixel - 1) / ypixel; + image_size_in_cells(ks->width, ks->height, xpixel, ypixel, &sx, &sy); + if (ks->columns != 0) + sx = ks->columns; + if (ks->rows != 0) + sy = ks->rows; im = image_create(ks->width, ks->height, sx, sy, pixels); if (im == NULL) free(pixels); diff --git a/image-sixel.c b/image-sixel.c index 3b01fe191..577ba51a1 100644 --- a/image-sixel.c +++ b/image-sixel.c @@ -18,6 +18,7 @@ #include +#include #include #include @@ -401,14 +402,7 @@ sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y) si->xpixel = 8; if (si->ypixel == 0) si->ypixel = 16; - 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); + image_size_in_cells(si->x, si->y, si->xpixel, si->ypixel, x, y); } #ifdef ENABLE_IMAGES @@ -740,20 +734,27 @@ sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size) } static struct sixel_image * -sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells, - u_int xpixel, u_int ypixel) +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, sourcey0; - u_int sourcey1; + u_int x, y, sx, sy, sourcex, sourcey; + u_int sourcex0, sourcey0, sourcewidth, sourceheight; u_int r, g, b, colour, i; - sx = cells * xpixel; - sy = ypixel; + 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; @@ -772,16 +773,11 @@ sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells, } for (y = 0; y < sy; y++) { - sourcey0 = (uint64_t)oy * im->height / im->sy; - sourcey1 = (uint64_t)(oy + 1) * im->height / im->sy; - sourcey = sourcey0 + - (uint64_t)y * (sourcey1 - sourcey0) / sy; + sourcey = sourcey0 + (uint64_t)y * sourceheight / sy; if (sourcey >= im->height) sourcey = im->height - 1; for (x = 0; x < sx; x++) { - sourcex = ((uint64_t)ox * im->width / im->sx) + - (uint64_t)x * im->width * cells / - ((uint64_t)im->sx * sx); + sourcex = sourcex0 + (uint64_t)x * sourcewidth / sx; if (sourcex >= im->width) sourcex = im->width - 1; pixel = im->pixels + sourcey * im->stride + sourcex * 4; @@ -801,51 +797,29 @@ sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells, } void -sixel_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, - u_int nx, u_int atx, u_int aty) +sixel_draw_rectangle(struct tty *tty, const struct image_rectangle *rectangle, + __unused const struct tty_style_ctx *style_ctx) { - struct grid_cell gc, next; - struct image *im; struct sixel_image *si; char *data; size_t size; - u_int i, run; - 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; - } - si = sixel_from_image(im, gc.image_x, gc.image_y, run, - tty->xpixel, tty->ypixel); - if (si == NULL) - continue; - data = sixel_print(si, NULL, &size); - sixel_free(si); - if (data == NULL) - continue; - tty_region_off(tty); - tty_margin_off(tty); - tty_cursor(tty, atx + i, aty); - tty->flags |= TTY_NOBLOCK; - tty_putn(tty, data, size, 0); - tty_invalidate(tty); - free(data); - } + 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 * diff --git a/image.c b/image.c index 93768e35d..eb82a265f 100644 --- a/image.c +++ b/image.c @@ -19,6 +19,9 @@ #include +#include +#include +#include #include #include @@ -27,6 +30,88 @@ static struct images images = RB_INITIALIZER(&images); static u_int image_next_id; +#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 *); + void (*free)(struct tty *, int); + void (*geometry_changed)(struct tty *); +}; + +static const struct image_backend image_backend_ascii = { + "ascii", IMAGE_BACKEND_SCROLLS, NULL, NULL, NULL +}; +static const struct image_backend image_backend_kitty = { + "kitty", IMAGE_BACKEND_GRAPHICAL|IMAGE_BACKEND_SCROLLS, + kitty_draw_rectangle, kitty_free_output, kitty_geometry_changed +}; +static const struct image_backend image_backend_sixel = { + "sixel", IMAGE_BACKEND_GRAPHICAL, sixel_draw_rectangle, NULL, NULL +}; + +static const struct image_backend * +image_tty_find_backend(struct tty *tty) +{ + if (tty->term != NULL && tty->term->flags & TERM_KITTY) + return (&image_backend_kitty); + if (tty->term != NULL && tty->term->flags & TERM_SIXEL && + tty->xpixel != 0 && tty->ypixel != 0) + return (&image_backend_sixel); + return (&image_backend_ascii); +} + +void +image_tty_update(struct tty *tty) +{ + const struct image_backend *backend; + + backend = image_tty_find_backend(tty); + if (tty->image_backend == backend) + return; + + if (tty->image_backend != NULL && tty->image_backend->free != NULL) + tty->image_backend->free(tty, !!(tty->flags & TTY_OPENED)); + tty->image_data = NULL; + tty->image_backend = backend; + log_debug("%s: %s image backend is %s", __func__, + tty->client->name, backend->name); +} + +int +image_tty_is_graphical(struct tty *tty) +{ + image_tty_update(tty); + return (!!(tty->image_backend->flags & IMAGE_BACKEND_GRAPHICAL)); +} + +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); + if (tty->image_backend->geometry_changed != NULL) + tty->image_backend->geometry_changed(tty); +} + +void +image_tty_free(struct tty *tty, int send) +{ + if (tty->image_backend != NULL && tty->image_backend->free != NULL) + tty->image_backend->free(tty, send); + tty->image_backend = NULL; + tty->image_data = NULL; +} + static int image_cmp(struct image *a, struct image *b) { @@ -38,6 +123,79 @@ image_cmp(struct image *a, struct image *b) } RB_GENERATE_STATIC(images, image, entry, image_cmp); +static void +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) +{ + 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; + + 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; + + 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++; + } + } + if (count == 0) + return; + sample->red = red / count; + sample->green = green / count; + sample->blue = blue / count; + sample->alpha = alpha / count; + sample->brightness = brightness / count; +} + +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) { @@ -56,7 +214,7 @@ image_create(u_int width, u_int height, u_int sx, u_int sy, u_char *pixels) return (NULL); if ((uint64_t)width * height * 4 > SIZE_MAX) return (NULL); - if ((uint64_t)sx * sy > SIZE_MAX) + if ((uint64_t)sx * sy > SIZE_MAX / sizeof *im->cells) return (NULL); im = xcalloc(1, sizeof *im); @@ -74,7 +232,6 @@ image_create(u_int width, u_int height, u_int sx, u_int sy, u_char *pixels) im->stride = (size_t)width * 4; im->size = im->stride * height; im->pixels = pixels; - image_make_ascii(im); RB_INSERT(images, &images, im); log_debug("%s: image %u is %ux%u pixels, %ux%u cells", __func__, @@ -107,10 +264,20 @@ image_free(u_int id) log_debug("%s: freeing image %u", __func__, id); RB_REMOVE(images, &images, im); free(im->pixels); - free(im->ascii); + 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) { @@ -121,9 +288,118 @@ image_set_cell(struct grid_cell *gc, struct image *im, u_int x, u_int y) gc->image_y = y; } +/* Convert a cell-aligned image rectangle into source pixel coordinates. */ void -image_clear(struct screen *s, u_int id) +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; +} + +u_char * +image_base64_decode(const char *data, size_t len, size_t limit, size_t *size) +{ + char *copy; + u_char *out; + size_t needed; + int result; + + if (len > SIZE_MAX - 3) + return (NULL); + needed = (len + 3) / 4 * 3; + if (needed > limit || needed > INT_MAX) + return (NULL); + + copy = xmalloc(len + 1); + memcpy(copy, data, len); + copy[len] = '\0'; + out = xmalloc(needed == 0 ? 1 : needed); + result = b64_pton(copy, out, needed); + free(copy); + if (result < 0) { + free(out); + return (NULL); + } + *size = result; + return (out); +} + +u_char * +image_png_decode(const u_char *data, size_t size, size_t limit, u_int *width, + u_int *height) +{ + png_image pi; + u_char *pixels; + + if (size == 0 || size > limit) + return (NULL); + memset(&pi, 0, sizeof pi); + pi.version = PNG_IMAGE_VERSION; + if (!png_image_begin_read_from_memory(&pi, data, size)) + return (NULL); + pi.format = PNG_FORMAT_RGBA; + if (pi.width == 0 || pi.height == 0 || + (uint64_t)pi.width * pi.height * 4 > limit) { + png_image_free(&pi); + return (NULL); + } + pixels = xmalloc(PNG_IMAGE_SIZE(pi)); + if (!png_image_finish_read(&pi, NULL, pixels, 0, NULL)) { + free(pixels); + png_image_free(&pi); + return (NULL); + } + *width = pi.width; + *height = pi.height; + png_image_free(&pi); + return (pixels); +} + +void +image_clear(struct screen_write_ctx *ctx, u_int id) +{ + struct screen *s = ctx->s; struct grid *gd = s->grid; struct grid_cell gc; u_int x, y; @@ -132,24 +408,33 @@ image_clear(struct screen *s, u_int id) for (x = 0; x < gd->sx; x++) { grid_get_cell(gd, x, y, &gc); if ((gc.flags & GRID_FLAG_IMAGE) && - (id == 0 || gc.image_id == id)) + (id == 0 || gc.image_id == id)) { + if (y >= gd->hsize) + image_damage_area(ctx, x, y - gd->hsize, + 1, 1); grid_set_cell(gd, x, y, &grid_default_cell); + } } } } -int +static int image_check_area(struct screen *s, u_int px, u_int py, u_int nx, u_int ny) { struct grid_cell gc; u_int x, y, ex, ey; + u_int sx = screen_size_x(s), sy = screen_size_y(s); - ex = px + nx; - if (ex > screen_size_x(s)) - ex = screen_size_x(s); - ey = py + ny; - if (ey > screen_size_y(s)) - ey = screen_size_y(s); + 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); @@ -160,23 +445,72 @@ image_check_area(struct screen *s, u_int px, u_int py, u_int nx, u_int ny) return (0); } -int -image_check_line(struct screen *s, u_int py, u_int ny) +void +image_damage_area(struct screen_write_ctx *ctx, u_int px, u_int py, u_int nx, + u_int ny) { - return (image_check_area(s, 0, py, screen_size_x(s), ny)); + if (ctx->wp != NULL && image_check_area(ctx->s, px, py, nx, ny)) + ctx->wp->flags |= PANE_REDRAW; } -int -image_free_all(struct screen *s) +void +image_damage_all(struct screen_write_ctx *ctx) { - return (image_check_area(s, 0, 0, screen_size_x(s), - screen_size_y(s))); + image_damage_area(ctx, 0, 0, screen_size_x(ctx->s), + screen_size_y(ctx->s)); } -int -image_scroll_up(struct screen *s, __unused u_int lines) +void +image_damage_scroll(struct screen_write_ctx *ctx, __unused u_int lines) { - return (image_free_all(s)); + 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); + } } /* @@ -218,7 +552,6 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg) grid_view_set_cell(gd, cx + x, cy + y, &gc); } } - if (ctx->wp != NULL) - ctx->wp->flags |= PANE_REDRAW; + image_damage_area(ctx, cx, cy, sx, sy); screen_write_cursormove(ctx, 0, cy + sy, 0); } diff --git a/input.c b/input.c index e07e15bf7..83d43165b 100644 --- a/input.c +++ b/input.c @@ -2833,7 +2833,7 @@ input_exit_apc(struct input_ctx *ictx) } if (im != NULL) { if (action == 'd') - image_clear(sctx->s, im->id); + image_clear(sctx, im->id); else image_write(sctx, im, ictx->cell.cell.bg); image_free(im->id); @@ -2841,7 +2841,7 @@ input_exit_apc(struct input_ctx *ictx) input_reply(ictx, 0, "\033_Gi=%u;OK\033\\", image_id); } else if (action == 'd' && image_id == 0) { - image_clear(sctx->s, 0); + image_clear(sctx, 0); } else if ((action == 't' || action == 'q') && quiet == 0) input_reply(ictx, 0, "\033_Gi=%u;OK\033\\", image_id); return; diff --git a/regress/image-support.sh b/regress/image-support.sh index 64bc77b37..82564ca4d 100755 --- a/regress/image-support.sh +++ b/regress/image-support.sh @@ -54,13 +54,20 @@ $TMUX new-window -d " sleep 1 [ "$($TMUX display-message -pt:2 '#{cursor_y}')" = 1 ] || exit 1 +# PNG Kitty input uses the shared image decoder and canonical cell sizing. +$TMUX new-window -d " + printf '\033_Ga=T,q=2,f=100;iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGUExURf8AAP///0EdNBEAAAABYktHRAH/Ai3eAAAAB3RJTUUH6ggCDAECH324BwAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=\033\\' + sleep 10" +sleep 1 +[ "$($TMUX display-message -pt:3 '#{cursor_y}')" = 1 ] || exit 1 + # SIXEL input reaches the same grid marker and copy-mode paths. $TMUX new-window -d "cat '$FIXTURE'; sleep 10" sleep 1 -[ "$($TMUX display-message -pt:3 '#{cursor_y}')" -gt 0 ] || exit 1 -$TMUX copy-mode -t:3 || exit 1 -$TMUX send-keys -t:3 -X history-top || exit 1 -$TMUX capture-pane -pt:3 >$TMP || exit 1 +[ "$($TMUX display-message -pt:4 '#{cursor_y}')" -gt 0 ] || exit 1 +$TMUX copy-mode -t:4 || exit 1 +$TMUX send-keys -t:4 -X history-top || exit 1 +$TMUX capture-pane -pt:4 >$TMP || exit 1 # A client without an image feature gets the brightness-based ASCII backend. $TMUX kill-server 2>/dev/null diff --git a/screen-redraw.c b/screen-redraw.c index a20ac59eb..2f4e78283 100644 --- a/screen-redraw.c +++ b/screen-redraw.c @@ -1126,11 +1126,7 @@ redraw_draw_pane_span(struct redraw_draw_ctx *dctx, py = span->data.p.py; tty_draw_line(tty, s, px, py, n, x, y, &style_ctx); #ifdef ENABLE_IMAGES - if (tty->term->flags & TERM_KITTY) - kitty_draw_line(tty, s, px, py, n, x, y, &style_ctx); - else if ((tty->term->flags & TERM_SIXEL) && - tty->xpixel != 0 && tty->ypixel != 0) - sixel_draw_line(tty, s, px, py, n, x, y); + image_draw_line(tty, s, px, py, n, x, y, &style_ctx); #endif } diff --git a/screen-write.c b/screen-write.c index 46b735a6c..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, diff --git a/screen.c b/screen.c index 7fa0dd743..a06f1e26c 100644 --- a/screen.c +++ b/screen.c @@ -126,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); } diff --git a/tmux.h b/tmux.h index 9fc905cc6..c553b4feb 100644 --- a/tmux.h +++ b/tmux.h @@ -77,7 +77,8 @@ struct session; #ifdef ENABLE_IMAGES struct image; -struct tty_image_cache; +struct image_backend; +struct image_rectangle; #endif #ifdef ENABLE_SIXEL struct sixel_image; @@ -1037,6 +1038,23 @@ struct style { }; #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; +}; + +/* 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; @@ -1048,11 +1066,25 @@ struct image { size_t stride; size_t size; u_char *pixels; - char *ascii; + struct image_cell *cells; /* lazily generated text samples */ RB_ENTRY(image) entry; }; 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. */ @@ -1843,8 +1875,8 @@ struct tty { struct event key_timer; struct tty_key *key_tree; #ifdef ENABLE_IMAGES - struct tty_image_cache *images; - u_int image_next_id; + const struct image_backend *image_backend; + void *image_data; #endif }; @@ -4227,16 +4259,32 @@ 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); -int image_free_all(struct screen *); -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); 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_make_ascii(struct image *); -const char *image_get_ascii(struct image *, u_int, u_int); -void image_clear(struct screen *, 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 *); +u_char *image_base64_decode(const char *, size_t, size_t, size_t *); +u_char *image_png_decode(const u_char *, size_t, size_t, 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_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 *); +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 image_clear(struct screen_write_ctx *, u_int); #define KITTY_PARSE_ERROR -1 #define KITTY_PARSE_OK 0 #define KITTY_PARSE_MORE 1 @@ -4244,11 +4292,12 @@ void image_clear(struct screen *, u_int); struct image *kitty_parse_image(void **, const u_char *, size_t, u_int, u_int, u_int *, u_int *, char *, int *); void kitty_free_state(void *); -void kitty_draw_line(struct tty *, struct screen *, u_int, u_int, - u_int, u_int, u_int, const struct tty_style_ctx *); -void kitty_images_free(struct tty *, int); -void sixel_draw_line(struct tty *, struct screen *, u_int, u_int, - u_int, u_int, u_int); +void kitty_draw_rectangle(struct tty *, + const struct image_rectangle *, const struct tty_style_ctx *); +void kitty_free_output(struct tty *, int); +void kitty_geometry_changed(struct tty *); +void sixel_draw_rectangle(struct tty *, + const struct image_rectangle *, const struct tty_style_ctx *); #endif #ifdef ENABLE_SIXEL diff --git a/tty-draw.c b/tty-draw.c index 563a86dea..51d2d7dcd 100644 --- a/tty-draw.c +++ b/tty-draw.c @@ -128,7 +128,6 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, #ifdef ENABLE_IMAGES struct grid_cell image_gc; struct image *im; - const char *ascii; #endif struct grid_line *gl; u_int i, j, last_i, cx, ex, width; @@ -265,19 +264,19 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx, #ifdef ENABLE_IMAGES /* Text terminals render image blocks as ASCII. */ if (gc.flags & GRID_FLAG_IMAGE) { - memcpy(&image_gc, &gc, sizeof image_gc); im = image_find(gc.image_id); - if ((tty->term->flags & TERM_KITTY) || - ((tty->term->flags & TERM_SIXEL) && - tty->xpixel != 0 && - tty->ypixel != 0)) { - ascii = " "; + 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 - ascii = image_get_ascii(im, - gc.image_x, gc.image_y); - utf8_set(&image_gc.data, *ascii); - image_gc.flags &= ~GRID_FLAG_IMAGE; + } 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; diff --git a/tty.c b/tty.c index 1811dba62..285c314ac 100644 --- a/tty.c +++ b/tty.c @@ -109,9 +109,6 @@ tty_init(struct tty *tty, struct client *c) tty->ccolour = -1; tty->fg = tty->bg = -1; tty->mouse_last_pane = -1; -#ifdef ENABLE_IMAGES - tty->image_next_id = arc4random_uniform(0xffff00) + 1; -#endif if (tcgetattr(c->fd, &tty->tio) != 0) return (-1); @@ -161,10 +158,19 @@ 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); +#endif } static void @@ -510,7 +516,7 @@ tty_close(struct tty *tty) if (tty->flags & TTY_OPENED) { #ifdef ENABLE_IMAGES - kitty_images_free(tty, 1); + image_tty_free(tty, 1); #endif evbuffer_free(tty->in); event_del(&tty->event_in); @@ -529,7 +535,7 @@ tty_free(struct tty *tty) { tty_close(tty); #ifdef ENABLE_IMAGES - kitty_images_free(tty, 0); + image_tty_free(tty, 0); #endif free(tty->r.ranges); @@ -542,6 +548,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); @@ -2122,7 +2131,6 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, #ifdef ENABLE_IMAGES struct grid_cell image_gc; struct image *im; - const char *ascii; #endif /* Skip last character if terminal is stupid. */ @@ -2146,15 +2154,11 @@ tty_cell(struct tty *tty, const struct grid_cell *gc, * selection; the unchanged image would then need to be drawn again. */ if (gc->flags & GRID_FLAG_IMAGE) { - if ((tty->term->flags & TERM_KITTY) || - ((tty->term->flags & TERM_SIXEL) && - tty->xpixel != 0 && tty->ypixel != 0)) + if (image_tty_is_graphical(tty)) return; - memcpy(&image_gc, gc, sizeof image_gc); im = image_find(gc->image_id); - ascii = image_get_ascii(im, gc->image_x, gc->image_y); - utf8_set(&image_gc.data, *ascii); - image_gc.flags &= ~GRID_FLAG_IMAGE; + image_get_text_cell(tty, im, gc->image_x, gc->image_y, gc, + &image_gc, style_ctx); gc = &image_gc; } #endif