diff --git a/image-kitty.c b/image-kitty.c index f78ee943a..9975a7ecf 100644 --- a/image-kitty.c +++ b/image-kitty.c @@ -75,6 +75,10 @@ struct kitty_state { char compression; u_int width; u_int height; + u_int source_x; + u_int source_y; + u_int source_width; + u_int source_height; u_int columns; u_int rows; u_int image_id; @@ -232,7 +236,8 @@ kitty_upload(struct tty *tty, struct image *im) struct kitty_output *ko = kitty_get_output(tty); struct kitty_image_cache *cache; char control[128], encoded[4097]; - size_t offset, size; + u_char raw[3072]; + size_t offset, size, copied, row, column, available; int encodedlen; u_int id; @@ -261,9 +266,18 @@ kitty_upload(struct tty *tty, struct image *im) for (offset = 0; offset < im->size; offset += size) { size = im->size - offset; - if (size > 3072) - size = 3072; - encodedlen = b64_ntop(im->pixels + offset, size, encoded, + if (size > sizeof raw) + size = sizeof raw; + for (copied = 0; copied < size; copied += available) { + row = (offset + copied) / ((size_t)im->width * 4); + column = (offset + copied) % ((size_t)im->width * 4); + available = (size_t)im->width * 4 - column; + if (available > size - copied) + available = size - copied; + memcpy(raw + copied, im->pixels + row * im->stride + + column, available); + } + encodedlen = b64_ntop(raw, size, encoded, sizeof encoded); if (encodedlen < 0) return (NULL); @@ -418,6 +432,10 @@ kitty_control(struct kitty_state *ks, const u_char *buf, size_t len) case 'f': case 's': case 'v': + case 'x': + case 'y': + case 'w': + case 'h': case 'c': case 'r': case 'i': @@ -431,6 +449,10 @@ kitty_control(struct kitty_state *ks, const u_char *buf, size_t len) case 'f': ks->format = number; break; case 's': ks->width = number; break; case 'v': ks->height = number; break; + case 'x': ks->source_x = number; break; + case 'y': ks->source_y = number; break; + case 'w': ks->source_width = number; break; + case 'h': ks->source_height = number; break; case 'c': ks->columns = number; break; case 'r': ks->rows = number; break; case 'i': ks->image_id = number; break; @@ -594,9 +616,87 @@ kitty_raw(struct kitty_state *ks, u_char *data, size_t size) return (pixels); } +static struct image * +kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel, + u_int ypixel) +{ + uint64_t numerator, denominator, value; + u_int x, y, width, height, sx, sy, canvas_width; + u_int canvas_height, cell_width, cell_height; + + x = ks->source_x; + y = ks->source_y; + if (x >= source->width || y >= source->height) + return (NULL); + width = ks->source_width; + if (width == 0 || width > source->width - x) + width = source->width - x; + height = ks->source_height; + if (height == 0 || height > source->height - y) + height = source->height - y; + + cell_width = (xpixel == 0 ? 8 : xpixel); + cell_height = (ypixel == 0 ? 16 : ypixel); + if (ks->columns == 0 && ks->rows == 0) { + image_size_in_cells(width, height, cell_width, cell_height, + &sx, &sy); + value = (uint64_t)sx * cell_width; + if (value > UINT_MAX) + return (NULL); + canvas_width = value; + value = (uint64_t)sy * cell_height; + if (value > UINT_MAX) + return (NULL); + canvas_height = value; + } else if (ks->columns != 0 && ks->rows != 0) { + sx = ks->columns; + sy = ks->rows; + canvas_width = width; + canvas_height = height; + } else if (ks->columns != 0) { + sx = ks->columns; + numerator = (uint64_t)height * sx * cell_width; + denominator = (uint64_t)width * cell_height; + value = (numerator + denominator - 1) / denominator; + if (value == 0 || value > UINT_MAX) + return (NULL); + sy = value; + canvas_width = width; + numerator = (uint64_t)sy * cell_height * width; + denominator = (uint64_t)sx * cell_width; + value = (numerator + denominator - 1) / denominator; + if (value < height) + value = height; + if (value > UINT_MAX) + return (NULL); + canvas_height = value; + } else { + sy = ks->rows; + numerator = (uint64_t)width * sy * cell_height; + denominator = (uint64_t)height * cell_width; + value = (numerator + denominator - 1) / denominator; + if (value == 0 || value > UINT_MAX) + return (NULL); + sx = value; + canvas_height = height; + numerator = (uint64_t)sx * cell_width * height; + denominator = (uint64_t)sy * cell_height; + value = (numerator + denominator - 1) / denominator; + if (value < width) + value = width; + if (value > UINT_MAX) + return (NULL); + canvas_width = value; + } + + return (image_create_view(source, x, y, width, height, canvas_width, + canvas_height, sx, sy)); +} + /* * Parse one Kitty graphics APC body (without the leading G). Only direct - * static images are accepted. The returned image owns decoded RGBA pixels. + * static images are accepted. The returned image retains immutable RGBA + * pixels for the lifetime of its placement. */ struct image * kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel, @@ -609,8 +709,9 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel, u_char *uncompressed; size_t controllen, payloadlen, decodedlen; uLongf uncompressedlen; - u_int sx, sy; - struct image *im; + u_int sx, sy, cell_width, cell_height; + uint64_t canvas_width, canvas_height; + struct image *im = NULL, *source; if (kc == NULL) { kc = xcalloc(1, sizeof *kc); @@ -650,9 +751,16 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel, } kc->transfer = NULL; if (ks->action == 'p') { - im = kitty_source_get(kc, ks->image_id); - *status = (im == NULL ? KITTY_PARSE_MISSING : - KITTY_PARSE_OK); + source = kitty_source_get(kc, ks->image_id); + if (source == NULL) { + *status = KITTY_PARSE_MISSING; + im = NULL; + } else { + im = kitty_place_image(source, ks, xpixel, ypixel); + image_free(source->id); + if (im != NULL) + *status = KITTY_PARSE_OK; + } kitty_state_free(ks); return (im); } @@ -717,21 +825,30 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel, if (pixels == NULL) goto fail; - 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); + cell_width = (xpixel == 0 ? 8 : xpixel); + cell_height = (ypixel == 0 ? 16 : ypixel); + image_size_in_cells(ks->width, ks->height, cell_width, cell_height, + &sx, &sy); + canvas_width = (uint64_t)sx * cell_width; + canvas_height = (uint64_t)sy * cell_height; + if (canvas_width > UINT_MAX || canvas_height > UINT_MAX) + source = NULL; else + source = image_create(ks->width, ks->height, canvas_width, + canvas_height, sx, sy, pixels); + if (source == NULL) + free(pixels); + else { *status = KITTY_PARSE_OK; - if (im != NULL && ks->action != 'q') - kitty_source_set(kc, ks->image_id, im); - if (im != NULL && (ks->action == 'q' || ks->action == 't')) { - image_free(im->id); - im = NULL; + if (ks->action != 'q') + kitty_source_set(kc, ks->image_id, source); + if (ks->action == 'T') { + im = kitty_place_image(source, ks, xpixel, ypixel); + if (im == NULL) + *status = KITTY_PARSE_ERROR; + } else + im = NULL; + image_free(source->id); } kitty_state_free(ks); return (im); diff --git a/image.c b/image.c index 104377b04..c3cdf5612 100644 --- a/image.c +++ b/image.c @@ -209,6 +209,38 @@ image_find(u_int id) return (RB_FIND(images, &images, &find)); } +static struct image * +image_create1(u_int width, u_int height, u_int canvas_width, + u_int canvas_height, u_int sx, u_int sy, size_t stride, u_char *pixels) +{ + struct image *im; + + im = xcalloc(1, sizeof *im); + do { + if (++image_next_id == 0) + image_next_id++; + im->id = image_next_id; + } while (image_find(im->id) != NULL); + + im->references = 1; + im->source_id = im->id; + im->width = width; + im->height = height; + im->canvas_width = canvas_width; + im->canvas_height = canvas_height; + im->sx = sx; + im->sy = sy; + im->stride = stride; + im->size = (size_t)width * height * 4; + im->pixels = pixels; + + RB_INSERT(images, &images, im); + log_debug("%s: image %u is %ux%u pixels on %ux%u canvas, " + "%ux%u cells", __func__, im->id, width, height, canvas_width, + canvas_height, sx, sy); + return (im); +} + struct image * image_create(u_int width, u_int height, u_int canvas_width, u_int canvas_height, u_int sx, u_int sy, u_char *pixels) @@ -222,29 +254,32 @@ image_create(u_int width, u_int height, u_int canvas_width, return (NULL); if ((uint64_t)sx * sy > SIZE_MAX / sizeof *im->cells) return (NULL); + im = image_create1(width, height, canvas_width, canvas_height, sx, sy, + (size_t)width * 4, pixels); + return (im); +} - im = xcalloc(1, sizeof *im); - do { - if (++image_next_id == 0) - image_next_id++; - im->id = image_next_id; - } while (image_find(im->id) != NULL); +/* Create an immutable rectangular view without copying its source pixels. */ +struct image * +image_create_view(struct image *source, u_int x, u_int y, u_int width, + u_int height, u_int canvas_width, u_int canvas_height, u_int sx, u_int sy) +{ + struct image *im; - im->references = 1; - im->width = width; - im->height = height; - im->canvas_width = canvas_width; - im->canvas_height = canvas_height; - im->sx = sx; - im->sy = sy; - im->stride = (size_t)width * 4; - im->size = im->stride * height; - im->pixels = pixels; + if (source == NULL || x >= source->width || y >= source->height || + width == 0 || width > source->width - x || height == 0 || + height > source->height - y || canvas_width < width || + canvas_height < height || sx == 0 || sy == 0) + return (NULL); + if ((uint64_t)sx * sy > SIZE_MAX / sizeof *im->cells) + return (NULL); - RB_INSERT(images, &images, im); - log_debug("%s: image %u is %ux%u pixels on %ux%u canvas, " - "%ux%u cells", __func__, im->id, width, height, canvas_width, - canvas_height, sx, sy); + im = image_create1(width, height, canvas_width, canvas_height, sx, sy, + source->stride, source->pixels + (size_t)y * source->stride + + (size_t)x * 4); + im->parent_id = source->id; + im->source_id = source->source_id; + image_ref(source->id); return (im); } @@ -272,7 +307,10 @@ image_free(u_int id) log_debug("%s: freeing image %u", __func__, id); RB_REMOVE(images, &images, im); - free(im->pixels); + if (im->parent_id == 0) + free(im->pixels); + else + image_free(im->parent_id); free(im->cells); free(im); } @@ -413,13 +451,17 @@ image_clear(struct screen_write_ctx *ctx, u_int id) struct screen *s = ctx->s; struct grid *gd = s->grid; struct grid_cell gc; + struct image *im; u_int x, y; for (y = 0; y < gd->hsize + gd->sy; y++) { 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)) { + im = NULL; + if (gc.flags & GRID_FLAG_IMAGE) + im = image_find(gc.image_id); + if (im != NULL && (id == 0 || gc.image_id == id || + im->source_id == id)) { if (y >= gd->hsize) image_damage_area(ctx, x, y - gd->hsize, 1, 1); diff --git a/regress/image-support.sh b/regress/image-support.sh index 05eabc128..aa6fd8058 100755 --- a/regress/image-support.sh +++ b/regress/image-support.sh @@ -96,4 +96,22 @@ sleep 1 $TMUX capture-pane -pS0 -E0 >$TMP || exit 1 grep -q '^#=' $TMP || exit 1 +# A retained Kitty image may be placed repeatedly using source rectangles. +# Crop the green and white right column from a red/green/blue/white image. +$TMUX kill-server 2>/dev/null +$TMUX2 kill-server 2>/dev/null +$TMUX2 new-session -d -x 10 -y 4 " + printf '\033_Ga=t,q=2,f=32,s=2,v=2,i=10;/wAA/wD/AP8AAP///////w==\033\\' + printf '\033_Ga=p,q=2,i=10,x=1,y=0,w=1,h=2,c=1,r=2\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 -E1 >$TMP || exit 1 +[ "$(sed -n 1p $TMP)" = "*" ] || exit 1 +[ "$(sed -n 2p $TMP)" = "@" ] || exit 1 + exit 0 diff --git a/tmux.h b/tmux.h index ca2b282f1..e9d6f01c1 100644 --- a/tmux.h +++ b/tmux.h @@ -1059,6 +1059,8 @@ struct image_cell { struct image { u_int id; u_int references; + u_int parent_id; + u_int source_id; u_int width; u_int height; u_int canvas_width; @@ -4259,6 +4261,8 @@ char *regsub(const char *, const char *, const char *, int); /* image.c */ struct image *image_create(u_int, u_int, u_int, u_int, u_int, u_int, u_char *); +struct image *image_create_view(struct image *, u_int, u_int, u_int, + u_int, u_int, u_int, u_int, u_int); struct image *image_find(u_int); void image_ref(u_int); void image_free(u_int);