Refactor to centralize image-specific rendering and lifecycle code in image.c and the image-* backends.

This commit is contained in:
Michael Grant
2026-08-03 09:13:45 +01:00
parent f68aad1b41
commit fe938ea657
14 changed files with 643 additions and 380 deletions

1
.gitignore vendored
View File

@@ -25,3 +25,4 @@ regress/logs/
tags
tmux
tmux.1.*
tools/image-ascii-test

View File

@@ -5,7 +5,8 @@ CLEANFILES = tmux.1.mdoc tmux.1.man cmd-parse.c
# Distribution tarball options.
EXTRA_DIST = \
CHANGES README COPYING example_tmux.conf \
osdep-*.c mdoc2man.awk tmux.1
osdep-*.c mdoc2man.awk tmux.1 \
tools/generate-image-diacritics.sh tools/image-ascii-test.c
dist_EXTRA_tmux_SOURCES = compat/*.[ch]
# Preprocessor flags.
@@ -248,8 +249,7 @@ endif
# Enable image support.
if ENABLE_IMAGES
dist_tmux_SOURCES += image.c image-sixel.c image-kitty.c image-ascii.c \
image-diacritics.h
dist_tmux_SOURCES += image.c image-sixel.c image-kitty.c image-ascii.c
endif
# Add bits for fuzzing if enabled.

View File

@@ -18,51 +18,23 @@
#include <sys/types.h>
#include <string.h>
#include "tmux.h"
void
image_make_ascii(struct image *im)
image_get_text_cell(__unused struct tty *tty, struct image *im, u_int x,
u_int y, const struct grid_cell *gc, struct grid_cell *out,
__unused const struct tty_style_ctx *style_ctx)
{
static const char ramp[] = " .:-=+*#%@";
const u_char *pixel;
uint64_t total, count;
u_int x, y, px, py, x0, x1, y0, y1, level;
static const char ramp[] = " .:-=+*#%@";
const struct image_cell *cell;
u_int level = 0;
im->ascii = xcalloc((size_t)im->sx * im->sy, 1);
for (y = 0; y < im->sy; y++) {
y0 = ((uint64_t)y * im->height) / im->sy;
y1 = ((uint64_t)(y + 1) * im->height) / im->sy;
if (y1 <= y0)
y1 = y0 + 1;
for (x = 0; x < im->sx; x++) {
x0 = ((uint64_t)x * im->width) / im->sx;
x1 = ((uint64_t)(x + 1) * im->width) / im->sx;
if (x1 <= x0)
x1 = x0 + 1;
total = count = 0;
for (py = y0; py < y1 && py < im->height; py++) {
for (px = x0; px < x1 && px < im->width; px++) {
pixel = im->pixels + py * im->stride + px * 4;
total += ((2126ULL * pixel[0] +
7152ULL * pixel[1] +
722ULL * pixel[2]) / 10000) *
pixel[3] / 255;
count++;
}
}
if (count == 0)
continue;
level = (total / count) * (sizeof ramp - 2) / 255;
im->ascii[y * im->sx + x] = ramp[level];
}
}
}
const char *
image_get_ascii(struct image *im, u_int x, u_int y)
{
if (im == NULL || x >= im->sx || y >= im->sy)
return (" ");
return (&im->ascii[y * im->sx + x]);
memcpy(out, gc, sizeof *out);
cell = image_get_cell(im, x, y);
if (cell != NULL)
level = cell->whole.brightness * (sizeof ramp - 2) / 255;
utf8_set(&out->data, ramp[level]);
out->flags &= ~GRID_FLAG_IMAGE;
}

View File

@@ -18,7 +18,6 @@
#include <sys/types.h>
#include <limits.h>
#include <png.h>
#include <resolv.h>
#include <stdlib.h>
#include <string.h>
@@ -26,7 +25,7 @@
#include "tmux.h"
/* Generated from Kitty's Unicode 6.0 rowcolumn-diacritics.txt. */
/* Generated from Unicode 6.0.0 by tools/generate-image-diacritics.sh. */
static const uint32_t kitty_diacritics[] = {
0x0305, 0x030D, 0x030E, 0x0310, 0x0312, 0x033D, 0x033E, 0x033F,
0x0346, 0x034A, 0x034B, 0x034C, 0x0350, 0x0351, 0x0352, 0x0357,
@@ -68,8 +67,6 @@ static const uint32_t kitty_diacritics[] = {
0x1D244
};
#define KITTY_IMAGE_LIMIT (64 * 1024 * 1024)
struct kitty_state {
char action;
char delete;
@@ -100,14 +97,32 @@ struct kitty_context {
struct kitty_source *sources;
};
struct tty_image_cache {
struct kitty_image_cache {
u_int server_id;
u_int kitty_id;
u_int xpixel;
u_int ypixel;
struct tty_image_cache *next;
struct kitty_image_cache *next;
};
struct kitty_output {
struct kitty_image_cache *images;
u_int next_id;
};
static struct kitty_output *
kitty_get_output(struct tty *tty)
{
struct kitty_output *ko = tty->image_data;
if (ko == NULL) {
ko = xcalloc(1, sizeof *ko);
ko->next_id = arc4random_uniform(0xffff00) + 1;
tty->image_data = ko;
}
return (ko);
}
static size_t
kitty_utf8(char *out, uint32_t value)
{
@@ -143,25 +158,38 @@ kitty_delete(struct tty *tty, u_int id)
}
void
kitty_images_free(struct tty *tty, int send)
kitty_free_output(struct tty *tty, int send)
{
struct tty_image_cache *cache, *next;
struct kitty_output *ko = tty->image_data;
struct kitty_image_cache *cache, *next;
for (cache = tty->images; cache != NULL; cache = next) {
if (ko == NULL)
return;
for (cache = ko->images; cache != NULL; cache = next) {
next = cache->next;
if (send)
kitty_delete(tty, cache->kitty_id);
free(cache);
}
tty->images = NULL;
free(ko);
tty->image_data = NULL;
}
void
kitty_geometry_changed(struct tty *tty)
{
kitty_free_output(tty, !!(tty->flags & TTY_OPENED));
}
static void
kitty_images_collect(struct tty *tty)
{
struct tty_image_cache **pp, *cache;
struct kitty_output *ko = tty->image_data;
struct kitty_image_cache **pp, *cache;
for (pp = &tty->images; (cache = *pp) != NULL; ) {
if (ko == NULL)
return;
for (pp = &ko->images; (cache = *pp) != NULL; ) {
if (image_find(cache->server_id) != NULL) {
pp = &cache->next;
continue;
@@ -183,23 +211,12 @@ kitty_place(struct tty *tty, struct image *im, u_int id)
rows = im->sy - y;
if (rows > nitems(kitty_diacritics))
rows = nitems(kitty_diacritics);
py = (uint64_t)y * im->height / im->sy;
pheight = (uint64_t)(y + rows) * im->height / im->sy - py;
if (pheight == 0) {
py = im->height - 1;
pheight = 1;
}
for (x = 0; x < im->sx; x += nitems(kitty_diacritics)) {
columns = im->sx - x;
if (columns > nitems(kitty_diacritics))
columns = nitems(kitty_diacritics);
px = (uint64_t)x * im->width / im->sx;
pwidth = (uint64_t)(x + columns) * im->width /
im->sx - px;
if (pwidth == 0) {
px = im->width - 1;
pwidth = 1;
}
image_get_pixel_rectangle(im, x, y, columns, rows, &px,
&py, &pwidth, &pheight);
xsnprintf(control, sizeof control,
"\033_Ga=p,U=1,i=%u,p=%u,x=%u,y=%u,w=%u,h=%u,"
"c=%u,r=%u,q=2\033\\", id, placement++, px, py,
@@ -209,16 +226,17 @@ kitty_place(struct tty *tty, struct image *im, u_int id)
}
}
static struct tty_image_cache *
static struct kitty_image_cache *
kitty_upload(struct tty *tty, struct image *im)
{
struct tty_image_cache *cache;
struct kitty_output *ko = kitty_get_output(tty);
struct kitty_image_cache *cache;
char control[128], encoded[4097];
size_t offset, size;
int encodedlen;
u_int id;
for (cache = tty->images; cache != NULL; cache = cache->next) {
for (cache = ko->images; cache != NULL; cache = cache->next) {
if (cache->server_id != im->id)
continue;
if (cache->xpixel == tty->xpixel &&
@@ -230,11 +248,11 @@ kitty_upload(struct tty *tty, struct image *im)
}
if (cache == NULL) {
cache = xcalloc(1, sizeof *cache);
cache->next = tty->images;
tty->images = cache;
cache->next = ko->images;
ko->images = cache;
}
do {
id = ++tty->image_next_id & 0xffffff;
id = ++ko->next_id & 0xffffff;
} while (id == 0);
cache->server_id = im->id;
cache->kitty_id = id;
@@ -278,7 +296,7 @@ kitty_placement(struct image *im, u_int x, u_int y)
}
static void
kitty_placeholder(struct tty *tty, struct tty_image_cache *cache,
kitty_placeholder(struct tty *tty, struct kitty_image_cache *cache,
struct image *im, u_int x, u_int y, u_int count)
{
char buf[8192], sgr[96];
@@ -307,52 +325,38 @@ kitty_placeholder(struct tty *tty, struct tty_image_cache *cache,
tty_puts(tty, "\033[39;59m");
}
/*
* Draw only the marker cells present in a scene-selected horizontal span.
* Overlay and pane clipping have already been applied by screen-redraw.c.
*/
void
kitty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py,
u_int nx, u_int atx, u_int aty, const struct tty_style_ctx *style_ctx)
kitty_draw_rectangle(struct tty *tty, const struct image_rectangle *rectangle,
const struct tty_style_ctx *style_ctx)
{
struct grid_cell gc, next, draw_gc;
struct image *im;
struct tty_image_cache *cache;
u_int i, run, tile;
struct grid_cell draw_gc;
struct kitty_image_cache *cache;
struct image *im = rectangle->image;
u_int x, y, run, available;
kitty_images_collect(tty);
for (i = 0; i < nx; i += run) {
grid_view_get_cell(s->grid, px + i, py, &gc);
if (~gc.flags & GRID_FLAG_IMAGE) {
run = 1;
continue;
cache = kitty_upload(tty, im);
if (cache == NULL)
return;
memcpy(&draw_gc, &rectangle->cell, sizeof draw_gc);
draw_gc.flags &= ~(GRID_FLAG_IMAGE|GRID_FLAG_SELECTED);
utf8_set(&draw_gc.data, ' ');
for (y = 0; y < rectangle->height; y++) {
for (x = 0; x < rectangle->width; x += run) {
available = nitems(kitty_diacritics) -
(rectangle->source_x + x) %
nitems(kitty_diacritics);
run = rectangle->width - x;
if (run > available)
run = available;
tty_cursor(tty, rectangle->destination_x + x,
rectangle->destination_y + y);
tty_attributes(tty, &draw_gc, style_ctx);
kitty_placeholder(tty, cache, im,
rectangle->source_x + x,
rectangle->source_y + y, run);
tty_reset(tty);
}
im = image_find(gc.image_id);
if (im == NULL) {
run = 1;
continue;
}
tile = gc.image_x / nitems(kitty_diacritics);
for (run = 1; i + run < nx; run++) {
grid_view_get_cell(s->grid, px + i + run, py, &next);
if (~next.flags & GRID_FLAG_IMAGE ||
next.image_id != gc.image_id ||
next.image_y != gc.image_y ||
next.image_x != gc.image_x + run ||
next.image_x / nitems(kitty_diacritics) != tile)
break;
}
cache = kitty_upload(tty, im);
if (cache == NULL)
continue;
tty_cursor(tty, atx + i, aty);
memcpy(&draw_gc, &gc, sizeof draw_gc);
draw_gc.flags &= ~(GRID_FLAG_IMAGE|GRID_FLAG_SELECTED);
utf8_set(&draw_gc.data, ' ');
tty_attributes(tty, &draw_gc, style_ctx);
kitty_placeholder(tty, cache, im, gc.image_x, gc.image_y,
run);
tty_reset(tty);
}
}
@@ -535,8 +539,8 @@ kitty_source_get(struct kitty_context *kc, u_int id)
static int
kitty_append(struct kitty_state *ks, const u_char *buf, size_t len)
{
if (len > KITTY_IMAGE_LIMIT ||
ks->encodedlen > KITTY_IMAGE_LIMIT - len)
if (len > IMAGE_SIZE_LIMIT ||
ks->encodedlen > IMAGE_SIZE_LIMIT - len)
return (-1);
ks->encoded = xrealloc(ks->encoded, ks->encodedlen + len + 1);
memcpy(ks->encoded + ks->encodedlen, buf, len);
@@ -545,54 +549,6 @@ kitty_append(struct kitty_state *ks, const u_char *buf, size_t len)
return (0);
}
static u_char *
kitty_base64(struct kitty_state *ks, size_t *size)
{
u_char *out;
size_t needed;
int result;
if (ks->encodedlen > (SIZE_MAX - 3) / 3 * 4)
return (NULL);
needed = (ks->encodedlen + 3) / 4 * 3;
out = xmalloc(needed == 0 ? 1 : needed);
result = b64_pton(ks->encoded, out, needed);
if (result < 0) {
free(out);
return (NULL);
}
*size = result;
return (out);
}
static u_char *
kitty_png(const u_char *data, size_t size, u_int *width, u_int *height)
{
png_image pi;
u_char *pixels;
memset(&pi, 0, sizeof pi);
pi.version = PNG_IMAGE_VERSION;
if (!png_image_begin_read_from_memory(&pi, data, size))
return (NULL);
pi.format = PNG_FORMAT_RGBA;
if (pi.width == 0 || pi.height == 0 ||
PNG_IMAGE_SIZE(pi) > KITTY_IMAGE_LIMIT) {
png_image_free(&pi);
return (NULL);
}
pixels = xmalloc(PNG_IMAGE_SIZE(pi));
if (!png_image_finish_read(&pi, NULL, pixels, 0, NULL)) {
free(pixels);
png_image_free(&pi);
return (NULL);
}
*width = pi.width;
*height = pi.height;
png_image_free(&pi);
return (pixels);
}
static u_char *
kitty_raw(struct kitty_state *ks, u_char *data, size_t size)
{
@@ -603,7 +559,7 @@ kitty_raw(struct kitty_state *ks, u_char *data, size_t size)
bytes = (ks->format == 24 ? 3 : 4);
if (ks->width == 0 || ks->height == 0 ||
(uint64_t)ks->width * ks->height * bytes > KITTY_IMAGE_LIMIT)
(uint64_t)ks->width * ks->height * bytes > IMAGE_SIZE_LIMIT)
return (NULL);
expected = (size_t)ks->width * ks->height * bytes;
raw = data;
@@ -720,13 +676,14 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
if (ks->action != 'T' && ks->action != 't' && ks->action != 'q')
goto fail;
decoded = kitty_base64(ks, &decodedlen);
decoded = image_base64_decode(ks->encoded, ks->encodedlen,
IMAGE_SIZE_LIMIT, &decodedlen);
if (decoded == NULL)
goto fail;
if (ks->format == 100) {
if (ks->compression == 'z') {
if (ks->data_size == 0 ||
ks->data_size > KITTY_IMAGE_LIMIT) {
ks->data_size > IMAGE_SIZE_LIMIT) {
free(decoded);
goto fail;
}
@@ -746,7 +703,8 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
free(decoded);
goto fail;
}
pixels = kitty_png(decoded, decodedlen, &ks->width, &ks->height);
pixels = image_png_decode(decoded, decodedlen, IMAGE_SIZE_LIMIT,
&ks->width, &ks->height);
free(decoded);
} else if (ks->format == 24 || ks->format == 32) {
pixels = kitty_raw(ks, decoded, decodedlen);
@@ -759,16 +717,11 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
if (pixels == NULL)
goto fail;
sx = ks->columns;
sy = ks->rows;
if (xpixel == 0)
xpixel = 8;
if (ypixel == 0)
ypixel = 16;
if (sx == 0)
sx = (ks->width + xpixel - 1) / xpixel;
if (sy == 0)
sy = (ks->height + ypixel - 1) / ypixel;
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);

View File

@@ -18,6 +18,7 @@
#include <sys/types.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
@@ -401,14 +402,7 @@ sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y)
si->xpixel = 8;
if (si->ypixel == 0)
si->ypixel = 16;
if ((si->x % si->xpixel) == 0)
*x = (si->x / si->xpixel);
else
*x = 1 + (si->x / si->xpixel);
if ((si->y % si->ypixel) == 0)
*y = (si->y / si->ypixel);
else
*y = 1 + (si->y / si->ypixel);
image_size_in_cells(si->x, si->y, si->xpixel, si->ypixel, x, y);
}
#ifdef ENABLE_IMAGES
@@ -740,20 +734,27 @@ sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size)
}
static struct sixel_image *
sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells,
u_int xpixel, u_int ypixel)
sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells_x,
u_int cells_y, u_int xpixel, u_int ypixel)
{
struct sixel_image *si;
const u_char *pixel;
u_int x, y, sx, sy, sourcex, sourcey, sourcey0;
u_int sourcey1;
u_int x, y, sx, sy, sourcex, sourcey;
u_int sourcex0, sourcey0, sourcewidth, sourceheight;
u_int r, g, b, colour, i;
sx = cells * xpixel;
sy = ypixel;
if ((uint64_t)cells_x * xpixel > UINT_MAX ||
(uint64_t)cells_y * ypixel > UINT_MAX)
return (NULL);
sx = cells_x * xpixel;
sy = cells_y * ypixel;
if (sx == 0 || sy == 0 || sx > SIXEL_WIDTH_LIMIT ||
sy > SIXEL_HEIGHT_LIMIT)
return (NULL);
image_get_pixel_rectangle(im, ox, oy, cells_x, cells_y, &sourcex0,
&sourcey0, &sourcewidth, &sourceheight);
if (sourcewidth == 0 || sourceheight == 0)
return (NULL);
si = xcalloc(1, sizeof *si);
si->xpixel = xpixel;
@@ -772,16 +773,11 @@ sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells,
}
for (y = 0; y < sy; y++) {
sourcey0 = (uint64_t)oy * im->height / im->sy;
sourcey1 = (uint64_t)(oy + 1) * im->height / im->sy;
sourcey = sourcey0 +
(uint64_t)y * (sourcey1 - sourcey0) / sy;
sourcey = sourcey0 + (uint64_t)y * sourceheight / sy;
if (sourcey >= im->height)
sourcey = im->height - 1;
for (x = 0; x < sx; x++) {
sourcex = ((uint64_t)ox * im->width / im->sx) +
(uint64_t)x * im->width * cells /
((uint64_t)im->sx * sx);
sourcex = sourcex0 + (uint64_t)x * sourcewidth / sx;
if (sourcex >= im->width)
sourcex = im->width - 1;
pixel = im->pixels + sourcey * im->stride + sourcex * 4;
@@ -801,51 +797,29 @@ sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells,
}
void
sixel_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py,
u_int nx, u_int atx, u_int aty)
sixel_draw_rectangle(struct tty *tty, const struct image_rectangle *rectangle,
__unused const struct tty_style_ctx *style_ctx)
{
struct grid_cell gc, next;
struct image *im;
struct sixel_image *si;
char *data;
size_t size;
u_int i, run;
for (i = 0; i < nx; i += run) {
grid_view_get_cell(s->grid, px + i, py, &gc);
if (~gc.flags & GRID_FLAG_IMAGE) {
run = 1;
continue;
}
im = image_find(gc.image_id);
if (im == NULL) {
run = 1;
continue;
}
for (run = 1; i + run < nx; run++) {
grid_view_get_cell(s->grid, px + i + run, py, &next);
if (~next.flags & GRID_FLAG_IMAGE ||
next.image_id != gc.image_id ||
next.image_y != gc.image_y ||
next.image_x != gc.image_x + run)
break;
}
si = sixel_from_image(im, gc.image_x, gc.image_y, run,
tty->xpixel, tty->ypixel);
if (si == NULL)
continue;
data = sixel_print(si, NULL, &size);
sixel_free(si);
if (data == NULL)
continue;
tty_region_off(tty);
tty_margin_off(tty);
tty_cursor(tty, atx + i, aty);
tty->flags |= TTY_NOBLOCK;
tty_putn(tty, data, size, 0);
tty_invalidate(tty);
free(data);
}
si = sixel_from_image(rectangle->image, rectangle->source_x,
rectangle->source_y, rectangle->width, rectangle->height,
tty->xpixel, tty->ypixel);
if (si == NULL)
return;
data = sixel_print(si, NULL, &size);
sixel_free(si);
if (data == NULL)
return;
tty_region_off(tty);
tty_margin_off(tty);
tty_cursor(tty, rectangle->destination_x, rectangle->destination_y);
tty->flags |= TTY_NOBLOCK;
tty_putn(tty, data, size, 0);
tty_invalidate(tty);
free(data);
}
struct screen *

381
image.c
View File

@@ -19,6 +19,9 @@
#include <sys/types.h>
#include <limits.h>
#include <png.h>
#include <resolv.h>
#include <stdlib.h>
#include <string.h>
@@ -27,6 +30,88 @@
static struct images images = RB_INITIALIZER(&images);
static u_int image_next_id;
#define IMAGE_BACKEND_GRAPHICAL 0x1
#define IMAGE_BACKEND_SCROLLS 0x2
struct image_backend {
const char *name;
int flags;
void (*draw_rectangle)(struct tty *,
const struct image_rectangle *, const struct tty_style_ctx *);
void (*free)(struct tty *, int);
void (*geometry_changed)(struct tty *);
};
static const struct image_backend image_backend_ascii = {
"ascii", IMAGE_BACKEND_SCROLLS, NULL, NULL, NULL
};
static const struct image_backend image_backend_kitty = {
"kitty", IMAGE_BACKEND_GRAPHICAL|IMAGE_BACKEND_SCROLLS,
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
};
static const struct image_backend *
image_tty_find_backend(struct tty *tty)
{
if (tty->term != NULL && tty->term->flags & TERM_KITTY)
return (&image_backend_kitty);
if (tty->term != NULL && tty->term->flags & TERM_SIXEL &&
tty->xpixel != 0 && tty->ypixel != 0)
return (&image_backend_sixel);
return (&image_backend_ascii);
}
void
image_tty_update(struct tty *tty)
{
const struct image_backend *backend;
backend = image_tty_find_backend(tty);
if (tty->image_backend == backend)
return;
if (tty->image_backend != NULL && tty->image_backend->free != NULL)
tty->image_backend->free(tty, !!(tty->flags & TTY_OPENED));
tty->image_data = NULL;
tty->image_backend = backend;
log_debug("%s: %s image backend is %s", __func__,
tty->client->name, backend->name);
}
int
image_tty_is_graphical(struct tty *tty)
{
image_tty_update(tty);
return (!!(tty->image_backend->flags & IMAGE_BACKEND_GRAPHICAL));
}
int
image_tty_scrolls(struct tty *tty)
{
image_tty_update(tty);
return (!!(tty->image_backend->flags & IMAGE_BACKEND_SCROLLS));
}
void
image_tty_geometry_changed(struct tty *tty)
{
image_tty_update(tty);
if (tty->image_backend->geometry_changed != NULL)
tty->image_backend->geometry_changed(tty);
}
void
image_tty_free(struct tty *tty, int send)
{
if (tty->image_backend != NULL && tty->image_backend->free != NULL)
tty->image_backend->free(tty, send);
tty->image_backend = NULL;
tty->image_data = NULL;
}
static int
image_cmp(struct image *a, struct image *b)
{
@@ -38,6 +123,79 @@ image_cmp(struct image *a, struct image *b)
}
RB_GENERATE_STATIC(images, image, entry, image_cmp);
static void
image_sample(struct image *im, uint64_t sample_x, uint64_t sample_y,
uint64_t sample_columns, uint64_t sample_rows, struct image_sample *sample)
{
const u_char *pixel;
uint64_t red = 0, green = 0, blue = 0, alpha = 0;
uint64_t brightness = 0, count = 0;
u_int x, y, x0, x1, y0, y1;
x0 = sample_x * im->width / sample_columns;
x1 = (sample_x + 1) * im->width / sample_columns;
y0 = sample_y * im->height / sample_rows;
y1 = (sample_y + 1) * im->height / sample_rows;
if (x1 <= x0)
x1 = x0 + 1;
if (y1 <= y0)
y1 = y0 + 1;
if (x1 > im->width)
x1 = im->width;
if (y1 > im->height)
y1 = im->height;
for (y = y0; y < y1; y++) {
for (x = x0; x < x1; x++) {
pixel = im->pixels + y * im->stride + x * 4;
red += pixel[0] * pixel[3] / 255;
green += pixel[1] * pixel[3] / 255;
blue += pixel[2] * pixel[3] / 255;
alpha += pixel[3];
brightness += ((2126ULL * pixel[0] +
7152ULL * pixel[1] + 722ULL * pixel[2]) / 10000) *
pixel[3] / 255;
count++;
}
}
if (count == 0)
return;
sample->red = red / count;
sample->green = green / count;
sample->blue = blue / count;
sample->alpha = alpha / count;
sample->brightness = brightness / count;
}
static void
image_make_cells(struct image *im)
{
struct image_cell *cell;
uint64_t columns, rows;
u_int x, y, sample_x, sample_y;
columns = (uint64_t)im->sx * IMAGE_SAMPLE_COLUMNS;
rows = (uint64_t)im->sy * IMAGE_SAMPLE_ROWS;
im->cells = xcalloc((size_t)im->sx * im->sy, sizeof *im->cells);
for (y = 0; y < im->sy; y++) {
for (x = 0; x < im->sx; x++) {
cell = &im->cells[(size_t)y * im->sx + x];
image_sample(im, x, y, im->sx, im->sy, &cell->whole);
for (sample_y = 0; sample_y < IMAGE_SAMPLE_ROWS;
sample_y++) {
for (sample_x = 0;
sample_x < IMAGE_SAMPLE_COLUMNS; sample_x++) {
image_sample(im,
(uint64_t)x * IMAGE_SAMPLE_COLUMNS + sample_x,
(uint64_t)y * IMAGE_SAMPLE_ROWS + sample_y,
columns, rows,
&cell->samples[sample_y][sample_x]);
}
}
}
}
}
struct image *
image_find(u_int id)
{
@@ -56,7 +214,7 @@ image_create(u_int width, u_int height, u_int sx, u_int sy, u_char *pixels)
return (NULL);
if ((uint64_t)width * height * 4 > SIZE_MAX)
return (NULL);
if ((uint64_t)sx * sy > SIZE_MAX)
if ((uint64_t)sx * sy > SIZE_MAX / sizeof *im->cells)
return (NULL);
im = xcalloc(1, sizeof *im);
@@ -74,7 +232,6 @@ image_create(u_int width, u_int height, u_int sx, u_int sy, u_char *pixels)
im->stride = (size_t)width * 4;
im->size = im->stride * height;
im->pixels = pixels;
image_make_ascii(im);
RB_INSERT(images, &images, im);
log_debug("%s: image %u is %ux%u pixels, %ux%u cells", __func__,
@@ -107,10 +264,20 @@ image_free(u_int id)
log_debug("%s: freeing image %u", __func__, id);
RB_REMOVE(images, &images, im);
free(im->pixels);
free(im->ascii);
free(im->cells);
free(im);
}
const struct image_cell *
image_get_cell(struct image *im, u_int x, u_int y)
{
if (im == NULL || x >= im->sx || y >= im->sy)
return (NULL);
if (im->cells == NULL)
image_make_cells(im);
return (&im->cells[(size_t)y * im->sx + x]);
}
void
image_set_cell(struct grid_cell *gc, struct image *im, u_int x, u_int y)
{
@@ -121,9 +288,118 @@ image_set_cell(struct grid_cell *gc, struct image *im, u_int x, u_int y)
gc->image_y = y;
}
/* Convert a cell-aligned image rectangle into source pixel coordinates. */
void
image_clear(struct screen *s, u_int id)
image_get_pixel_rectangle(const struct image *im, u_int x, u_int y,
u_int width, u_int height, u_int *px, u_int *py, u_int *pwidth,
u_int *pheight)
{
u_int x1, y1;
*px = *py = *pwidth = *pheight = 0;
if (im == NULL || x >= im->sx || y >= im->sy || width == 0 ||
height == 0)
return;
if (width > im->sx - x)
width = im->sx - x;
if (height > im->sy - y)
height = im->sy - y;
*px = (uint64_t)x * im->width / im->sx;
*py = (uint64_t)y * im->height / im->sy;
x1 = (uint64_t)(x + width) * im->width / im->sx;
y1 = (uint64_t)(y + height) * im->height / im->sy;
if (*px >= im->width)
*px = im->width - 1;
if (*py >= im->height)
*py = im->height - 1;
if (x1 <= *px)
x1 = *px + 1;
if (y1 <= *py)
y1 = *py + 1;
if (x1 > im->width)
x1 = im->width;
if (y1 > im->height)
y1 = im->height;
*pwidth = x1 - *px;
*pheight = y1 - *py;
}
void
image_size_in_cells(u_int width, u_int height, u_int xpixel, u_int ypixel,
u_int *sx, u_int *sy)
{
if (xpixel == 0)
xpixel = 8;
if (ypixel == 0)
ypixel = 16;
*sx = ((uint64_t)width + xpixel - 1) / xpixel;
*sy = ((uint64_t)height + ypixel - 1) / ypixel;
}
u_char *
image_base64_decode(const char *data, size_t len, size_t limit, size_t *size)
{
char *copy;
u_char *out;
size_t needed;
int result;
if (len > SIZE_MAX - 3)
return (NULL);
needed = (len + 3) / 4 * 3;
if (needed > limit || needed > INT_MAX)
return (NULL);
copy = xmalloc(len + 1);
memcpy(copy, data, len);
copy[len] = '\0';
out = xmalloc(needed == 0 ? 1 : needed);
result = b64_pton(copy, out, needed);
free(copy);
if (result < 0) {
free(out);
return (NULL);
}
*size = result;
return (out);
}
u_char *
image_png_decode(const u_char *data, size_t size, size_t limit, u_int *width,
u_int *height)
{
png_image pi;
u_char *pixels;
if (size == 0 || size > limit)
return (NULL);
memset(&pi, 0, sizeof pi);
pi.version = PNG_IMAGE_VERSION;
if (!png_image_begin_read_from_memory(&pi, data, size))
return (NULL);
pi.format = PNG_FORMAT_RGBA;
if (pi.width == 0 || pi.height == 0 ||
(uint64_t)pi.width * pi.height * 4 > limit) {
png_image_free(&pi);
return (NULL);
}
pixels = xmalloc(PNG_IMAGE_SIZE(pi));
if (!png_image_finish_read(&pi, NULL, pixels, 0, NULL)) {
free(pixels);
png_image_free(&pi);
return (NULL);
}
*width = pi.width;
*height = pi.height;
png_image_free(&pi);
return (pixels);
}
void
image_clear(struct screen_write_ctx *ctx, u_int id)
{
struct screen *s = ctx->s;
struct grid *gd = s->grid;
struct grid_cell gc;
u_int x, y;
@@ -132,24 +408,33 @@ image_clear(struct screen *s, u_int id)
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))
(id == 0 || gc.image_id == id)) {
if (y >= gd->hsize)
image_damage_area(ctx, x, y - gd->hsize,
1, 1);
grid_set_cell(gd, x, y, &grid_default_cell);
}
}
}
}
int
static int
image_check_area(struct screen *s, u_int px, u_int py, u_int nx, u_int ny)
{
struct grid_cell gc;
u_int x, y, ex, ey;
u_int sx = screen_size_x(s), sy = screen_size_y(s);
ex = px + nx;
if (ex > screen_size_x(s))
ex = screen_size_x(s);
ey = py + ny;
if (ey > screen_size_y(s))
ey = screen_size_y(s);
if (px >= sx || py >= sy || nx == 0 || ny == 0)
return (0);
if (nx > sx - px)
ex = sx;
else
ex = px + nx;
if (ny > sy - py)
ey = sy;
else
ey = py + ny;
for (y = py; y < ey; y++) {
for (x = px; x < ex; x++) {
grid_view_get_cell(s->grid, x, y, &gc);
@@ -160,23 +445,72 @@ image_check_area(struct screen *s, u_int px, u_int py, u_int nx, u_int ny)
return (0);
}
int
image_check_line(struct screen *s, u_int py, u_int ny)
void
image_damage_area(struct screen_write_ctx *ctx, u_int px, u_int py, u_int nx,
u_int ny)
{
return (image_check_area(s, 0, py, screen_size_x(s), ny));
if (ctx->wp != NULL && image_check_area(ctx->s, px, py, nx, ny))
ctx->wp->flags |= PANE_REDRAW;
}
int
image_free_all(struct screen *s)
void
image_damage_all(struct screen_write_ctx *ctx)
{
return (image_check_area(s, 0, 0, screen_size_x(s),
screen_size_y(s)));
image_damage_area(ctx, 0, 0, screen_size_x(ctx->s),
screen_size_y(ctx->s));
}
int
image_scroll_up(struct screen *s, __unused u_int lines)
void
image_damage_scroll(struct screen_write_ctx *ctx, __unused u_int lines)
{
return (image_free_all(s));
image_damage_all(ctx);
}
/* Draw the graphical image marker runs in one visible scene span. */
void
image_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py,
u_int nx, u_int atx, u_int aty, const struct tty_style_ctx *style_ctx)
{
const struct image_backend *backend;
struct image_rectangle rectangle;
struct grid_cell gc, next;
struct image *im;
u_int i, run;
image_tty_update(tty);
backend = tty->image_backend;
if (~backend->flags & IMAGE_BACKEND_GRAPHICAL)
return;
for (i = 0; i < nx; i += run) {
grid_view_get_cell(s->grid, px + i, py, &gc);
if (~gc.flags & GRID_FLAG_IMAGE) {
run = 1;
continue;
}
im = image_find(gc.image_id);
if (im == NULL) {
run = 1;
continue;
}
for (run = 1; i + run < nx; run++) {
grid_view_get_cell(s->grid, px + i + run, py, &next);
if (~next.flags & GRID_FLAG_IMAGE ||
next.image_id != gc.image_id ||
next.image_y != gc.image_y ||
next.image_x != gc.image_x + run)
break;
}
rectangle.image = im;
memcpy(&rectangle.cell, &gc, sizeof rectangle.cell);
rectangle.source_x = gc.image_x;
rectangle.source_y = gc.image_y;
rectangle.width = run;
rectangle.height = 1;
rectangle.destination_x = atx + i;
rectangle.destination_y = aty;
backend->draw_rectangle(tty, &rectangle, style_ctx);
}
}
/*
@@ -218,7 +552,6 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg)
grid_view_set_cell(gd, cx + x, cy + y, &gc);
}
}
if (ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
image_damage_area(ctx, cx, cy, sx, sy);
screen_write_cursormove(ctx, 0, cy + sy, 0);
}

View File

@@ -2833,7 +2833,7 @@ input_exit_apc(struct input_ctx *ictx)
}
if (im != NULL) {
if (action == 'd')
image_clear(sctx->s, im->id);
image_clear(sctx, im->id);
else
image_write(sctx, im, ictx->cell.cell.bg);
image_free(im->id);
@@ -2841,7 +2841,7 @@ input_exit_apc(struct input_ctx *ictx)
input_reply(ictx, 0, "\033_Gi=%u;OK\033\\",
image_id);
} else if (action == 'd' && image_id == 0) {
image_clear(sctx->s, 0);
image_clear(sctx, 0);
} else if ((action == 't' || action == 'q') && quiet == 0)
input_reply(ictx, 0, "\033_Gi=%u;OK\033\\", image_id);
return;

View File

@@ -54,13 +54,20 @@ $TMUX new-window -d "
sleep 1
[ "$($TMUX display-message -pt:2 '#{cursor_y}')" = 1 ] || exit 1
# PNG Kitty input uses the shared image decoder and canonical cell sizing.
$TMUX new-window -d "
printf '\033_Ga=T,q=2,f=100;iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGUExURf8AAP///0EdNBEAAAABYktHRAH/Ai3eAAAAB3RJTUUH6ggCDAECH324BwAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=\033\\'
sleep 10"
sleep 1
[ "$($TMUX display-message -pt:3 '#{cursor_y}')" = 1 ] || exit 1
# SIXEL input reaches the same grid marker and copy-mode paths.
$TMUX new-window -d "cat '$FIXTURE'; sleep 10"
sleep 1
[ "$($TMUX display-message -pt:3 '#{cursor_y}')" -gt 0 ] || exit 1
$TMUX copy-mode -t:3 || exit 1
$TMUX send-keys -t:3 -X history-top || exit 1
$TMUX capture-pane -pt:3 >$TMP || exit 1
[ "$($TMUX display-message -pt:4 '#{cursor_y}')" -gt 0 ] || exit 1
$TMUX copy-mode -t:4 || exit 1
$TMUX send-keys -t:4 -X history-top || exit 1
$TMUX capture-pane -pt:4 >$TMP || exit 1
# A client without an image feature gets the brightness-based ASCII backend.
$TMUX kill-server 2>/dev/null

View File

@@ -1126,11 +1126,7 @@ redraw_draw_pane_span(struct redraw_draw_ctx *dctx,
py = span->data.p.py;
tty_draw_line(tty, s, px, py, n, x, y, &style_ctx);
#ifdef ENABLE_IMAGES
if (tty->term->flags & TERM_KITTY)
kitty_draw_line(tty, s, px, py, n, x, y, &style_ctx);
else if ((tty->term->flags & TERM_SIXEL) &&
tty->xpixel != 0 && tty->ypixel != 0)
sixel_draw_line(tty, s, px, py, n, x, y);
image_draw_line(tty, s, px, py, n, x, y, &style_ctx);
#endif
}

View File

@@ -1308,9 +1308,8 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
memcpy(&gc, &grid_default_cell, sizeof gc);
utf8_set(&gc.data, 'E');
#ifdef ENABLE_SIXEL
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_all(ctx);
#endif
for (yy = 0; yy < screen_size_y(s); yy++) {
@@ -1355,9 +1354,8 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1)
return;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
screen_write_initctx(ctx, &ttyctx, 0, 1);
@@ -1396,9 +1394,8 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1)
return;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
screen_write_initctx(ctx, &ttyctx, 0, 1);
@@ -1437,9 +1434,8 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
if (s->cx > screen_size_x(s) - 1)
return;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
screen_write_initctx(ctx, &ttyctx, 0, 1);
@@ -1472,9 +1468,8 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
if (ny == 0)
ny = 1;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
if (s->cy < s->rupper || s->cy > s->rlower) {
@@ -1540,9 +1535,8 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
if (ny == 0)
ny = 1;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
if (s->cy < s->rupper || s->cy > s->rlower) {
@@ -1613,9 +1607,8 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg)
if (gl->cellsize == 0 && COLOUR_DEFAULT(bg))
return;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
flags = gl->flags & GRID_LINE_OSC133_FLAGS;
@@ -1652,9 +1645,8 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg)
if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg)))
return;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg);
@@ -1679,9 +1671,8 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg)
return;
}
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), 1);
#endif
if (s->cx > sx - 1)
@@ -1733,9 +1724,8 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg)
return;
}
#ifdef ENABLE_SIXEL
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_all(ctx);
#endif
grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg);
@@ -1785,9 +1775,6 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
struct screen *s = ctx->s;
struct grid *gd = s->grid;
struct grid_line *gl;
#ifdef ENABLE_SIXEL
int redraw = 0;
#endif
u_int rupper = s->rupper, rlower = s->rlower;
gl = grid_get_line(gd, gd->hsize + s->cy);
@@ -1808,13 +1795,12 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
return;
}
#ifdef ENABLE_SIXEL
#ifdef ENABLE_IMAGES
if (rlower == screen_size_y(s) - 1)
redraw = image_scroll_up(s, 1);
image_damage_scroll(ctx, 1);
else
redraw = image_check_line(s, rupper, rlower - rupper);
if (redraw && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
image_damage_area(ctx, 0, rupper, screen_size_x(s),
rlower - rupper);
#endif
grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg);
@@ -1840,9 +1826,8 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg)
ctx->bg = bg;
}
#ifdef ENABLE_SIXEL
if (image_scroll_up(s, lines) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_scroll(ctx, lines);
#endif
for (i = 0; i < lines; i++) {
@@ -1869,9 +1854,8 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg)
else if (lines > s->rlower - s->rupper + 1)
lines = s->rlower - s->rupper + 1;
#ifdef ENABLE_SIXEL
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_all(ctx);
#endif
for (i = 0; i < lines; i++)
@@ -1910,9 +1894,8 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg)
struct visible_ranges *r;
struct visible_range *ri;
#ifdef ENABLE_SIXEL
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
#endif
screen_write_initctx(ctx, &ttyctx, 1, 1);
@@ -1991,9 +1974,8 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg)
struct visible_ranges *r;
struct visible_range *ri;
#ifdef ENABLE_SIXEL
if (image_check_line(s, 0, s->cy - 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(ctx, 0, 0, screen_size_x(s), s->cy + 1);
#endif
screen_write_initctx(ctx, &ttyctx, 1, 1);
@@ -2064,9 +2046,8 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg)
struct visible_ranges *r;
struct visible_range *ri;
#ifdef ENABLE_SIXEL
if (image_free_all(s) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_all(ctx);
#endif
screen_write_initctx(ctx, &ttyctx, 1, 1);
@@ -2531,9 +2512,8 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
}
}
#ifdef ENABLE_SIXEL
if (image_check_area(s, s->cx, s->cy, ci->used, 1) && ctx->wp != NULL)
ctx->wp->flags |= PANE_REDRAW;
#ifdef ENABLE_IMAGES
image_damage_area(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,

View File

@@ -126,11 +126,6 @@ screen_reinit(struct screen *s, int check)
screen_clear_selection(s);
screen_free_titles(s);
#ifdef ENABLE_SIXEL
image_free_all(s);
#endif
screen_set_progress_bar(s, PROGRESS_BAR_HIDDEN, 0);
screen_reset_hyperlinks(s);
}

81
tmux.h
View File

@@ -77,7 +77,8 @@ struct session;
#ifdef ENABLE_IMAGES
struct image;
struct tty_image_cache;
struct image_backend;
struct image_rectangle;
#endif
#ifdef ENABLE_SIXEL
struct sixel_image;
@@ -1037,6 +1038,23 @@ struct style {
};
#ifdef ENABLE_IMAGES
/* A protocol-neutral average of part of an image cell. RGB is premultiplied. */
struct image_sample {
u_char red;
u_char green;
u_char blue;
u_char alpha;
u_char brightness;
};
/* Half blocks, quadrants and sextants all divide evenly into a 2 by 6 grid. */
#define IMAGE_SAMPLE_COLUMNS 2
#define IMAGE_SAMPLE_ROWS 6
struct image_cell {
struct image_sample whole;
struct image_sample samples[IMAGE_SAMPLE_ROWS][IMAGE_SAMPLE_COLUMNS];
};
/* Immutable protocol-neutral image placement. */
struct image {
u_int id;
@@ -1048,11 +1066,25 @@ struct image {
size_t stride;
size_t size;
u_char *pixels;
char *ascii;
struct image_cell *cells; /* lazily generated text samples */
RB_ENTRY(image) entry;
};
RB_HEAD(images, image);
/* A cell-aligned part of an image to draw at a terminal position. */
struct image_rectangle {
struct image *image;
struct grid_cell cell;
u_int source_x;
u_int source_y;
u_int width;
u_int height;
u_int destination_x;
u_int destination_y;
};
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
#endif
/* Cursor style. */
@@ -1843,8 +1875,8 @@ struct tty {
struct event key_timer;
struct tty_key *key_tree;
#ifdef ENABLE_IMAGES
struct tty_image_cache *images;
u_int image_next_id;
const struct image_backend *image_backend;
void *image_data;
#endif
};
@@ -4227,16 +4259,32 @@ struct image *image_create(u_int, u_int, u_int, u_int, u_char *);
struct image *image_find(u_int);
void image_ref(u_int);
void image_free(u_int);
int image_free_all(struct screen *);
int image_check_line(struct screen *, u_int, u_int);
int image_check_area(struct screen *, u_int, u_int, u_int, u_int);
int image_scroll_up(struct screen *, u_int);
void image_set_cell(struct grid_cell *, struct image *, u_int,
u_int);
void image_write(struct screen_write_ctx *, struct image *, u_int);
void image_make_ascii(struct image *);
const char *image_get_ascii(struct image *, u_int, u_int);
void image_clear(struct screen *, u_int);
void image_get_pixel_rectangle(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 *,
u_int *);
u_char *image_base64_decode(const char *, size_t, size_t, size_t *);
u_char *image_png_decode(const u_char *, size_t, size_t, u_int *,
u_int *);
void image_damage_area(struct screen_write_ctx *, u_int, u_int,
u_int, u_int);
void image_damage_all(struct screen_write_ctx *);
void image_damage_scroll(struct screen_write_ctx *, u_int);
int image_tty_is_graphical(struct tty *);
int image_tty_scrolls(struct tty *);
void image_tty_update(struct tty *);
void image_tty_geometry_changed(struct tty *);
void image_tty_free(struct tty *, int);
void image_draw_line(struct tty *, struct screen *, u_int, u_int,
u_int, u_int, u_int, const struct tty_style_ctx *);
const struct image_cell *image_get_cell(struct image *, u_int, u_int);
void image_get_text_cell(struct tty *, struct image *, u_int,
u_int, const struct grid_cell *, struct grid_cell *,
const struct tty_style_ctx *);
void image_clear(struct screen_write_ctx *, u_int);
#define KITTY_PARSE_ERROR -1
#define KITTY_PARSE_OK 0
#define KITTY_PARSE_MORE 1
@@ -4244,11 +4292,12 @@ void image_clear(struct screen *, u_int);
struct image *kitty_parse_image(void **, const u_char *, size_t, u_int,
u_int, u_int *, u_int *, char *, int *);
void kitty_free_state(void *);
void kitty_draw_line(struct tty *, struct screen *, u_int, u_int,
u_int, u_int, u_int, const struct tty_style_ctx *);
void kitty_images_free(struct tty *, int);
void sixel_draw_line(struct tty *, struct screen *, u_int, u_int,
u_int, u_int, u_int);
void kitty_draw_rectangle(struct tty *,
const struct image_rectangle *, const struct tty_style_ctx *);
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 *);
#endif
#ifdef ENABLE_SIXEL

View File

@@ -128,7 +128,6 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
#ifdef ENABLE_IMAGES
struct grid_cell image_gc;
struct image *im;
const char *ascii;
#endif
struct grid_line *gl;
u_int i, j, last_i, cx, ex, width;
@@ -265,19 +264,19 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
#ifdef ENABLE_IMAGES
/* Text terminals render image blocks as ASCII. */
if (gc.flags & GRID_FLAG_IMAGE) {
memcpy(&image_gc, &gc, sizeof image_gc);
im = image_find(gc.image_id);
if ((tty->term->flags & TERM_KITTY) ||
((tty->term->flags & TERM_SIXEL) &&
tty->xpixel != 0 &&
tty->ypixel != 0)) {
ascii = " ";
if (image_tty_is_graphical(tty)) {
memcpy(&image_gc, &gc,
sizeof image_gc);
utf8_set(&image_gc.data, ' ');
image_gc.flags &=
~GRID_FLAG_IMAGE;
skip = 1;
} else
ascii = image_get_ascii(im,
gc.image_x, gc.image_y);
utf8_set(&image_gc.data, *ascii);
image_gc.flags &= ~GRID_FLAG_IMAGE;
} else {
image_get_text_cell(tty, im,
gc.image_x, gc.image_y, &gc,
&image_gc, style_ctx);
}
if (skip)
image_gc.flags &=
~GRID_FLAG_SELECTED;

30
tty.c
View File

@@ -109,9 +109,6 @@ tty_init(struct tty *tty, struct client *c)
tty->ccolour = -1;
tty->fg = tty->bg = -1;
tty->mouse_last_pane = -1;
#ifdef ENABLE_IMAGES
tty->image_next_id = arc4random_uniform(0xffff00) + 1;
#endif
if (tcgetattr(c->fd, &tty->tio) != 0)
return (-1);
@@ -161,10 +158,19 @@ tty_resize(struct tty *tty)
void
tty_set_size(struct tty *tty, u_int sx, u_int sy, u_int xpixel, u_int ypixel)
{
#ifdef ENABLE_IMAGES
int geometry_changed;
geometry_changed = (tty->xpixel != xpixel || tty->ypixel != ypixel);
#endif
tty->sx = sx;
tty->sy = sy;
tty->xpixel = xpixel;
tty->ypixel = ypixel;
#ifdef ENABLE_IMAGES
if (geometry_changed)
image_tty_geometry_changed(tty);
#endif
}
static void
@@ -510,7 +516,7 @@ tty_close(struct tty *tty)
if (tty->flags & TTY_OPENED) {
#ifdef ENABLE_IMAGES
kitty_images_free(tty, 1);
image_tty_free(tty, 1);
#endif
evbuffer_free(tty->in);
event_del(&tty->event_in);
@@ -529,7 +535,7 @@ tty_free(struct tty *tty)
{
tty_close(tty);
#ifdef ENABLE_IMAGES
kitty_images_free(tty, 0);
image_tty_free(tty, 0);
#endif
free(tty->r.ranges);
@@ -542,6 +548,9 @@ tty_update_features(struct tty *tty)
if (tty_apply_features(tty->term, c->term_features))
tty_term_apply_overrides(tty->term);
#ifdef ENABLE_IMAGES
image_tty_update(tty);
#endif
if (tty_use_margin(tty))
tty_putcode(tty, TTYC_ENMG);
@@ -2122,7 +2131,6 @@ tty_cell(struct tty *tty, const struct grid_cell *gc,
#ifdef ENABLE_IMAGES
struct grid_cell image_gc;
struct image *im;
const char *ascii;
#endif
/* Skip last character if terminal is stupid. */
@@ -2146,15 +2154,11 @@ tty_cell(struct tty *tty, const struct grid_cell *gc,
* selection; the unchanged image would then need to be drawn again.
*/
if (gc->flags & GRID_FLAG_IMAGE) {
if ((tty->term->flags & TERM_KITTY) ||
((tty->term->flags & TERM_SIXEL) &&
tty->xpixel != 0 && tty->ypixel != 0))
if (image_tty_is_graphical(tty))
return;
memcpy(&image_gc, gc, sizeof image_gc);
im = image_find(gc->image_id);
ascii = image_get_ascii(im, gc->image_x, gc->image_y);
utf8_set(&image_gc.data, *ascii);
image_gc.flags &= ~GRID_FLAG_IMAGE;
image_get_text_cell(tty, im, gc->image_x, gc->image_y, gc,
&image_gc, style_ctx);
gc = &image_gc;
}
#endif