image: preserve fractional cell extents

This commit is contained in:
Michael Grant
2026-08-10 15:08:26 +01:00
parent c97ceaffe8
commit c2426fcf08
5 changed files with 89 additions and 27 deletions

View File

@@ -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);

View File

@@ -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;

33
image.c
View File

@@ -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;

View File

@@ -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

2
tmux.h
View File

@@ -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 *);