Keep SIXEL palettes across redraw spans

This commit is contained in:
Michael Grant
2026-08-04 08:25:34 +01:00
parent 5fc24e373a
commit e602dfa771
4 changed files with 189 additions and 9 deletions

View File

@@ -72,6 +72,22 @@ struct sixel_chunk {
char *data;
};
struct sixel_image_cache {
u_int server_id;
u_int xpixel;
u_int ypixel;
size_t size;
uint64_t age;
struct sixel_image *si;
struct sixel_image_cache *next;
};
struct sixel_output {
struct sixel_image_cache *images;
size_t size;
uint64_t age;
};
static int
sixel_parse_expand_lines(struct sixel_image *si, u_int y)
{
@@ -499,6 +515,8 @@ sixel_to_image(struct sixel_image *si)
sx, sy, pixels);
if (im == NULL)
free(pixels);
else
im->sixel = si;
return (im);
}
#endif
@@ -1124,21 +1142,175 @@ fail:
return (NULL);
}
static struct sixel_output *
sixel_get_output(struct tty *tty)
{
struct sixel_output *so = tty->image_data;
if (so == NULL) {
so = xcalloc(1, sizeof *so);
tty->image_data = so;
}
return (so);
}
static size_t
sixel_image_size(struct sixel_image *si)
{
uint64_t size;
if ((uint64_t)si->x * si->y > SIZE_MAX / sizeof(uint16_t))
return (0);
size = (uint64_t)si->x * si->y * sizeof(uint16_t);
if ((uint64_t)si->ncolours * sizeof *si->colours > SIZE_MAX - size)
return (0);
size += (uint64_t)si->ncolours * sizeof *si->colours;
if (size > SIZE_MAX)
return (0);
return (size);
}
static void
sixel_remove_cache(struct sixel_output *so, struct sixel_image_cache **pp)
{
struct sixel_image_cache *cache = *pp;
*pp = cache->next;
so->size -= cache->size;
sixel_free(cache->si);
free(cache);
}
static void
sixel_collect_images(struct sixel_output *so)
{
struct sixel_image_cache **pp, *cache;
for (pp = &so->images; (cache = *pp) != NULL; ) {
if (image_find(cache->server_id) == NULL)
sixel_remove_cache(so, pp);
else
pp = &cache->next;
}
}
void
sixel_free_output(struct tty *tty, __unused int send)
{
struct sixel_output *so = tty->image_data;
struct sixel_image_cache *cache, *next;
if (so == NULL)
return;
for (cache = so->images; cache != NULL; cache = next) {
next = cache->next;
sixel_free(cache->si);
free(cache);
}
free(so);
tty->image_data = NULL;
}
void
sixel_geometry_changed(struct tty *tty)
{
sixel_free_output(tty, !!(tty->flags & TTY_OPENED));
}
static struct sixel_image *
sixel_render_image(struct image *im, u_int xpixel, u_int ypixel)
{
/* Preserve SIXEL's original palette and indexed pixels when possible. */
if (im->sixel != NULL)
return (sixel_scale(im->sixel, xpixel, ypixel, 0, 0,
im->sx, im->sy, 1));
return (sixel_from_image(im, 0, 0, im->sx, im->sy, xpixel, ypixel));
}
static struct sixel_image *
sixel_get_image(struct tty *tty, struct image *im)
{
struct sixel_output *so = sixel_get_output(tty);
struct sixel_image_cache **pp, *cache, **oldest;
struct sixel_image *si;
size_t size;
sixel_collect_images(so);
for (cache = so->images; cache != NULL; cache = cache->next) {
if (cache->server_id != im->id || cache->xpixel != tty->xpixel ||
cache->ypixel != tty->ypixel)
continue;
cache->age = ++so->age;
return (cache->si);
}
si = sixel_render_image(im, tty->xpixel, tty->ypixel);
if (si == NULL)
return (NULL);
size = sixel_image_size(si);
if (size == 0 || size > IMAGE_SIZE_LIMIT) {
/* The renderer still has a usable image, but it is not cacheable. */
return (si);
}
while (so->size > IMAGE_SIZE_LIMIT - size) {
oldest = NULL;
for (pp = &so->images; (cache = *pp) != NULL;
pp = &cache->next) {
if (oldest == NULL || cache->age < (*oldest)->age)
oldest = pp;
}
if (oldest == NULL)
break;
sixel_remove_cache(so, oldest);
}
cache = xcalloc(1, sizeof *cache);
cache->server_id = im->id;
cache->xpixel = tty->xpixel;
cache->ypixel = tty->ypixel;
cache->size = size;
cache->age = ++so->age;
cache->si = si;
cache->next = so->images;
so->images = cache;
so->size += size;
return (si);
}
static int
sixel_image_is_cached(struct tty *tty, struct sixel_image *si)
{
struct sixel_output *so = tty->image_data;
struct sixel_image_cache *cache;
if (so == NULL)
return (0);
for (cache = so->images; cache != NULL; cache = cache->next) {
if (cache->si == si)
return (1);
}
return (0);
}
void
sixel_draw_rectangle(struct tty *tty, const struct image_rectangle *rectangle,
__unused const struct tty_style_ctx *style_ctx)
{
struct sixel_image *si;
struct sixel_image *si, *crop;
char *data;
size_t size;
si = sixel_from_image(rectangle->image, rectangle->source_x,
rectangle->source_y, rectangle->width, rectangle->height,
tty->xpixel, tty->ypixel);
si = sixel_get_image(tty, rectangle->image);
if (si == NULL)
return;
data = sixel_print(si, NULL, &size);
sixel_free(si);
crop = sixel_scale(si, tty->xpixel, tty->ypixel,
rectangle->source_x, rectangle->source_y, rectangle->width,
rectangle->height, 1);
if (!sixel_image_is_cached(tty, si))
sixel_free(si);
if (crop == NULL)
return;
data = sixel_print(crop, NULL, &size);
sixel_free(crop);
if (data == NULL)
return;
tty_region_off(tty);

View File

@@ -50,7 +50,8 @@ static const struct image_backend image_backend_kitty = {
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
"sixel", IMAGE_BACKEND_GRAPHICAL, sixel_draw_rectangle,
sixel_free_output, sixel_geometry_changed
};
static const struct image_backend *
@@ -311,6 +312,8 @@ image_free(u_int id)
free(im->pixels);
else
image_free(im->parent_id);
if (im->sixel != NULL)
sixel_free(im->sixel);
free(im->cells);
free(im);
}

View File

@@ -3081,9 +3081,10 @@ screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si,
struct image *im;
im = sixel_to_image(si);
sixel_free(si);
if (im == NULL)
if (im == NULL) {
sixel_free(si);
return;
}
image_write(ctx, im, bg);
image_free(im->id);
}

4
tmux.h
View File

@@ -1071,6 +1071,8 @@ struct image {
size_t stride;
size_t size;
u_char *pixels;
/* Original indexed SIXEL data, if this image arrived as SIXEL. */
struct sixel_image *sixel;
struct image_cell *cells; /* lazily generated text samples */
RB_ENTRY(image) entry;
@@ -4331,6 +4333,8 @@ 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 *);
void sixel_free_output(struct tty *, int);
void sixel_geometry_changed(struct tty *);
#endif
#ifdef ENABLE_SIXEL