diff --git a/image-kitty.c b/image-kitty.c index 49786ed0a..0eac76780 100644 --- a/image-kitty.c +++ b/image-kitty.c @@ -76,6 +76,8 @@ struct kitty_state { u_int height; u_int source_x; u_int source_y; + u_int x_offset; + u_int y_offset; u_int source_width; u_int source_height; u_int columns; @@ -526,6 +528,8 @@ kitty_control(struct kitty_state *ks, const u_char *buf, size_t len) case 'S': case 'C': case 'U': + case 'X': + case 'Y': if (kitty_number((const char *)value, valuelen, &number) != 0) return (-1); @@ -546,6 +550,8 @@ kitty_control(struct kitty_state *ks, const u_char *buf, size_t len) case 'S': ks->data_size = number; break; case 'C': ks->no_cursor = (number != 0); break; case 'U': ks->virtual = (number != 0); break; + case 'X': ks->x_offset = number; break; + case 'Y': ks->y_offset = number; break; } break; } @@ -855,7 +861,7 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel, uint64_t numerator, denominator, value; u_int x, y, width, height, sx, sy, canvas_width; u_int canvas_height, cell_width, cell_height, source_width; - u_int source_height; + u_int source_height, display_width, display_height; struct image *im; x = ks->source_x; @@ -869,11 +875,17 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel, height = ks->source_height; if (height == 0 || height > source_height - y) height = source_height - y; + if (ks->x_offset > UINT_MAX - width || + ks->y_offset > UINT_MAX - height) + return (NULL); + display_width = width + ks->x_offset; + display_height = height + ks->y_offset; 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, + image_size_in_cells(display_width, display_height, cell_width, + cell_height, &sx, &sy); value = (uint64_t)sx * cell_width; if (value > UINT_MAX) @@ -886,46 +898,46 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel, } else if (ks->columns != 0 && ks->rows != 0) { sx = ks->columns; sy = ks->rows; - canvas_width = width; - canvas_height = height; + canvas_width = display_width; + canvas_height = display_height; } else if (ks->columns != 0) { sx = ks->columns; - numerator = (uint64_t)height * sx * cell_width; - denominator = (uint64_t)width * cell_height; + numerator = (uint64_t)display_height * sx * cell_width; + denominator = (uint64_t)display_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; + canvas_width = display_width; + numerator = (uint64_t)sy * cell_height * display_width; denominator = (uint64_t)sx * cell_width; value = (numerator + denominator - 1) / denominator; - if (value < height) - value = height; + if (value < display_height) + value = display_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; + numerator = (uint64_t)display_width * sy * cell_height; + denominator = (uint64_t)display_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; + canvas_height = display_height; + numerator = (uint64_t)sx * cell_width * display_height; denominator = (uint64_t)sy * cell_height; value = (numerator + denominator - 1) / denominator; - if (value < width) - value = width; + if (value < display_width) + value = display_width; if (value > UINT_MAX) return (NULL); canvas_width = value; } im = image_create_view(source, x, y, width, height, canvas_width, - canvas_height, sx, sy); + canvas_height, sx, sy, ks->x_offset, ks->y_offset); if (im != NULL && ks->no_cursor) image_set_no_cursor(im); return (im); diff --git a/image-sixel.c b/image-sixel.c index c82f8c463..15b64bf99 100644 --- a/image-sixel.c +++ b/image-sixel.c @@ -603,7 +603,7 @@ sixel_scale(struct sixel_image *si, u_int cell_w, u_int cell_h, u_int ox, { struct sixel_image *new; u_int cx, cy, pox, poy, psx, psy, tsx, tsy, px, py; - uint64_t x0, x1, y0, y1; + uint64_t x0, x1, y0, y1, tx0, tx1, ty0, ty1; u_int x, y, i; /* @@ -640,8 +640,27 @@ sixel_scale(struct sixel_image *si, u_int cell_w, u_int cell_h, u_int ox, psx = x1 - x0; psy = y1 - y0; - tsx = sx * cell_w; - tsy = sy * cell_h; + /* + * Preserve any partial final source cell. The grid still covers whole + * cells, but the SIXEL raster must end at the corresponding pixel offset + * rather than stretching to the cell boundary. + */ + tx1 = ((uint64_t)si->sx * cell_w + si->cell_w - 1) / si->cell_w; + ty1 = ((uint64_t)si->sy * cell_h + si->cell_h - 1) / si->cell_h; + if (tx1 > UINT_MAX || ty1 > UINT_MAX) + return (NULL); + tx0 = (uint64_t)ox * cell_w; + ty0 = (uint64_t)oy * cell_h; + if (tx0 >= tx1 || ty0 >= ty1) + return (NULL); + if ((uint64_t)(ox + sx) * cell_w < tx1) + tx1 = (uint64_t)(ox + sx) * cell_w; + if ((uint64_t)(oy + sy) * cell_h < ty1) + ty1 = (uint64_t)(oy + sy) * cell_h; + tsx = tx1 - tx0; + tsy = ty1 - ty0; + if (tsx == 0 || tsy == 0) + return (NULL); new = xcalloc (1, sizeof *si); new->cell_w = cell_w; diff --git a/image.c b/image.c index cb925e07f..48e84f9cf 100644 --- a/image.c +++ b/image.c @@ -971,13 +971,15 @@ image_create(u_int width, u_int height, u_int canvas_width, return (im); } -/* Create an immutable rectangular view without copying its source pixels. */ -/* Create a cell-aligned view of an existing image. */ +/* Create a cell-aligned view of an existing image with an optional offset. */ 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) + u_int height, u_int canvas_width, u_int canvas_height, u_int sx, u_int sy, + u_int x_offset, u_int y_offset) { struct image *im; + u_char *pixels; + u_int padded_width, padded_height, yy; if (source == NULL || x >= source->width || y >= source->height || width == 0 || width > source->width - x || height == 0 || @@ -988,9 +990,28 @@ image_create_view(struct image *source, u_int x, u_int y, u_int width, sx > USHRT_MAX || sy > USHRT_MAX) return (NULL); - im = image_create1(width, height, canvas_width, canvas_height, sx, sy, - source->stride, source->pixels + (size_t)y * source->stride + - (size_t)x * 4); + if (x_offset == 0 && y_offset == 0) { + im = image_create1(width, height, canvas_width, canvas_height, + sx, sy, source->stride, source->pixels + + (size_t)y * source->stride + (size_t)x * 4); + } else { + if (x_offset > UINT_MAX - width || + y_offset > UINT_MAX - height) + return (NULL); + padded_width = width + x_offset; + padded_height = height + y_offset; + if ((uint64_t)padded_width * padded_height * 4 > SIZE_MAX) + return (NULL); + pixels = xcalloc((size_t)padded_width * padded_height, 4); + for (yy = 0; yy < height; yy++) { + memcpy(pixels + (size_t)(yy + y_offset) * padded_width * 4 + + (size_t)x_offset * 4, + source->pixels + (size_t)(y + yy) * source->stride + + (size_t)x * 4, (size_t)width * 4); + } + im = image_create1(padded_width, padded_height, canvas_width, + canvas_height, sx, sy, (size_t)padded_width * 4, pixels); + } if (im == NULL) return (NULL); im->parent_id = source->id; diff --git a/regress/image-support.sh b/regress/image-support.sh index de81a15ed..cba724a90 100755 --- a/regress/image-support.sh +++ b/regress/image-support.sh @@ -79,14 +79,24 @@ $TMUX2 new-session -d -x 10 -y 4 " printf '\033Pq\"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\' sleep 10" || exit 1 $TMUX2 set -g status off || exit 1 +$TMUX2 set -as terminal-features ',*:sixel' || exit 1 $TMUX new-session -d -x 10 -y 4 || exit 1 $TMUX set -g status off || exit 1 +$TMUX pipe-pane -O "cat >$TMP" || 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 +# Re-emitting the image preserves the 26-pixel raster rather than expanding +# it to the two complete 16-pixel grid cells occupied by the image. +$TMUX pipe-pane || exit 1 +$TMUX pipe-pane -O "cat >$TMP" || exit 1 +$TMUX2 refresh-client -R || exit 1 +sleep 1 +grep -a '"1;1;26;26' $TMP >/dev/null || exit 1 + # A selection redraw uses the single-cell path. It must leave ASCII image # cells visible (graphical clients skip these cells to avoid erasing pixels). $TMUX2 copy-mode || exit 1 diff --git a/tmux.h b/tmux.h index 69583c50d..79bf8d962 100644 --- a/tmux.h +++ b/tmux.h @@ -4237,7 +4237,7 @@ char *regsub(const char *, const char *, const char *, int); 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); + u_int, u_int, u_int, u_int, u_int, u_int, u_int); struct image *image_find(u_int); u_int image_get_id(const struct image *); void image_get_size(const struct image *, u_int *, u_int *);