image: retain ordered placements in the grid

Replace the per-cell image marker with sparse placement spans attached to
grid lines. A placement owns all of its spans and records the input
protocol, application image and placement IDs, z-index, and creation order.

This retains overlapping image layers without storing a list in every grid
cell. Grid operations move, split, clip, and remove only the affected spans.

Use the input protocol to determine image/text interaction: later text
damages SIXEL spans, while Kitty placements remain and are ordered by their
z-index. Rendering then adapts that one logical scene for each client,
rather than changing its semantics according to whether the outer terminal
uses Kitty or SIXEL.
This commit is contained in:
Michael Grant
2026-08-10 12:04:37 +01:00
parent cf80845be7
commit 047308b156
13 changed files with 1165 additions and 472 deletions

203
grid.c
View File

@@ -41,9 +41,6 @@
/* 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
};
/*
@@ -52,38 +49,16 @@ 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 (image_get_id_by_grid_id(gee->image_id));
}
#endif
#ifdef __APPLE__
void
grid_check_is_clear(struct grid *gd)
@@ -111,6 +86,9 @@ grid_check_is_clear(struct grid *gd)
assert(gl->extdsize == 0);
assert(gl->flags == 0);
assert(gl->time == 0);
#ifdef ENABLE_IMAGES
assert(gl->images == NULL);
#endif
}
}
#else
@@ -159,10 +137,6 @@ 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);
}
@@ -210,11 +184,6 @@ 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 = image_get_grid_id(gc->image_id);
gee->image_x = gc->image_x;
gee->image_y = gc->image_y;
#endif
return (gee);
}
@@ -302,12 +271,6 @@ 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) {
@@ -354,13 +317,6 @@ 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);
}
@@ -393,10 +349,6 @@ 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);
@@ -405,12 +357,7 @@ grid_free_line(struct grid *gd, u_int py)
#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);
}
image_grid_free_line(gd, gl);
#endif
free(gl->celldata);
free(gl->extddata);
@@ -456,6 +403,9 @@ void
grid_destroy(struct grid *gd)
{
grid_free_lines(gd, 0, gd->hsize + gd->sy);
#ifdef ENABLE_IMAGES
image_grid_free(gd);
#endif
free(gd->linedata);
free(gd);
}
@@ -683,11 +633,6 @@ 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 = image_get_id_by_grid_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);
@@ -708,11 +653,6 @@ 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. */
@@ -732,9 +672,6 @@ 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;
@@ -746,17 +683,6 @@ 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
@@ -782,10 +708,6 @@ grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc,
struct grid_line *gl;
struct grid_cell_entry *gce;
struct grid_extd_entry *gee;
const struct grid_cell *new_gc;
#ifdef ENABLE_IMAGES
struct grid_cell old_gc, image_gc;
#endif
u_int i;
if (grid_check_y(gd, __func__, py) != 0)
@@ -799,39 +721,11 @@ 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];
new_gc = gc;
#ifdef ENABLE_IMAGES
{
u_int old_id = grid_entry_image(gl, gce);
u_int new_id;
if (old_id != 0 && (~gc->flags & GRID_FLAG_IMAGE)) {
grid_get_cell1(gl, px + i, &old_gc);
memcpy(&image_gc, gc, sizeof image_gc);
image_gc.flags |= GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED;
image_gc.image_id = old_gc.image_id;
image_gc.image_x = old_gc.image_x;
image_gc.image_y = old_gc.image_y;
new_gc = &image_gc;
}
if (new_gc->flags & GRID_FLAG_IMAGE)
new_id = new_gc->image_id;
else
new_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, new_gc)) {
gee = grid_extended_cell(gl, gce, new_gc);
if (grid_need_extended_cell(gce, gc)) {
gee = grid_extended_cell(gl, gce, gc);
gee->data = utf8_build_one(s[i]);
} else
grid_store_cell(gce, new_gc, s[i]);
grid_store_cell(gce, gc, s[i]);
}
}
@@ -844,6 +738,9 @@ grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg)
if (nx == 0 || ny == 0)
return;
#ifdef ENABLE_IMAGES
image_grid_damage(gd, px, py, nx, ny);
#endif
if (px == 0 && nx == gd->sx) {
grid_clear_lines(gd, py, ny, bg);
@@ -879,6 +776,10 @@ grid_clear(struct grid *gd, u_int px, u_int py, u_int nx, u_int ny, u_int bg)
void
grid_clear_lines(struct grid *gd, u_int py, u_int ny, u_int bg)
{
struct grid_line *gl;
#ifdef ENABLE_IMAGES
struct image_line *images;
#endif
u_int yy;
if (ny == 0)
@@ -890,8 +791,18 @@ grid_clear_lines(struct grid *gd, u_int py, u_int ny, u_int bg)
return;
for (yy = py; yy < py + ny; yy++) {
grid_free_line(gd, yy);
gl = &gd->linedata[yy];
#ifdef ENABLE_IMAGES
image_grid_damage(gd, 0, yy, gd->sx, 1);
images = gl->images;
#endif
free(gl->celldata);
free(gl->extddata);
memset(gl, 0, sizeof *gl);
grid_empty_line(gd, yy, bg);
#ifdef ENABLE_IMAGES
gl->images = images;
#endif
}
if (py != 0)
gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED;
@@ -946,9 +857,6 @@ 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;
@@ -960,14 +868,7 @@ 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);
}
image_grid_move_cells(gd, dx, px, py, nx);
#endif
memmove(&gl->celldata[dx], &gl->celldata[px],
nx * sizeof *gl->celldata);
@@ -1341,14 +1242,6 @@ 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|GRID_FLAG_IMAGE_DAMAGED);
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,
flags, s, &has_link);
@@ -1416,13 +1309,17 @@ 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;
u_int original_dy, original_sy;
#endif
if (dy + ny > dst->hsize + dst->sy)
ny = dst->hsize + dst->sy - dy;
if (sy + ny > src->hsize + src->sy)
ny = src->hsize + src->sy - sy;
#ifdef ENABLE_IMAGES
original_dy = dy;
original_sy = sy;
#endif
grid_free_lines(dst, dy, ny);
for (yy = 0; yy < ny; yy++) {
@@ -1430,6 +1327,9 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
dstl = &dst->linedata[dy];
memcpy(dstl, srcl, sizeof *dstl);
#ifdef ENABLE_IMAGES
dstl->images = NULL;
#endif
if (srcl->cellsize != 0) {
dstl->celldata = xreallocarray(NULL,
srcl->cellsize, sizeof *dstl->celldata);
@@ -1446,16 +1346,12 @@ 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++;
}
#ifdef ENABLE_IMAGES
image_grid_duplicate_lines(dst, original_dy, src, original_sy, ny);
#endif
}
/* Mark line as dead. */
@@ -1473,14 +1369,14 @@ grid_reflow_has_image(struct grid_line *gl)
struct grid_cell gc;
u_int i;
#ifdef ENABLE_IMAGES
if (image_grid_line_has_images(gl))
return (1);
#endif
if (~gl->flags & GRID_LINE_EXTENDED)
return (0);
for (i = 0; i < gl->cellused; i++) {
grid_get_cell1(gl, i, &gc);
#ifdef ENABLE_IMAGES
if (gc.flags & GRID_FLAG_IMAGE)
return (1);
#endif
/* Kitty Unicode placeholder base character (U+10EEEE). */
if (gc.data.size >= 4 && gc.data.data[0] == 0xf4 &&
gc.data.data[1] == 0x8e && gc.data.data[2] == 0xbb &&
@@ -1672,15 +1568,6 @@ 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)
@@ -1727,7 +1614,7 @@ grid_reflow(struct grid *gd, u_int sx)
if (gl->flags & GRID_LINE_DEAD)
continue;
/* Keep image markers at their original cell coordinates. */
/* Keep image layers at their original cell coordinates. */
if (grid_reflow_has_image(gl)) {
grid_reflow_move(target, gl);
continue;

View File

@@ -82,6 +82,7 @@ struct kitty_state {
u_int rows;
u_int image_id;
u_int placement_id;
int32_t z;
u_int quiet;
int no_cursor;
int virtual;
@@ -95,6 +96,7 @@ struct kitty_state {
struct kitty_placement {
u_int placement_id;
u_int server_id;
int32_t z;
struct kitty_placement *next;
};
@@ -256,7 +258,7 @@ kitty_images_collect(struct tty *tty)
static void
kitty_place(struct tty *tty, struct kitty_image_cache *cache,
struct image *im, u_int source_x, u_int source_y, u_int width,
u_int height, u_int destination_x, u_int destination_y)
u_int height, u_int destination_x, u_int destination_y, int32_t z)
{
char control[192];
u_int px, py, pwidth, pheight, sx, sy;
@@ -271,6 +273,9 @@ kitty_place(struct tty *tty, struct kitty_image_cache *cache,
sx - px;
pheight = ((uint64_t)(source_y + height) * canvas_height + sy - 1) /
sy - py;
/* Account for the duplicate-pixel border added by kitty_upload(). */
px++;
py++;
placement = xcalloc(1, sizeof *placement);
do {
placement->id = ++cache->next_placement;
@@ -283,9 +288,10 @@ kitty_place(struct tty *tty, struct kitty_image_cache *cache,
cache->placements = placement;
tty_cursor(tty, destination_x, destination_y);
xsnprintf(control, sizeof control,
"\033_Ga=p,i=%u,p=%llu,x=%u,y=%u,w=%u,h=%u,c=%u,r=%u,C=1,"
"q=2\033\\", cache->kitty_id, (unsigned long long)placement->id, px, py,
pwidth, pheight, width, height);
"\033_Ga=p,i=%u,p=%llu,x=%u,y=%u,w=%u,h=%u,c=%u,r=%u,z=%d,"
"C=1,q=2\033\\", cache->kitty_id,
(unsigned long long)placement->id, px, py, pwidth, pheight, width,
height, z);
tty_puts(tty, control);
}
@@ -303,6 +309,7 @@ kitty_upload(struct tty *tty, struct image *im)
size_t image_size;
int encodedlen;
u_int id, width, height, canvas_width, canvas_height;
u_int upload_width, upload_height;
for (cache = ko->images; cache != NULL; cache = cache->next) {
if (cache->server_id != image_get_id(im))
@@ -332,15 +339,36 @@ kitty_upload(struct tty *tty, struct image *im)
pixels = image_get_pixels(im, &stride, &image_size);
image_get_size(im, &width, &height);
image_get_canvas_size(im, &canvas_width, &canvas_height);
if ((uint64_t)canvas_width * canvas_height * 4 > IMAGE_SIZE_LIMIT)
if (canvas_width > UINT_MAX - 2 || canvas_height > UINT_MAX - 2)
return (NULL);
padded = xcalloc((size_t)canvas_width * canvas_height, 4);
upload_width = canvas_width + 2;
upload_height = canvas_height + 2;
if ((uint64_t)upload_width * upload_height * 4 > IMAGE_SIZE_LIMIT)
return (NULL);
/*
* Pad the upload with duplicate edge pixels. Kitty linearly filters scaled
* textures against transparent border pixels, which otherwise darkens the
* outermost pixels of an opaque image.
*/
padded = xcalloc((size_t)upload_width * upload_height, 4);
for (row = 0; row < height; row++)
memcpy(padded + (size_t)row * canvas_width * 4,
memcpy(padded + ((size_t)(row + 1) * upload_width + 1) * 4,
pixels + row * stride, (size_t)width * 4);
for (row = 1; row <= canvas_height; row++) {
memcpy(padded + (size_t)row * upload_width * 4,
padded + ((size_t)row * upload_width + 1) * 4, 4);
memcpy(padded + ((size_t)row * upload_width + upload_width - 1) * 4,
padded + ((size_t)row * upload_width + upload_width - 2) * 4,
4);
}
memcpy(padded, padded + (size_t)upload_width * 4,
(size_t)upload_width * 4);
memcpy(padded + (size_t)(upload_height - 1) * upload_width * 4,
padded + (size_t)(upload_height - 2) * upload_width * 4,
(size_t)upload_width * 4);
pixels = padded;
width = canvas_width;
height = canvas_height;
width = upload_width;
height = upload_height;
image_size = (size_t)width * height * 4;
stride = (size_t)width * 4;
for (offset = 0; offset < image_size; offset += size) {
@@ -387,6 +415,7 @@ kitty_draw_rect(struct tty *tty, const struct image_rect *rectangle,
struct image *im;
u_int source_x, source_y;
u_int width, height, destination_x, destination_y;
int32_t z;
im = image_rect_get_image(rectangle);
kitty_images_collect(tty);
@@ -395,8 +424,9 @@ kitty_draw_rect(struct tty *tty, const struct image_rect *rectangle,
return;
image_rect_get_coords(rectangle, &source_x, &source_y, &width,
&height, &destination_x, &destination_y);
z = image_rect_get_z(rectangle);
kitty_place(tty, cache, im, source_x, source_y, width, height,
destination_x, destination_y);
destination_x, destination_y, z);
}
/* Parse an unsigned Kitty graphics control value. */
@@ -418,6 +448,25 @@ kitty_number(const char *s, size_t len, u_int *value)
return (0);
}
/* Parse a signed Kitty graphics control value. */
static int
kitty_signed_number(const char *s, size_t len, int32_t *value)
{
char copy[32];
const char *errstr;
long long ll;
if (len == 0 || len >= sizeof copy)
return (-1);
memcpy(copy, s, len);
copy[len] = '\0';
ll = strtonum(copy, INT32_MIN, INT32_MAX, &errstr);
if (errstr != NULL)
return (-1);
*value = ll;
return (0);
}
/* Parse a Kitty graphics control string. */
static int
kitty_control(struct kitty_state *ks, const u_char *buf, size_t len)
@@ -456,6 +505,11 @@ kitty_control(struct kitty_state *ks, const u_char *buf, size_t len)
case 'o':
ks->compression = value[0];
break;
case 'z':
if (kitty_signed_number((const char *)value, valuelen,
&ks->z) != 0)
return (-1);
break;
case 'f':
case 's':
case 'v':
@@ -534,6 +588,26 @@ kitty_placements_free_all(struct kitty_context *kc)
kitty_placements_free(source);
}
/* Remove all parser-side placements at a Kitty z-index. */
static void
kitty_placements_remove_z(struct kitty_context *kc, int32_t z)
{
struct kitty_source *source;
struct kitty_placement **pp, *placement;
for (source = kc->sources; source != NULL; source = source->next) {
for (pp = &source->placements; (placement = *pp) != NULL; ) {
if (placement->z != z) {
pp = &placement->next;
continue;
}
*pp = placement->next;
image_free(placement->server_id);
free(placement);
}
}
}
/* Free Kitty graphics parser state. */
void
kitty_free_state(void *state)
@@ -600,7 +674,7 @@ kitty_source_set(struct kitty_context *kc, u_int id, struct image *im)
/* Associate a placement ID with an image. */
static u_int
kitty_placement_set(struct kitty_context *kc, u_int image_id,
u_int placement_id, struct image *im)
u_int placement_id, int32_t z, struct image *im)
{
struct kitty_source *source;
struct kitty_placement *placement;
@@ -623,6 +697,7 @@ kitty_placement_set(struct kitty_context *kc, u_int image_id,
source->placements = placement;
}
old_id = placement->server_id;
placement->z = z;
image_ref(image_get_id(im));
placement->server_id = image_get_id(im);
if (old_id != 0)
@@ -864,7 +939,7 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
struct image *
kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
u_int ypixel, u_int *image_id, u_int *replace_id, u_int *quiet,
char *action, int *status)
char *action, char *delete, u_int *placement_id, int32_t *z, int *status)
{
struct kitty_context *kc = *state;
struct kitty_state *ks;
@@ -886,6 +961,9 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
*replace_id = 0;
*quiet = 0;
*action = '\0';
*delete = '\0';
*placement_id = 0;
*z = 0;
*status = KITTY_PARSE_ERROR;
ks = kc->transfer;
semi = memchr(buf, ';', len);
@@ -902,6 +980,9 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
*image_id = ks->image_id;
*quiet = ks->quiet;
*action = ks->action;
*delete = ks->delete;
*placement_id = ks->placement_id;
*z = ks->z;
if (kitty_control(ks, buf, controllen) != 0 ||
ks->medium != 'd' ||
(payloadlen != 0 &&
@@ -911,6 +992,9 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
*image_id = ks->image_id;
*quiet = ks->quiet;
*action = ks->action;
*delete = ks->delete;
*placement_id = ks->placement_id;
*z = ks->z;
if (ks->more) {
kc->transfer = ks;
*status = KITTY_PARSE_MORE;
@@ -937,7 +1021,7 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
image_free(image_get_id(source));
if (im != NULL) {
*replace_id = kitty_placement_set(kc, ks->image_id,
ks->placement_id, im);
ks->placement_id, ks->z, im);
*status = KITTY_PARSE_OK;
}
}
@@ -963,7 +1047,10 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
}
} else if (ks->delete == 'I')
im = kitty_source_remove(kc, ks->image_id);
else
else if (ks->delete == 'z' || ks->delete == 'Z') {
kitty_placements_remove_z(kc, ks->z);
im = NULL;
} else
goto fail;
if ((ks->delete == 'i' || ks->delete == 'I') &&
im == NULL)
@@ -1040,7 +1127,7 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
*status = KITTY_PARSE_ERROR;
else if (ks->placement_id != 0)
(void)kitty_placement_set(kc, ks->image_id,
ks->placement_id, im);
ks->placement_id, ks->z, im);
} else if (ks->virtual) {
im = kitty_place_image(source, ks, xpixel, ypixel);
if (im == NULL)
@@ -1119,10 +1206,11 @@ kitty_placeholder_index(uint32_t value, u_int *index)
return (0);
}
/* Convert a Kitty Unicode placeholder cell into an image marker. */
/* Resolve a Kitty Unicode placeholder to an image and source cell. */
int
kitty_placeholder_to_cell(void *state, struct grid_cell *gc,
const struct grid_cell *left)
kitty_placeholder_to_image(void *state, struct grid *gd, struct grid_cell *gc,
u_int grid_x, u_int grid_y, struct image **image, u_int *source_x,
u_int *source_y, u_int *image_id, u_int *placement_id, int32_t *z)
{
struct kitty_context *kc = state;
struct kitty_source *source;
@@ -1130,8 +1218,7 @@ kitty_placeholder_to_cell(void *state, struct grid_cell *gc,
uint32_t value;
size_t offset = 0;
u_int values[3], nvalues = 0, id, x, y, sx, sy;
struct utf8_data data;
int fg, bg, us;
u_int left_x, left_y;
if (kc == NULL ||
!kitty_placeholder_character(gc->data.data, gc->data.size, &offset,
@@ -1166,29 +1253,33 @@ kitty_placeholder_to_cell(void *state, struct grid_cell *gc,
if (nvalues >= 1)
y = values[0];
else if (left != NULL && left->flags & GRID_FLAG_IMAGE &&
left->image_id == image_get_id(im))
y = left->image_y;
else
return (0);
else {
if (grid_x == 0 || !image_grid_get_source(gd, grid_x - 1,
gd->hsize + grid_y, im, &left_x, &left_y))
return (0);
y = left_y;
}
if (nvalues >= 2)
x = values[1];
else if (left != NULL && left->flags & GRID_FLAG_IMAGE &&
left->image_id == image_get_id(im) && left->image_x != UINT_MAX)
x = left->image_x + 1;
else
return (0);
else {
if (grid_x == 0 || !image_grid_get_source(gd, grid_x - 1,
gd->hsize + grid_y, im, &left_x, &left_y) ||
left_x == UINT_MAX)
return (0);
x = left_x + 1;
}
if (x >= sx || y >= sy)
return (0);
utf8_copy(&data, &gc->data);
fg = gc->fg;
bg = gc->bg;
us = gc->us;
image_set_cell(gc, im, x, y);
utf8_copy(&gc->data, &data);
gc->fg = fg;
gc->bg = bg;
gc->us = us;
*image = im;
*source_x = x;
*source_y = y;
*image_id = id;
if (gc->us & COLOUR_FLAG_RGB)
*placement_id = gc->us & 0xffffff;
else
*placement_id = 0;
*z = 0;
utf8_set(&gc->data, ' ');
return (1);
}

View File

@@ -603,6 +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;
u_int x, y, i;
/*
@@ -625,10 +626,19 @@ sixel_scale(struct sixel_image *si, u_int cell_w, u_int cell_h, u_int ox,
if (cell_h == 0)
cell_h = si->cell_h;
pox = ox * si->cell_w;
poy = oy * si->cell_h;
psx = sx * si->cell_w;
psy = sy * si->cell_h;
/*
* Map cell boundaries over the actual raster, not the rounded-up cell
* canvas. Otherwise a raster shorter than its last cell row produces an
* empty strip when it is scaled for output.
*/
x0 = (uint64_t)ox * si->sx / cx;
x1 = (uint64_t)(ox + sx) * si->sx / cx;
y0 = (uint64_t)oy * si->sy / cy;
y1 = (uint64_t)(oy + sy) * si->sy / cy;
pox = x0;
poy = y0;
psx = x1 - x0;
psy = y1 - y0;
tsx = sx * cell_w;
tsy = sy * cell_h;
@@ -1445,6 +1455,21 @@ sixel_draw_rect(struct tty *tty, const struct image_rect *rectangle,
free(data);
}
/* Remove old SIXEL pixels before replaying a dirty image area. */
void
sixel_redraw_start(struct tty *tty, u_int x, u_int y, u_int sx, u_int sy)
{
u_int yy;
for (yy = y; yy < y + sy; yy++) {
tty_cursor(tty, x, yy);
if (tty_term_has(tty->term, TTYC_ECH))
tty_putcode_i(tty, TTYC_ECH, sx);
else
tty_repeat_space(tty, sx);
}
}
/* Convert a SIXEL image to a fallback screen. */
struct screen *
sixel_to_screen(struct sixel_image *si)

955
image.c

File diff suppressed because it is too large Load Diff

41
input.c
View File

@@ -947,6 +947,7 @@ input_reset(struct input_ctx *ictx, int clear)
screen_write_start_pane(sctx, wp, &wp->base);
else
screen_write_start(sctx, &wp->base);
sctx->flags |= SCREEN_WRITE_INPUT;
screen_write_reset(sctx);
screen_write_stop(sctx);
}
@@ -1074,6 +1075,7 @@ input_parse_buffer(struct window_pane *wp, const u_char *buf, size_t len)
screen_write_start_pane(sctx, wp, &wp->base);
else
screen_write_start(sctx, &wp->base);
sctx->flags |= SCREEN_WRITE_INPUT;
log_debug("%s: %%%u %s, %zu bytes: %.*s", __func__, wp->id,
ictx->state->name, len, (int)len, buf);
@@ -1093,6 +1095,7 @@ input_parse_screen(struct input_ctx *ictx, struct screen *s,
return;
screen_write_start_callback(sctx, s, cb, arg);
sctx->flags |= SCREEN_WRITE_INPUT;
input_parse(ictx, buf, len);
screen_write_stop(sctx);
}
@@ -2809,7 +2812,9 @@ input_handle_kitty(struct input_ctx *ictx, const u_char *buf, size_t len)
struct window_pane *wp = ictx->wp;
struct image *im;
u_int image_id = 0, replace_id = 0, quiet = 0;
char action = '\0';
u_int placement_id = 0;
int32_t z = 0;
char action = '\0', delete = '\0';
int status;
if (wp == NULL)
@@ -2817,7 +2822,7 @@ input_handle_kitty(struct input_ctx *ictx, const u_char *buf, size_t len)
im = kitty_parse_image(&ictx->kitty_state, buf, len,
wp->window->xpixel,
wp->window->ypixel, &image_id, &replace_id, &quiet, &action,
&status);
&delete, &placement_id, &z, &status);
if (status == KITTY_PARSE_MORE)
return (1);
if (status != KITTY_PARSE_OK) {
@@ -2835,14 +2840,15 @@ input_handle_kitty(struct input_ctx *ictx, const u_char *buf, size_t len)
image_clear(sctx, replace_id);
if (im != NULL) {
if (action == 'd')
image_clear(sctx, image_get_id(im));
image_clear_kitty(sctx, delete, image_id, placement_id, z);
else
image_write(sctx, im, ictx->cell.cell.bg);
image_write_kitty(sctx, im, ictx->cell.cell.bg, image_id,
placement_id, z);
image_free(image_get_id(im));
if (quiet == 0 && image_id != 0)
input_reply(ictx, 0, "\033_Gi=%u;OK\033\\", image_id);
} else if (action == 'd' && image_id == 0)
image_clear(sctx, 0);
} else if (action == 'd')
image_clear_kitty(sctx, delete, image_id, placement_id, z);
else if ((action == 't' || action == 'q' || action == 'u') && quiet == 0)
input_reply(ictx, 0, "\033_Gi=%u;OK\033\\", image_id);
return (1);
@@ -2927,9 +2933,10 @@ input_top_bit_set(struct input_ctx *ictx)
struct screen_write_ctx *sctx = &ictx->ctx;
struct utf8_data *ud = &ictx->utf8data;
#ifdef ENABLE_IMAGES
struct grid_cell gc, left;
const struct grid_cell *gcl;
u_int x;
struct grid_cell gc;
struct image *im;
u_int x, source_x, source_y, image_id, placement_id;
int32_t z;
#endif
ictx->flags &= ~INPUT_LAST;
@@ -2962,18 +2969,12 @@ input_top_bit_set(struct input_ctx *ictx)
if (sctx->s->cx != 0) {
x = sctx->s->cx - 1; /* cx-1 is the cell just written. */
grid_view_get_cell(sctx->s->grid, x, sctx->s->cy, &gc);
/* The preceding cell provides context for implicit coordinates. */
if (x == 0) {
gcl = NULL;
} else {
grid_view_get_cell(sctx->s->grid, x - 1, sctx->s->cy, &left);
gcl = &left;
}
/* Convert a recognized Kitty placeholder in gc to an image marker. */
if (kitty_placeholder_to_cell(ictx->kitty_state, &gc, gcl)) {
if (kitty_placeholder_to_image(ictx->kitty_state,
sctx->s->grid, &gc, x, sctx->s->cy, &im, &source_x,
&source_y, &image_id, &placement_id, &z)) {
grid_view_set_cell(sctx->s->grid, x, sctx->s->cy, &gc);
image_redraw_area(sctx, x, sctx->s->cy, 1, 1);
image_place_cell_kitty(sctx, im, x, sctx->s->cy, source_x,
source_y, image_id, placement_id, z);
}
}
#endif

View File

@@ -1,6 +1,6 @@
#!/bin/sh
# Grid-resident image markers: Kitty input, history, copy and overwrite.
# Grid-resident image layers: Kitty input, history, copy and overwrite.
PATH=/bin:/usr/bin
TERM=screen
@@ -30,7 +30,7 @@ sleep 1
$TMUX capture-pane -pS- >$TMP || exit 1
grep '_G' $TMP >/dev/null && exit 1
# Copy mode duplicates the backing grid, including image marker references.
# Copy mode duplicates the backing grid, including image layers.
$TMUX copy-mode || exit 1
$TMUX send-keys -X history-top || exit 1
$TMUX capture-pane -p >$TMP || exit 1
@@ -63,7 +63,7 @@ $TMUX new-window -d "
sleep 1
[ "$($TMUX display-message -pt:3 '#{cursor_y}')" = 1 ] || exit 1
# SIXEL input reaches the same grid marker and copy-mode paths.
# SIXEL input reaches the same grid layer and copy-mode paths.
$TMUX new-window -d "cat '$FIXTURE'; sleep 10"
sleep 1
[ "$($TMUX display-message -pt:4 '#{cursor_y}')" -gt 0 ] || exit 1
@@ -145,7 +145,7 @@ $TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ "$(sed -n 2p $TMP)" = "*" ] || exit 1
[ "$(sed -n 3p $TMP)" = "@" ] || exit 1
# Image marker rows remain cell-aligned when a narrower terminal causes text
# Image rows remain cell-aligned when a narrower terminal causes text
# reflow. The ten-column rows are clipped to five columns, not split into four
# wrapped rows.
$TMUX2 new-window -d "
@@ -225,7 +225,7 @@ $TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ -z "$(sed -n 2p $TMP)" ] || exit 1
[ "$(sed -n 4p $TMP)" = " @@" ] || exit 1
# Image markers retain the cells beneath them. Deleting a transparent image
# Image layers retain the cells beneath them. Deleting a transparent image
# must reveal the original text rather than replacing it with spaces.
PLACEMENT_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
printf 'XY'
@@ -259,6 +259,44 @@ $TMUX capture-pane -pS0 -E1 >$TMP || exit 1
[ "$(sed -n 1p $TMP)" = "test" ] || exit 1
[ "$(sed -n 2p $TMP)" = "test" ] || exit 1
# Kitty z-indexes and SIXEL's temporal plane are properties of the input, not
# of the outer terminal. Exercise the logical scene through the fallback
# backend: higher nonnegative z-indexes cover lower ones and text, ordinary
# negative z-indexes cover backgrounds but not text, and very negative ones
# remain below the background.
Z_WINDOW=$($TMUX2 new-window -dP -F '#{window_id}' "
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=21;/wAA/w==\033\\'
printf '\033_Ga=t,q=2,f=32,s=1,v=1,i=22;/////w==\033\\'
printf 'X\r'
printf '\033_Ga=p,q=2,C=1,i=21,p=1,z=2,c=1,r=1\033\\'
printf '\033_Ga=p,q=2,C=1,i=22,p=2,z=1,c=1,r=1\033\\'
printf '\033[2;1H\033[41m \033[0m\r'
printf '\033_Ga=p,q=2,C=1,i=21,p=3,z=-1,c=1,r=1\033\\'
printf '\033[3;1HY\r'
printf '\033_Ga=p,q=2,C=1,i=21,p=4,z=-1,c=1,r=1\033\\'
printf '\033[4;1H\033[41m \033[0m\r'
printf '\033_Ga=p,q=2,C=1,i=21,p=5,z=-1073741825,c=1,r=1\033\\'
$TEST_TMUX wait-for image-z-top-$$
printf '\033_Ga=d,d=z,q=2,z=2\033\\'
$TEST_TMUX wait-for image-z-lower-$$
printf '\033_Ga=d,d=z,q=2,z=1\033\\'
sleep 10") || exit 1
$TMUX2 select-window -t"$Z_WINDOW" || exit 1
sleep 1
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ "$(sed -n 1p $TMP)" = "." ] || exit 1
[ "$(sed -n 2p $TMP)" = "." ] || exit 1
[ "$(sed -n 3p $TMP)" = "Y" ] || exit 1
[ -z "$(sed -n 4p $TMP)" ] || exit 1
$TMUX2 wait-for -S image-z-top-$$ || exit 1
sleep 1
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
[ "$(sed -n 1p $TMP)" = "@" ] || exit 1
$TMUX2 wait-for -S image-z-lower-$$ || exit 1
sleep 1
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
[ "$(sed -n 1p $TMP)" = "X" ] || exit 1
# A weighted median at the maximum channel level must still leave colours on
# both sides of the split. This skewed black, grey and white image used to stop
# palette generation after one colour instead of producing three.
@@ -277,4 +315,22 @@ $TMUX send-keys Enter || exit 1
sleep 1
grep -a '#2;2;' $TMP >/dev/null || exit 1
# A pane redraw on a SIXEL terminal must not clear the status line, which is
# outside the window scene and may not be redrawn after the pane scrolls.
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
$TMUX2 new-session -d -x 20 -y 8 || exit 1
$TMUX2 set -g 'status-format[0]' 'STATUS' || exit 1
$TMUX2 set -as terminal-features ',*:sixel' || exit 1
$TMUX new-session -d -x 20 -y 8 || 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
$TMUX2 send-keys -l 'seq 30' || exit 1
$TMUX2 send-keys Enter || exit 1
sleep 1
$TMUX capture-pane -p >$TMP || exit 1
[ "$(tail -n 1 $TMP)" = "STATUS" ] || exit 1
exit 0

View File

@@ -1969,8 +1969,12 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
}
tty_sync_start(tty);
#ifdef ENABLE_IMAGES
if (wp == NULL && (flags & REDRAW_PANE))
image_redraw_start(tty, 0, 0, tty->sx, tty->sy);
if (wp == NULL && (flags & REDRAW_PANE)) {
y = 0;
if (dctx.flags & REDRAW_STATUS_TOP)
y = dctx.status_lines;
image_redraw_start(tty, 0, y, scene->sx, scene->sy);
}
#endif
tty_update_mode(tty, tty->mode & ~CURSOR_MODES, NULL);

View File

@@ -34,6 +34,10 @@ static void screen_write_collect_clear(struct screen_write_ctx *, u_int,
static void screen_write_collect_scroll(struct screen_write_ctx *, u_int);
static void screen_write_collect_flush(struct screen_write_ctx *, int,
const char *);
#ifdef ENABLE_IMAGES
static void screen_write_image_damage(struct screen_write_ctx *, u_int,
u_int, u_int, u_int);
#endif
static int screen_write_overwrite(struct screen_write_ctx *,
struct grid_cell *, u_int);
static int screen_write_combine(struct screen_write_ctx *,
@@ -377,6 +381,19 @@ screen_write_init(struct screen_write_ctx *ctx, struct screen *s)
ctx->bg = 8;
}
#ifdef ENABLE_IMAGES
/* Damage temporal images when application input writes text cells. */
static void
screen_write_image_damage(struct screen_write_ctx *ctx, u_int x, u_int y,
u_int sx, u_int sy)
{
image_redraw_area(ctx, x, y, sx, sy);
if (ctx->flags & SCREEN_WRITE_INPUT)
image_grid_damage(ctx->s->grid, x, ctx->s->grid->hsize + y,
sx, sy);
}
#endif
/* Initialize writing with a pane. */
void
screen_write_start_pane(struct screen_write_ctx *ctx, struct window_pane *wp,
@@ -1339,6 +1356,9 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
#ifdef ENABLE_IMAGES
image_redraw_all(ctx);
if (ctx->flags & SCREEN_WRITE_INPUT)
image_grid_damage(s->grid, 0, s->grid->hsize,
screen_size_x(s), screen_size_y(s));
#endif
for (yy = 0; yy < screen_size_y(s); yy++) {
@@ -2580,7 +2600,7 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
}
#ifdef ENABLE_IMAGES
image_redraw_area(ctx, s->cx, s->cy, ci->used, 1);
screen_write_image_damage(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,
@@ -2666,9 +2686,6 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
struct grid_line *gl;
struct grid_cell_entry *gce;
struct grid_cell tmp_gc, now_gc;
#ifdef ENABLE_IMAGES
struct grid_cell image_gc;
#endif
struct tty_ctx ttyctx;
u_int sx = screen_size_x(s), sy = screen_size_y(s);
u_int width = ud->width, xx, not_wrap, i, n, vis;
@@ -2714,18 +2731,8 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
screen_write_initctx(ctx, &ttyctx, 0, 0);
#ifdef ENABLE_IMAGES
/* Update the text underlay without removing an image placement. */
grid_view_get_cell(gd, s->cx, s->cy, &now_gc);
if ((now_gc.flags & GRID_FLAG_IMAGE) &&
(~gc->flags & GRID_FLAG_IMAGE)) {
image_redraw_area(ctx, s->cx, s->cy, width, 1);
memcpy(&image_gc, gc, sizeof image_gc);
image_gc.flags |= GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED;
image_gc.image_id = now_gc.image_id;
image_gc.image_x = now_gc.image_x;
image_gc.image_y = now_gc.image_y;
gc = &image_gc;
}
/* Input semantics decide which image layers this text damages. */
screen_write_image_damage(ctx, s->cx, s->cy, width, 1);
#endif
/* Handle overwriting of UTF-8 characters. */
@@ -2955,6 +2962,9 @@ screen_write_combine(struct screen_write_ctx *ctx, const struct grid_cell *gc)
force_wide = 0;
/* Set the new cell. */
#ifdef ENABLE_IMAGES
screen_write_image_damage(ctx, cx - n, cy, n, 1);
#endif
grid_view_set_cell(gd, cx - n, cy, &last);
if (force_wide)
grid_view_set_padding(gd, cx - 1, cy, last.bg);
@@ -3103,7 +3113,7 @@ screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si,
sixel_free(si);
return;
}
image_write(ctx, im, bg);
image_write_sixel(ctx, im, bg);
image_free(image_get_id(im));
}
#endif

View File

@@ -31,9 +31,6 @@
/* Default style. */
static struct style style_default = {
{ { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0
#ifdef ENABLE_IMAGES
, 0, 0, 0
#endif
},
0,
0,

66
tmux.h
View File

@@ -80,8 +80,8 @@ struct session;
struct image;
struct image_backend;
struct image_rect;
#endif
#ifdef ENABLE_IMAGES
struct image_line;
struct image_store;
struct sixel_image;
#endif
@@ -809,8 +809,6 @@ struct colour_palette {
#define GRID_FLAG_NOPALETTE 0x20
#define GRID_FLAG_CLEARED 0x40
#define GRID_FLAG_TAB 0x80
#define GRID_FLAG_IMAGE 0x100
#define GRID_FLAG_IMAGE_DAMAGED 0x200
/* Grid line flags. */
#define GRID_LINE_WRAPPED 0x1
@@ -871,11 +869,6 @@ struct grid_cell {
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. */
@@ -887,11 +880,6 @@ struct grid_extd_entry {
int bg;
int us;
u_int link;
#ifdef ENABLE_IMAGES
u_short image_id;
u_short image_x;
u_short image_y;
#endif
} __packed;
/* Grid cell entry. */
@@ -929,6 +917,9 @@ struct grid_line {
u_int time;
struct osc133_data osc133_data;
u_short flags;
#ifdef ENABLE_IMAGES
struct image_line *images;
#endif
};
/* Entire grid of cells. */
@@ -948,6 +939,9 @@ struct grid {
u_int scroll_generation;
struct grid_line *linedata;
#ifdef ENABLE_IMAGES
struct image_store *images;
#endif
};
/* Virtual cursor in a grid. */
@@ -1116,6 +1110,7 @@ struct screen_write_ctx {
#define SCREEN_WRITE_SYNC 0x1
#define SCREEN_WRITE_OBSCURED 0x2
#define SCREEN_WRITE_CHECKED_IF_OBSCURED 0x4
#define SCREEN_WRITE_INPUT 0x8
screen_write_init_ctx_cb init_ctx_cb;
void *arg;
@@ -4238,7 +4233,6 @@ char *regsub(const char *, const char *, const char *, int);
/* image.c */
#define IMAGE_BACKEND_GRAPHICAL 0x1
#define IMAGE_BACKEND_SCROLLS 0x2
#define IMAGE_BACKEND_TEMPORAL 0x4
struct image *image_create(u_int, u_int, u_int, u_int, u_int, u_int,
u_char *);
@@ -4246,8 +4240,6 @@ 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);
u_int image_get_id(const struct image *);
u_short image_get_grid_id(u_int);
u_int image_get_id_by_grid_id(u_short);
void image_get_size(const struct image *, u_int *, u_int *);
void image_get_canvas_size(const struct image *, u_int *, u_int *);
void image_get_size_in_cells(const struct image *, u_int *, u_int *);
@@ -4257,9 +4249,10 @@ struct sixel_image *image_get_sixel(const struct image *);
void image_set_sixel(struct image *, struct sixel_image *);
void image_ref(u_int);
void image_free(u_int);
void image_set_cell(struct grid_cell *, struct image *, u_int,
void image_write_sixel(struct screen_write_ctx *, struct image *,
u_int);
void image_write(struct screen_write_ctx *, struct image *, u_int);
void image_write_kitty(struct screen_write_ctx *, struct image *,
u_int, u_int, u_int, int32_t);
void image_get_pixel_rect(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 *,
@@ -4273,8 +4266,6 @@ void image_redraw_all(struct screen_write_ctx *);
void image_redraw_scroll(struct screen_write_ctx *, u_int);
void image_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
int image_backend_flags(struct tty *);
int image_get_draw_cell(struct tty *, const struct grid_cell *,
struct grid_cell *, const struct tty_style_ctx *);
void image_tty_update(struct tty *);
void image_tty_geometry_changed(struct tty *);
void image_tty_free(struct tty *, int);
@@ -4283,20 +4274,44 @@ void image_draw_line(struct tty *, struct screen *, u_int, u_int,
void image_get_fallback_cell(struct tty *, struct image *, u_int,
u_int, const struct grid_cell *, struct grid_cell *,
const struct tty_style_ctx *);
int image_get_fallback_at(struct tty *, struct screen *, u_int,
u_int, const struct grid_cell *, struct grid_cell *,
const struct tty_style_ctx *);
struct image *image_rect_get_image(const struct image_rect *);
const struct grid_cell *image_rect_get_cell(
const struct image_rect *);
void image_rect_get_coords(const struct image_rect *,
u_int *, u_int *, u_int *, u_int *, u_int *, u_int *);
int32_t image_rect_get_z(const struct image_rect *);
void image_clear(struct screen_write_ctx *, u_int);
void image_clear_kitty(struct screen_write_ctx *, char, u_int,
u_int, int32_t);
void image_grid_damage(struct grid *, u_int, u_int, u_int, u_int);
void image_grid_free_line(struct grid *, struct grid_line *);
void image_grid_free(struct grid *);
void image_grid_move_cells(struct grid *, u_int, u_int, u_int,
u_int);
void image_grid_duplicate_lines(struct grid *, u_int, struct grid *,
u_int, u_int);
void image_grid_copy_area(struct grid *, u_int, u_int, struct grid *,
u_int, u_int, u_int, u_int);
int image_grid_line_has_images(const struct grid_line *);
int image_grid_check_area(struct grid *, u_int, u_int, u_int,
u_int);
int image_grid_get_source(struct grid *, u_int, u_int,
struct image *, u_int *, u_int *);
void image_place_cell_kitty(struct screen_write_ctx *, struct image *,
u_int, u_int, u_int, u_int, u_int, u_int, int32_t);
#define KITTY_PARSE_ERROR -1
#define KITTY_PARSE_OK 0
#define KITTY_PARSE_MORE 1
#define KITTY_PARSE_MISSING 2
struct image *kitty_parse_image(void **, const u_char *, size_t, u_int,
u_int, u_int *, u_int *, u_int *, char *, int *);
int kitty_placeholder_to_cell(void *, struct grid_cell *,
const struct grid_cell *);
u_int, u_int *, u_int *, u_int *, char *, char *, u_int *,
int32_t *, int *);
int kitty_placeholder_to_image(void *, struct grid *,
struct grid_cell *, u_int, u_int, struct image **, u_int *,
u_int *, u_int *, u_int *, int32_t *);
void kitty_free_state(void *);
void kitty_draw_rect(struct tty *,
const struct image_rect *, const struct tty_style_ctx *);
@@ -4309,7 +4324,8 @@ void kitty_geometry_changed(struct tty *);
/* image-sixel.c */
#define SIXEL_COLOUR_REGISTERS 1024
void sixel_draw_rect(struct tty *,
const struct image_rect *, const struct tty_style_ctx *);
const struct image_rect *, const struct tty_style_ctx *);
void sixel_redraw_start(struct tty *, u_int, u_int, u_int, u_int);
void sixel_free_output(struct tty *, int);
void sixel_geometry_changed(struct tty *);
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int,

View File

@@ -125,6 +125,7 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
struct grid_cell gc, ngc, last;
#ifdef ENABLE_IMAGES
struct grid_cell image_gc;
int image_status;
#endif
struct grid_line *gl;
u_int i, j, last_i, cx, ex, width;
@@ -250,21 +251,26 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
if (px >= ex || i >= ex - px) {
/* Outside the area being drawn. */
empty = nx - i;
gcp = &grid_default_cell;
#ifdef ENABLE_IMAGES
image_status = image_get_fallback_at(tty, s, px + i,
py, gcp, &image_gc, style_ctx);
if (image_status == 1) {
gcp = &image_gc;
empty = 0;
} else if (image_status == -1)
empty = 1;
else
#endif
empty = nx - i;
} else {
/* Get the current cell. */
grid_view_get_cell(gd, px + i, py, &gc);
#ifdef ENABLE_IMAGES
if (gc.flags & GRID_FLAG_IMAGE) {
(void)image_get_draw_cell(tty, &gc, &image_gc,
style_ctx);
gcp = &image_gc;
} else
gcp = &gc;
#else
gcp = &gc;
#ifdef ENABLE_IMAGES
if (image_get_fallback_at(tty, s, px + i, py, &gc,
&image_gc, style_ctx) == 1)
gcp = &image_gc;
#endif
/* Work out empty cells. */

11
tty.c
View File

@@ -2132,9 +2132,6 @@ 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;
#endif
/* Skip last character if terminal is stupid. */
if ((tty->term->flags & TERM_NOAM) &&
@@ -2150,14 +2147,6 @@ tty_cell(struct tty *tty, const struct grid_cell *gc,
if (!tty_check_overlay(tty, tty->cx, tty->cy))
return;
#ifdef ENABLE_IMAGES
if (gc->flags & GRID_FLAG_IMAGE) {
if (image_get_draw_cell(tty, gc, &image_gc, style_ctx))
return;
gc = &image_gc;
}
#endif
/* Check the output codeset and apply attributes. */
gcp = tty_check_codeset(tty, gc);
tty_attributes(tty, gcp, style_ctx);

View File

@@ -5408,6 +5408,14 @@ window_copy_write_line(struct window_mode_entry *wme,
window_copy_write_one(wme, ctx, width, py, hsize - data->oy + py,
content_sx, &mgc, &cgc, &mkgc, &clgc);
#ifdef ENABLE_IMAGES
/* Copy the backing line's image layers separately from its text cells. */
image_grid_free_line(s->grid,
&s->grid->linedata[s->grid->hsize + py]);
image_grid_copy_area(s->grid, width, s->grid->hsize + py,
data->backing->grid, 0, hsize - data->oy + py, content_sx, 1);
#endif
if (py == 0 && s->rupper < s->rlower && !data->hide_position) {
value = options_get_string(oo, "copy-mode-position-format");
if (*value != '\0') {
@@ -6188,9 +6196,7 @@ 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_IMAGE)
utf8_set(&ud, ' ');
else if (gc.flags & GRID_FLAG_TAB)
if (gc.flags & GRID_FLAG_TAB)
utf8_set(&ud, '\t');
else
utf8_copy(&ud, &gc.data);