Rebased on current master without conflicts and completed the cleanup: comments above new function in longer functions, metadata comments, aligned declarations and simpler guards, consistent braces and shorter lines.

This commit is contained in:
Michael Grant
2026-10-09 23:37:40 +02:00
parent 6826d87bdb
commit 729b7e7a13
14 changed files with 535 additions and 483 deletions

3
grid.c
View File

@@ -1383,8 +1383,9 @@ grid_reflow_has_image(struct grid_line *gl)
/* 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 &&
gc.data.data[3] == 0xae)
gc.data.data[3] == 0xae) {
return (1);
}
}
return (0);
}

View File

@@ -48,9 +48,10 @@ struct image_rgb {
u_char b;
};
/* Shading levels cached for the lifetime of an image. */
struct image_glyph_data {
u_char *shade5;
u_char *shade8;
u_char *shade5; /* Cached levels for five-shade output. */
u_char *shade8; /* Cached levels for eight-shade output. */
};
static const struct image_rgb image_ansi_colours[16] = {
@@ -64,6 +65,7 @@ static const struct image_rgb image_ansi_colours[16] = {
{ 0x00, 0xff, 0xff }, { 0xff, 0xff, 0xff }
};
/* Return the squared RGB distance between two colours. */
static u_int
image_glyph_distance(struct image_rgb a, struct image_rgb b)
{
@@ -72,6 +74,7 @@ image_glyph_distance(struct image_rgb a, struct image_rgb b)
return (r * r + g * g + bl * bl);
}
/* Return whether a colour is close enough to grey to use neutral colours. */
static int
image_glyph_low_saturation(struct image_rgb colour)
{
@@ -89,6 +92,7 @@ image_glyph_low_saturation(struct image_rgb colour)
return (maximum == 0 || (maximum - minimum) * 4 <= maximum);
}
/* Find the closest ANSI colour, keeping greys on the neutral ramp. */
static u_int
image_glyph_nearest_ansi(struct image_rgb colour, u_int colours)
{
@@ -97,8 +101,9 @@ image_glyph_nearest_ansi(struct image_rgb colour, u_int colours)
for (i = 0; i < colours; i++) {
if (neutral && i != 0 && i != 7 &&
(colours != 16 || (i != 8 && i != 15)))
(colours != 16 || (i != 8 && i != 15))) {
continue;
}
distance = image_glyph_distance(colour, image_ansi_colours[i]);
if (distance < best_distance) {
best_distance = distance;
@@ -108,13 +113,14 @@ image_glyph_nearest_ansi(struct image_rgb colour, u_int colours)
return (best);
}
/* Map an RGB colour to the terminal palette and its output colour code. */
static struct image_rgb
image_glyph_quantize(struct image_rgb colour, enum image_glyph_palette palette,
int *output)
{
struct image_rgb result;
struct image_rgb result;
int value;
u_int index;
u_int index, ncolours;
if (palette == IMAGE_GLYPH_PALETTE_RGB) {
*output = colour_join_rgb(colour.r, colour.g, colour.b);
@@ -126,8 +132,10 @@ image_glyph_quantize(struct image_rgb colour, enum image_glyph_palette palette,
colour_split_rgb(value, &result.r, &result.g, &result.b);
return (result);
}
index = image_glyph_nearest_ansi(colour,
palette == IMAGE_GLYPH_PALETTE_8 ? 8 : 16);
ncolours = 16;
if (palette == IMAGE_GLYPH_PALETTE_8)
ncolours = 8;
index = image_glyph_nearest_ansi(colour, ncolours);
result = image_ansi_colours[index];
if (index < 8)
*output = index;
@@ -136,6 +144,7 @@ image_glyph_quantize(struct image_rgb colour, enum image_glyph_palette palette,
return (result);
}
/* Fit two colours to a block glyph's pixel samples. */
static void
image_glyph_fit_colours(const struct image_rgb *samples, u_int count,
struct image_rgb centres[2])
@@ -143,6 +152,7 @@ image_glyph_fit_colours(const struct image_rgb *samples, u_int count,
u_int sum[2][3], counts[2], group, i, j, iteration, distance;
u_int maximum = 0, first = 0, second = 0;
/* Start with the two most widely separated samples. */
for (i = 0; i < count; i++) {
for (j = i + 1; j < count; j++) {
distance = image_glyph_distance(samples[i], samples[j]);
@@ -155,6 +165,8 @@ image_glyph_fit_colours(const struct image_rgb *samples, u_int count,
}
centres[0] = samples[first];
centres[1] = samples[second];
/* Refine the foreground and background by averaging each cluster. */
for (iteration = 0; iteration < 4; iteration++) {
memset(sum, 0, sizeof sum);
memset(counts, 0, sizeof counts);
@@ -176,11 +188,12 @@ image_glyph_fit_colours(const struct image_rgb *samples, u_int count,
}
}
/* Copy the UTF-8 character for an ACS key into a fallback cell. */
static int
image_glyph_set_acs(struct tty *tty, struct utf8_data *data, u_char key)
{
struct utf8_data *ud;
const char *s = tty_acs_get(tty, key);
const char *s = tty_acs_get(tty, key);
if (s == NULL)
return (0);
@@ -194,6 +207,7 @@ image_glyph_set_acs(struct tty *tty, struct utf8_data *data, u_char key)
return (1);
}
/* Map a block's foreground mask to a half, quadrant or sextant glyph. */
static u_char
image_glyph_block_key(enum image_glyph_detail detail, u_int mask)
{
@@ -234,6 +248,7 @@ image_glyph_block_key(enum image_glyph_detail detail, u_int mask)
return (key);
}
/* Build and cache dithered brightness levels for a shading palette. */
static u_char *
image_glyph_make_shades(struct image *im, u_int levels)
{
@@ -250,6 +265,8 @@ image_glyph_make_shades(struct image *im, u_int levels)
result = (levels == 5 ? data->shade5 : data->shade8);
if (result != NULL)
return (result);
/* Collect cell brightness and the range used by this image. */
cells = (size_t)im->sx * im->sy;
values = xcalloc(cells, sizeof *values);
result = xcalloc(cells, 1);
@@ -265,11 +282,15 @@ image_glyph_make_shades(struct image *im, u_int levels)
values[index] = value;
}
}
/* Stretch the brightness range to make all shading levels available. */
if (maximum > minimum) {
for (index = 0; index < cells; index++)
for (index = 0; index < cells; index++) {
values[index] = (values[index] - minimum) * 255 /
(maximum - minimum);
}
}
/* Dither each row, alternating direction to distribute the error. */
for (y = 0; y < im->sy; y++) {
reverse = (y & 1);
for (scan = 0; scan < im->sx; scan++) {
@@ -302,6 +323,8 @@ image_glyph_make_shades(struct image *im, u_int levels)
}
}
free(values);
/* Keep the result for subsequent redraws at the same shading level. */
if (levels == 5)
data->shade5 = result;
else
@@ -309,10 +332,11 @@ image_glyph_make_shades(struct image *im, u_int levels)
return (result);
}
/* Choose the richest colour palette supported by this terminal. */
static enum image_glyph_palette
image_glyph_get_palette(struct tty *tty)
{
int colours;
int colours;
if (tty->term->flags & TERM_RGBCOLOURS)
return (IMAGE_GLYPH_PALETTE_RGB);
@@ -326,6 +350,7 @@ image_glyph_get_palette(struct tty *tty)
return (IMAGE_GLYPH_PALETTE_8);
}
/* Choose the fallback glyph detail supported by this terminal. */
static enum image_glyph_detail
image_glyph_get_detail(struct tty *tty, enum image_glyph_palette palette)
{
@@ -342,6 +367,7 @@ image_glyph_get_detail(struct tty *tty, enum image_glyph_palette palette)
return (IMAGE_GLYPH_SHADE8);
}
/* Render one image cell using a two-colour block glyph. */
static void
image_glyph_block(struct tty *tty, struct image *im, u_int x, u_int y,
enum image_glyph_detail detail, enum image_glyph_palette palette,
@@ -354,6 +380,7 @@ image_glyph_block(struct tty *tty, struct image *im, u_int x, u_int y,
int colours[2];
u_char key;
/* Average the stored samples into the chosen glyph's subcells. */
columns = (detail == IMAGE_GLYPH_HALF ? 1 : 2);
rows = (detail == IMAGE_GLYPH_SEXTANT ? 3 : 2);
i = 0;
@@ -378,6 +405,8 @@ image_glyph_block(struct tty *tty, struct image *im, u_int x, u_int y,
i++;
}
}
/* Fit the two colours and choose which subcells use the foreground. */
n = columns * rows;
image_glyph_fit_colours(samples, n, centres);
quantized[0] = image_glyph_quantize(centres[0], palette, &colours[0]);
@@ -385,10 +414,13 @@ image_glyph_block(struct tty *tty, struct image *im, u_int x, u_int y,
mask = 0;
for (i = 0; i < n; i++) {
if (image_glyph_distance(samples[i], quantized[1]) <
image_glyph_distance(samples[i], quantized[0]))
image_glyph_distance(samples[i], quantized[0])) {
mask |= (1U << i);
}
}
key = image_glyph_block_key(detail, mask);
/* Use a blank cell if the terminal has no matching glyph. */
if (key == 0)
utf8_set(&out->data, ' ');
else if (!image_glyph_set_acs(tty, &out->data, key))
@@ -397,6 +429,7 @@ image_glyph_block(struct tty *tty, struct image *im, u_int x, u_int y,
out->bg = colours[0];
}
/* Render an image cell using the terminal's text and colour capabilities. */
void
image_get_fallback_cell(struct tty *tty, struct image *im, u_int x, u_int y,
const struct grid_cell *gc, struct grid_cell *out)
@@ -414,7 +447,7 @@ image_get_fallback_cell(struct tty *tty, struct image *im, u_int x, u_int y,
enum image_glyph_palette palette;
enum image_glyph_detail detail;
u_char *levels, key;
u_int level = 0;
u_int level = 0, nlevels;
memcpy(out, gc, sizeof *out);
cell = image_get_cell(im, x, y);
@@ -422,6 +455,8 @@ image_get_fallback_cell(struct tty *tty, struct image *im, u_int x, u_int y,
utf8_set(&out->data, ' ');
return;
}
/* Select the palette and glyph detail for this terminal. */
palette = image_glyph_get_palette(tty);
detail = image_glyph_get_detail(tty, palette);
if (detail == IMAGE_GLYPH_ASCII) {
@@ -430,8 +465,10 @@ image_get_fallback_cell(struct tty *tty, struct image *im, u_int x, u_int y,
return;
}
if (detail == IMAGE_GLYPH_SHADE5 || detail == IMAGE_GLYPH_SHADE8) {
levels = image_glyph_make_shades(im,
detail == IMAGE_GLYPH_SHADE5 ? 5 : 8);
nlevels = 8;
if (detail == IMAGE_GLYPH_SHADE5)
nlevels = 5;
levels = image_glyph_make_shades(im, nlevels);
level = levels[(size_t)y * im->sx + x];
key = (detail == IMAGE_GLYPH_SHADE5 ? shades[level] :
bold_shades[level]);
@@ -443,13 +480,15 @@ image_get_fallback_cell(struct tty *tty, struct image *im, u_int x, u_int y,
out->bg = 0;
out->attr &= ~GRID_ATTR_BRIGHT;
if (detail == IMAGE_GLYPH_SHADE8 &&
(level == 2 || level == 4 || level == 7))
(level == 2 || level == 4 || level == 7)) {
out->attr |= GRID_ATTR_BRIGHT;
}
return;
}
image_glyph_block(tty, im, x, y, detail, palette, out);
}
/* Free the brightness levels cached for text image rendering. */
void
image_free_fallback(struct image *im)
{

View File

@@ -69,57 +69,62 @@ static const uint32_t kitty_diacritics[] = {
0x1D244
};
/* Graphics control values and decoded bytes retained across upload chunks. */
struct kitty_state {
char action;
char delete;
u_int format;
char medium;
char compression;
u_int width;
u_int width; /* Source pixel dimensions. */
u_int height;
u_int source_x;
u_int source_x; /* Crop origin in source pixels. */
u_int source_y;
u_int x_offset;
u_int x_offset; /* Pixel offset inside the starting cell. */
u_int y_offset;
u_int source_width;
u_int source_width; /* Crop dimensions, zero for the remainder. */
u_int source_height;
u_int columns;
u_int columns; /* Placement cells, zero for automatic size. */
u_int rows;
u_int image_id;
u_int image_number;
u_int placement_id;
int32_t z;
u_int quiet;
int no_cursor;
int virtual;
u_int data_size;
int more;
int unsupported;
int no_cursor; /* Suppress cursor movement after placement. */
int virtual; /* Placement uses Unicode placeholders. */
u_int data_size; /* Expected size of a compressed PNG payload. */
int more; /* Another upload chunk follows. */
int unsupported; /* A requested operation is not implemented. */
u_char *raw;
size_t rawlen;
u_char *raw; /* Base64-decoded upload bytes. */
size_t rawlen; /* Number of accumulated bytes. */
};
/* An application placement retained by one pane's Kitty parser. */
struct kitty_placement {
u_int placement_id;
u_int server_id;
u_int server_id; /* Referenced image view. */
int32_t z;
int virtual;
struct kitty_placement *next;
};
/* Map an application image ID to its source and placement views. */
struct kitty_source {
u_int app_id;
u_int server_id;
u_int app_id; /* Application ID. */
u_int server_id; /* Referenced source image. */
struct kitty_placement *placements;
struct kitty_source *next;
};
/* Kitty input state kept for the lifetime of a pane's parser. */
struct kitty_context {
struct kitty_state *transfer;
struct kitty_state *transfer; /* Pending upload, or NULL. */
struct kitty_source *sources;
};
/* An image uploaded to one terminal at its current cell geometry. */
struct kitty_image_cache {
u_int server_id;
u_int kitty_id;
@@ -130,19 +135,22 @@ struct kitty_image_cache {
struct kitty_image_cache *next;
};
/* One terminal placement, retained until its redraw replacement is ready. */
struct kitty_placement_cache {
u_int id;
u_int x;
u_int id; /* Terminal placement ID. */
u_int x; /* Origin in terminal cells. */
u_int y;
u_int width;
u_int width; /* Size in cells. */
u_int height;
u_int source_x;
u_int source_y;
int32_t z;
/* Delete after replacement placements have been drawn. */
int pending_delete;
struct kitty_placement_cache *next;
};
/* Image uploads and ID allocation kept for the lifetime of a terminal. */
struct kitty_output {
struct kitty_image_cache *images;
u_int next_id;
@@ -203,10 +211,7 @@ kitty_redraw_keep_piece(struct tty *tty, struct kitty_image_cache *entry,
placement->z);
}
/*
* Place the parts of a placement outside a redraw area again, since the
* redraw will not replace them.
*/
/* Preserve placement pieces outside the redraw area. */
static void
kitty_redraw_keep(struct tty *tty, struct kitty_image_cache *entry,
struct kitty_placement_cache *placement, u_int x, u_int y, u_int width,
@@ -241,14 +246,7 @@ kitty_redraw_keep(struct tty *tty, struct kitty_image_cache *entry,
px1 - ix1, iy1 - iy0);
}
/*
* Mark placements intersecting a redraw area as stale rather than deleting
* them yet (see kitty_redraw_finish()) - some implementations free an
* image's pixel data once its last placement is gone, so an image only
* placed here would go blank before its replacement lands. A placement only
* partly inside the area is deleted as a whole, so place its outside parts
* again first or they would vanish from cells nothing redraws.
*/
/* Mark intersecting placements for deletion after their replacements land. */
void
kitty_redraw_start(struct tty *tty, u_int x, u_int y, u_int width,
u_int height)
@@ -259,6 +257,8 @@ kitty_redraw_start(struct tty *tty, u_int x, u_int y, u_int width,
if (ko == NULL)
return;
/* Retain pixel data by keeping placements alive until replacement. */
for (entry = ko->images; entry != NULL; entry = entry->next) {
for (placement = entry->placements; placement != NULL;
placement = placement->next) {
@@ -267,28 +267,26 @@ kitty_redraw_start(struct tty *tty, u_int x, u_int y, u_int width,
if (placement->x >= x + width ||
placement->x + placement->width <= x ||
placement->y >= y + height ||
placement->y + placement->height <= y)
placement->y + placement->height <= y) {
continue;
}
placement->pending_delete = 1;
/* Preserve pieces outside the redraw area. */
kitty_redraw_keep(tty, entry, placement, x, y, width,
height);
}
}
}
/*
* Delete placements marked stale by kitty_redraw_start() - called once any
* replacement placements have already been created, so an image already
* placed elsewhere in the same redraw is never left with none at all in
* between the two.
*/
/* Delete stale placements once replacement placements exist. */
void
kitty_redraw_finish(struct tty *tty)
{
struct kitty_output *ko = tty->image_data;
struct kitty_image_cache *entry;
struct kitty_placement_cache **pp, *placement;
char s[64];
char s[64];
if (ko == NULL)
return;
@@ -299,8 +297,8 @@ kitty_redraw_finish(struct tty *tty)
continue;
}
xsnprintf(s, sizeof s,
"\033_Ga=d,d=i,i=%u,p=%u,q=2\033\\", entry->kitty_id,
placement->id);
"\033_Ga=d,d=i,i=%u,p=%u,q=2\033\\",
entry->kitty_id, placement->id);
tty_puts(tty, s);
*pp = placement->next;
free(placement);
@@ -350,12 +348,12 @@ kitty_free_stale_images(struct tty *tty)
/* Save the successor before this entry may be removed. */
next = entry->next;
if (image_find(entry->server_id) != NULL) {
/* Retained entries become the predecessor of the next one. */
/* Retain this entry as the next one's predecessor. */
previous = entry;
continue;
}
/* Unlink stale entries, including the first entry in the list. */
/* Unlink stale entries, including the list head. */
if (previous == NULL)
ko->images = next;
else
@@ -418,7 +416,7 @@ kitty_upload(struct tty *tty, struct image *im)
struct kitty_image_cache *entry;
char control[128], encoded[4097];
const u_char *pixels;
u_char *padded;
u_char *padded, *padded_row;
size_t offset, size, row, stride, image_size;
int encodedlen;
u_int id, width, height;
@@ -434,12 +432,14 @@ kitty_upload(struct tty *tty, struct image *im)
if ((uint64_t)upload_width * upload_height > IMAGE_SIZE_LIMIT / 4)
return (NULL);
/* Reuse uploads only while the terminal cell size is unchanged. */
for (entry = ko->images; entry != NULL; entry = entry->next) {
if (entry->server_id != image_get_id(im))
continue;
if (entry->xpixel == tty->xpixel &&
entry->ypixel == tty->ypixel)
entry->ypixel == tty->ypixel) {
return (entry);
}
kitty_delete(tty, entry->kitty_id);
kitty_free_placements(entry);
entry->server_id = 0;
@@ -450,6 +450,8 @@ kitty_upload(struct tty *tty, struct image *im)
entry->next = ko->images;
ko->images = entry;
}
/* Allocate a nonzero terminal image ID. */
do {
id = ++ko->next_id & 0xffffff;
} while (id == 0);
@@ -460,21 +462,18 @@ kitty_upload(struct tty *tty, struct image *im)
entry->next_placement = 0;
pixels = image_get_pixels(im, &stride, &image_size);
/*
* 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.
*/
/* Duplicate edge pixels to prevent dark borders from filtering. */
padded = xcalloc((size_t)upload_width * upload_height, 4);
for (row = 0; row < height; row++)
memcpy(padded + ((size_t)(row + 1) * upload_width + 1) * 4,
for (row = 0; row < height; row++) {
memcpy(padded + ((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);
padded_row = padded + row * upload_width * 4;
memcpy(padded_row, padded_row + 4, 4);
memcpy(padded_row + (upload_width - 1) * 4,
padded_row + (upload_width - 2) * 4, 4);
}
memcpy(padded, padded + (size_t)upload_width * 4,
(size_t)upload_width * 4);
@@ -485,6 +484,8 @@ kitty_upload(struct tty *tty, struct image *im)
width = upload_width;
height = upload_height;
image_size = (size_t)width * height * 4;
/* Send base64 chunks, with image controls on the first chunk. */
for (offset = 0; offset < image_size; offset += size) {
size = image_size - offset;
if (size > KITTY_CHUNK_SIZE)
@@ -518,7 +519,8 @@ kitty_draw_rect(struct tty *tty, const struct image_rect *rectangle)
struct kitty_image_cache *entry;
struct image *im;
u_int source_x, source_y;
u_int width, height, destination_x, destination_y;
u_int width, height;
u_int destination_x, destination_y;
int32_t z;
im = image_rect_get_image(rectangle);
@@ -581,6 +583,7 @@ kitty_control(struct kitty_state *ks, const u_char *buf, size_t len)
int32_t signed_number;
char key;
/* Read comma-separated keys and validate each control value. */
while (buf < end) {
key = *buf++;
if (buf == end || *buf++ != '=')
@@ -612,22 +615,25 @@ kitty_control(struct kitty_state *ks, const u_char *buf, size_t len)
break;
case 'P': case 'Q':
if (kitty_number((const char *)value, valuelen,
&number) != 0)
&number) != 0) {
return (-1);
}
if (number != 0)
ks->unsupported = 1;
break;
case 'H': case 'V':
if (kitty_signed_number((const char *)value, valuelen,
&signed_number) != 0)
&signed_number) != 0) {
return (-1);
}
if (signed_number != 0)
ks->unsupported = 1;
break;
case 'z':
if (kitty_signed_number((const char *)value, valuelen,
&ks->z) != 0)
&ks->z) != 0) {
return (-1);
}
break;
case 'f':
case 's':
@@ -649,8 +655,9 @@ kitty_control(struct kitty_state *ks, const u_char *buf, size_t len)
case 'X':
case 'Y':
if (kitty_number((const char *)value, valuelen,
&number) != 0)
&number) != 0) {
return (-1);
}
switch (key) {
case 'f': ks->format = number; break;
case 's': ks->width = number; break;
@@ -857,14 +864,16 @@ kitty_delete_images(void *state, struct screen_write_ctx *ctx,
if (kc == NULL)
return;
if (how >= 'A') {
if (how <= 'Z') {
how += 'a' - 'A';
release = 1;
}
/* Uppercase selectors also release unreferenced source image data. */
if (how >= 'A' && how <= 'Z') {
how += 'a' - 'A';
release = 1;
}
if (how == 'r')
placement_id = 0;
/* Remove grid placements before checking virtual placements. */
for (source = kc->sources; source != NULL; source = source->next)
(void)kitty_prune_placements(source, gd);
image_clear_kitty(ctx, result);
@@ -874,20 +883,18 @@ kitty_delete_images(void *state, struct screen_write_ctx *ctx,
if (how == 'i') {
if (source->app_id == result->image_id)
selected = 1;
} else if (how == 'r') {
if (source->app_id >= result->x) {
if (source->app_id <= result->y)
selected = 1;
}
} else if (how == 'r' && source->app_id >= result->x &&
source->app_id <= result->y) {
selected = 1;
}
if (selected) {
for (pp = &source->placements;
(placement = *pp) != NULL; ) {
if (!placement->virtual)
goto keep_placement;
if (placement_id != 0) {
if (placement->placement_id != placement_id)
goto keep_placement;
if (placement_id != 0 &&
placement->placement_id != placement_id) {
goto keep_placement;
}
*pp = placement->next;
image_free(placement->server_id);
@@ -900,11 +907,9 @@ keep_placement:
if (placement_id == 0)
removed++;
}
if (!release)
goto keep_source;
if (removed == 0)
goto keep_source;
if (source->placements != NULL)
/* Keep source data until every retained placement is gone. */
if (!release || removed == 0 || source->placements != NULL)
goto keep_source;
if (image_grid_has_image(gd, source->server_id))
goto keep_source;
@@ -930,6 +935,7 @@ kitty_append(struct kitty_state *ks, const u_char *buf, size_t len)
if (len > IMAGE_SIZE_LIMIT)
return (-1);
/* Older Chafa chunks have padding with nonzero unused bits. */
if (len >= 4 && len % 4 == 0 && buf[len - 1] == '=') {
offset = len - 2;
@@ -979,14 +985,14 @@ kitty_raw(struct kitty_state *ks, u_char *data, size_t size)
u_int bytes;
bytes = (ks->format == 24 ? 3 : 4);
if (ks->width == 0)
return (NULL);
if (ks->height == 0)
if (ks->width == 0 || ks->height == 0)
return (NULL);
if ((uint64_t)ks->width * ks->height > IMAGE_SIZE_LIMIT / 4)
return (NULL);
expected = (size_t)ks->width * ks->height * bytes;
raw = data;
/* Decompress before checking the exact raster byte count. */
if (ks->compression == 'z') {
rawlen = expected;
raw = xmalloc(expected);
@@ -1006,6 +1012,7 @@ kitty_raw(struct kitty_state *ks, u_char *data, size_t size)
if (bytes == 4)
return (raw);
/* Expand RGB input to the shared RGBA pixel format. */
pixels = xmalloc((size_t)ks->width * ks->height * 4);
for (i = j = 0; i < expected; i += 3, j += 4) {
pixels[j] = raw[i];
@@ -1035,24 +1042,23 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
image_get_size(source, &source_width, &source_height);
view.x = ks->source_x;
view.y = ks->source_y;
if (view.x >= source_width)
return (NULL);
if (view.y >= source_height)
/* Clip the requested source rectangle before scaling it. */
if (view.x >= source_width || view.y >= source_height)
return (NULL);
view.width = source_width - view.x;
if (ks->source_width != 0) {
if (view.width > ks->source_width)
view.width = ks->source_width;
}
if (ks->source_width != 0 && view.width > ks->source_width)
view.width = ks->source_width;
view.height = source_height - view.y;
if (ks->source_height != 0) {
if (view.height > ks->source_height)
view.height = ks->source_height;
}
if (ks->source_height != 0 && view.height > ks->source_height)
view.height = ks->source_height;
/* Choose placement dimensions in cells, preserving the aspect ratio. */
cell_width = (xpixel == 0 ? 8 : xpixel);
cell_height = (ypixel == 0 ? 16 : ypixel);
x_offset = ks->x_offset;
y_offset = ks->y_offset;
/* Match Kitty by clamping offsets to the starting cell. */
if (x_offset >= cell_width)
x_offset = cell_width - 1;
@@ -1060,19 +1066,15 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
y_offset = cell_height - 1;
view.sx = ks->columns;
view.sy = ks->rows;
if (view.sx > USHRT_MAX)
if (view.sx > USHRT_MAX || view.sy > USHRT_MAX)
return (NULL);
if (view.sy > USHRT_MAX)
return (NULL);
if (view.sx == 0) {
if (view.sy == 0)
natural_size = 1;
}
if (view.sx == 0 && view.sy == 0)
natural_size = 1;
if (natural_size) {
if (x_offset > UINT_MAX - view.width)
return (NULL);
if (y_offset > UINT_MAX - view.height)
if (x_offset > UINT_MAX - view.width ||
y_offset > UINT_MAX - view.height) {
return (NULL);
}
image_size_in_cells(view.width + x_offset,
view.height + y_offset, cell_width, cell_height,
&view.sx, &view.sy);
@@ -1100,6 +1102,7 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
}
canvas_width = (uint64_t)view.sx * cell_width;
canvas_height = (uint64_t)view.sy * cell_height;
/* Preserve source resolution when shrinking the image. */
if (scale < 1)
units = 1 / scale;
@@ -1119,49 +1122,38 @@ kitty_place_image(struct image *source, struct kitty_state *ks, u_int xpixel,
view.scaled_height = view.height;
view.x_offset = floor(x_offset * units);
view.y_offset = floor(y_offset * units);
if (view.x_offset > view.canvas_width)
if (view.x_offset > view.canvas_width ||
view.scaled_width > view.canvas_width - view.x_offset ||
view.y_offset > view.canvas_height ||
view.scaled_height > view.canvas_height - view.y_offset) {
return (NULL);
if (view.scaled_width > view.canvas_width - view.x_offset)
return (NULL);
if (view.y_offset > view.canvas_height)
return (NULL);
if (view.scaled_height > view.canvas_height - view.y_offset)
return (NULL);
if (ks->columns != 0) {
if (ks->rows != 0) {
view.x_offset += (view.canvas_width - view.x_offset -
view.scaled_width) / 2;
view.y_offset += (view.canvas_height - view.y_offset -
view.scaled_height) / 2;
}
}
/* Centre the image when both placement dimensions were specified. */
if (ks->columns != 0 && ks->rows != 0) {
view.x_offset += (view.canvas_width - view.x_offset -
view.scaled_width) / 2;
view.y_offset += (view.canvas_height - view.y_offset -
view.scaled_height) / 2;
}
/* A matching rectangle needs neither resampling nor padding. */
if (x_offset == 0) {
if (y_offset == 0) {
if ((uint64_t)view.width * canvas_height ==
(uint64_t)view.height * canvas_width) {
view.canvas_width = view.width;
view.scaled_width = view.width;
view.canvas_height = view.height;
view.scaled_height = view.height;
view.x_offset = 0;
view.y_offset = 0;
}
}
if (x_offset == 0 && y_offset == 0 &&
(uint64_t)view.width * canvas_height ==
(uint64_t)view.height * canvas_width) {
view.canvas_width = view.width;
view.scaled_width = view.width;
view.canvas_height = view.height;
view.scaled_height = view.height;
view.x_offset = 0;
view.y_offset = 0;
}
im = image_create_view(source, &view);
if (im != NULL) {
if (ks->no_cursor)
image_set_no_cursor(im);
}
if (im != NULL && ks->no_cursor)
image_set_no_cursor(im);
return (im);
}
/*
* Parse one Kitty graphics APC body (without the leading G). Only direct
* static images are accepted. The returned image retains immutable RGBA
* pixels for the lifetime of its placement.
*/
/* Parse a Kitty graphics APC body into a reply and optional image placement. */
struct image *
kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
u_int ypixel, struct kitty_parse_result *result)
@@ -1185,6 +1177,8 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
}
memset(result, 0, sizeof *result);
result->status = KITTY_PARSE_ERROR;
/* Split controls from payload and resume any incomplete upload. */
ks = kc->transfer;
semi = memchr(buf, ';', len);
controllen = (semi == NULL ? len : (size_t)(semi - buf));
@@ -1206,10 +1200,10 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
}
ks->more = 0;
error = kitty_control(ks, buf, controllen);
if (ks->image_id == 0) {
if (ks->image_number == 0)
ks->placement_id = 0;
}
if (ks->image_id == 0 && ks->image_number == 0)
ks->placement_id = 0;
/* Preserve reply fields even when the command cannot be accepted. */
result->image_id = ks->image_id;
result->image_number = ks->image_number;
result->quiet = ks->quiet;
@@ -1235,16 +1229,18 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
result->status = KITTY_PARSE_UNSUPPORTED;
goto fail;
}
if (payloadlen != 0) {
if (kitty_append(ks, semi + 1, payloadlen) != 0)
goto fail;
}
/* Accumulate decoded chunks until the final transmission. */
if (payloadlen != 0 && kitty_append(ks, semi + 1, payloadlen) != 0)
goto fail;
if (ks->more) {
kc->transfer = ks;
result->status = KITTY_PARSE_MORE;
return (NULL);
}
kc->transfer = NULL;
/* Placement and deletion commands need no new raster data. */
if (ks->action == 'p') {
source = kitty_source_get(kc, ks->image_id);
if (source == NULL) {
@@ -1298,6 +1294,7 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
goto fail;
}
/* Decode the complete PNG or raw pixel payload. */
decoded = ks->raw;
decodedlen = ks->rawlen;
ks->raw = NULL;
@@ -1338,6 +1335,7 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
if (pixels == NULL)
goto fail;
/* Pad the source canvas to whole cells before creating placements. */
cell_width = (xpixel == 0 ? 8 : xpixel);
cell_height = (ypixel == 0 ? 16 : ypixel);
image_size_in_cells(ks->width, ks->height, cell_width, cell_height,
@@ -1353,8 +1351,12 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
free(pixels);
else {
result->status = KITTY_PARSE_OK;
if (ks->action != 'q')
result->replace_id = kitty_source_set(kc, ks->image_id, source);
/* Queries validate the data without retaining an image. */
if (ks->action != 'q') {
result->replace_id = kitty_source_set(kc,
ks->image_id, source);
}
if (ks->action == 'q')
im = NULL;
else if (ks->action == 'T' && !ks->virtual) {
@@ -1487,8 +1489,8 @@ kitty_placeholder_to_image(void *state, struct grid *gd, struct grid_cell *gc,
struct kitty_context *kc = state;
struct kitty_source *source;
struct kitty_placement *placement;
struct kitty_placeholder left;
struct grid_cell left_cell;
struct kitty_placeholder left;
struct grid_cell left_cell;
struct image *im;
uint32_t value;
size_t offset = 0;
@@ -1498,15 +1500,19 @@ kitty_placeholder_to_image(void *state, struct grid *gd, struct grid_cell *gc,
if (kc == NULL)
return (0);
/* Decode the placeholder base character and its coordinate marks. */
if (!kitty_placeholder_character(gc->data.data, gc->data.size, &offset,
&value))
&value)) {
return (0);
}
if (value != 0x10eeee)
return (0);
while (offset < gc->data.size && nvalues < nitems(values)) {
if (!kitty_placeholder_character(gc->data.data, gc->data.size,
&offset, &value))
&offset, &value)) {
return (0);
}
if (!kitty_placeholder_index(value, &values[nvalues]))
return (0);
nvalues++;
@@ -1521,23 +1527,19 @@ kitty_placeholder_to_image(void *state, struct grid *gd, struct grid_cell *gc,
y = values[0];
if (nvalues >= 2)
x = values[1];
/* Inherit missing coordinates from the matching left placeholder. */
if (grid_x != 0) {
grid_view_get_cell(gd, grid_x - 1, grid_y, &left_cell);
if (left_cell.fg == gc->fg) {
if (left_cell.us == gc->us) {
inherit = image_grid_get_placeholder(gd,
grid_x - 1, gd->hsize + grid_y, &left);
}
if (left_cell.fg == gc->fg && left_cell.us == gc->us) {
inherit = image_grid_get_placeholder(gd,
grid_x - 1, gd->hsize + grid_y, &left);
}
}
if (inherit) {
if (nvalues >= 1) {
if (left.source_y != y)
inherit = 0;
}
if (nvalues >= 2) {
if (left.source_x + 1 != x)
inherit = 0;
if ((nvalues >= 1 && left.source_y != y) ||
(nvalues >= 2 && left.source_x + 1 != x)) {
inherit = 0;
}
}
if (inherit) {
@@ -1553,6 +1555,8 @@ kitty_placeholder_to_image(void *state, struct grid *gd, struct grid_cell *gc,
return (0);
id |= values[2] << 24;
}
/* Resolve the application image ID and its virtual placement. */
source = kitty_source_find(kc, id);
if (source == NULL)
return (0);
@@ -1560,9 +1564,9 @@ kitty_placeholder_to_image(void *state, struct grid *gd, struct grid_cell *gc,
placement = placement->next) {
if (!placement->virtual)
continue;
if (placement_id != 0) {
if (placement->placement_id != placement_id)
continue;
if (placement_id != 0 &&
placement->placement_id != placement_id) {
continue;
}
break;
}

View File

@@ -74,13 +74,8 @@ struct sixel_image {
struct sixel_line *lines;
};
/* One colour's encoded columns and controls in the current six-row band. */
struct sixel_plane {
/*
* A sixel_plane represents a single-colour SIXEL matrix for
* the current six-row band. stream contains the six-bit columns
* and sixel controls such as repeats and gaps for this colour.
*/
/* Position of the next encoded colour plane. */
u_int next_x;
u_int next_y;
@@ -109,17 +104,14 @@ struct sixel_image_cache {
struct sixel_image_cache *next;
};
/*
* Contiguous rows of one placement, held back so that they can be written as
* a single SIXEL instead of one per row. See sixel_draw_rect.
*/
/* Adjacent placement rows buffered for one SIXEL output sequence. */
struct sixel_pending {
struct image *image;
u_int source_x;
u_int source_x; /* Origin in image cells. */
u_int source_y;
u_int width;
u_int width; /* Size in cells. */
u_int height;
u_int destination_x;
u_int destination_x; /* Origin in terminal cells. */
u_int destination_y;
};
@@ -264,6 +256,7 @@ sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end)
char *endptr;
u_int x, y;
/* Skip the aspect ratio, then read the optional raster dimensions. */
last = cp;
while (last != end) {
if (*last != ';' && (*last < '0' || *last > '9'))
@@ -318,6 +311,7 @@ sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end)
char *endptr;
u_int c, type, c1, c2, c3;
/* Select the register before reading an optional colour definition. */
last = cp;
while (last != end) {
if (*last != ';' && (*last < '0' || *last > '9'))
@@ -357,6 +351,7 @@ sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end)
return (NULL);
}
/* Validate HLS or RGB components before extending the palette. */
if ((type != 1 && type != 2) ||
(type == 1 && (c1 > 360 || c2 > 100 || c3 > 100)) ||
(type == 2 && (c1 > 100 || c2 > 100 || c3 > 100))) {
@@ -419,7 +414,7 @@ sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end)
/* Parse SIXEL data into an indexed image. */
struct sixel_image *
sixel_parse(const char *buf, size_t len, u_int p1, u_int p2, u_int cell_w,
u_int cell_h)
u_int cell_h)
{
struct sixel_image *si;
const char *cp = buf, *end = buf + len;
@@ -430,12 +425,13 @@ sixel_parse(const char *buf, size_t len, u_int p1, u_int p2, u_int cell_w,
return (NULL);
}
si = xcalloc (1, sizeof *si);
si = xcalloc(1, sizeof *si);
si->cell_w = cell_w;
si->cell_h = cell_h;
si->p1 = p1;
si->p2 = p2;
/* Decode controls and six-pixel columns into indexed rows. */
while (cp != end) {
ch = *cp++;
switch (ch) {
@@ -590,12 +586,14 @@ sixel_colour_to_rgb(u_int colour, u_char *r, u_char *g, u_char *b)
struct image *
sixel_to_image(struct sixel_image *si)
{
u_char *pixels, *pixel, r, g, b;
u_int x, y, c, sx, sy;
struct image *im;
u_char *pixels, *pixel, r, g, b;
u_int x, y, c, sx, sy;
if ((uint64_t)si->sx * si->sy > IMAGE_SIZE_LIMIT / 4)
return (NULL);
/* Convert palette indexes to shared RGBA pixels. */
pixels = xcalloc((size_t)si->sx * si->sy, 4);
for (y = 0; y < si->sy; y++) {
for (x = 0; x < si->sx; x++) {
@@ -616,6 +614,7 @@ sixel_to_image(struct sixel_image *si)
pixel[3] = 255;
}
}
/* Keep transparent cell padding in the logical canvas. */
sixel_size_in_cells(si, &sx, &sy);
if ((uint64_t)sx * si->cell_w > UINT_MAX ||
(uint64_t)sy * si->cell_h > UINT_MAX) {
@@ -639,19 +638,15 @@ 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, raster_sx, raster_sy;
u_int pox, poy, psx, psy, tsx, tsy, px, py;
uint64_t source_left, source_right, source_top, source_bottom;
uint64_t target_left, target_right, target_top, target_bottom;
uint64_t source_left, source_right;
uint64_t source_top, source_bottom;
uint64_t target_left, target_right;
uint64_t target_top, target_bottom;
u_int x, y, i;
/*
* We want to get the section of the image at ox,oy in image cells and
* map it onto the same size in terminal cells.
*/
/* Clip image cells before mapping them to terminal cells. */
sixel_size_in_cells(si, &cx, &cy);
if (ox >= cx)
return (NULL);
if (oy >= cy)
if (ox >= cx || oy >= cy)
return (NULL);
if (ox + sx >= cx)
sx = cx - ox;
@@ -669,11 +664,7 @@ sixel_scale(struct sixel_image *si, u_int cell_w, u_int cell_h, u_int ox,
if (raster_sy > si->sy)
raster_sy = si->sy;
/*
* Map complete source cells at their real pixel boundaries and clamp
* only the final partial cell to the raster. Dividing the raster evenly
* between cells would stretch every complete cell and squash the last.
*/
/* Map whole cells at pixel boundaries and clip the final cell. */
source_left = (uint64_t)ox * si->cell_w;
source_right = (uint64_t)(ox + sx) * si->cell_w;
source_top = (uint64_t)oy * si->cell_h;
@@ -689,11 +680,7 @@ sixel_scale(struct sixel_image *si, u_int cell_w, u_int cell_h, u_int ox,
psx = source_right - source_left;
psy = source_bottom - source_top;
/*
* Preserve any partial final source cell. The grid still covers whole
* cells, but the SIXEL raster must end at the corresponding pixel offset
* rather than stretching to the cell boundary.
*/
/* Preserve the pixel extent of a partial final cell. */
target_right = ((uint64_t)raster_sx * cell_w + si->cell_w - 1) /
si->cell_w;
target_bottom = ((uint64_t)raster_sy * cell_h + si->cell_h - 1) /
@@ -713,7 +700,8 @@ sixel_scale(struct sixel_image *si, u_int cell_w, u_int cell_h, u_int ox,
if (tsx == 0 || tsy == 0)
return (NULL);
new = xcalloc (1, sizeof *si);
/* Resample the crop and preserve its palette and raster attributes. */
new = xcalloc(1, sizeof *si);
new->cell_w = cell_w;
new->cell_h = cell_h;
new->p1 = si->p1;
@@ -770,10 +758,11 @@ sixel_fit(struct sixel_image *si, u_int cell_w, u_int cell_h, u_int cells_x,
canvas_height = (uint64_t)cells_y * cell_h;
if (si->sx == 0 || si->sy == 0 || canvas_width == 0 ||
canvas_height == 0 || canvas_width > SIXEL_WIDTH_LIMIT ||
canvas_height > SIXEL_HEIGHT_LIMIT)
canvas_height > SIXEL_HEIGHT_LIMIT) {
return (NULL);
}
/* Use one scale factor so different terminal cell shapes do not distort. */
/* Use one scale factor to preserve the raster's aspect ratio. */
if ((uint64_t)cell_w * si->cell_h <=
(uint64_t)cell_h * si->cell_w) {
width = ((uint64_t)si->sx * cell_w + si->cell_w / 2) /
@@ -795,6 +784,7 @@ sixel_fit(struct sixel_image *si, u_int cell_w, u_int cell_h, u_int cells_x,
if (height > canvas_height)
height = canvas_height;
/* Resample the raster and pad the unused cell canvas. */
new = xcalloc(1, sizeof *new);
new->cell_w = cell_w;
new->cell_h = cell_h;
@@ -875,6 +865,7 @@ sixel_print_compress_colors(struct sixel_image *si, struct sixel_plane *planes,
struct sixel_line *sl;
for (x = 0; x < si->sx; x++) {
/* Collect this column's six-bit pattern for each colour. */
for (i = 0; i < 6; i++) {
pixels[i] = 0;
if (y + i < si->sy) {
@@ -887,6 +878,7 @@ sixel_print_compress_colors(struct sixel_image *si, struct sixel_plane *planes,
}
}
/* Compress patterns and gaps in active colour planes. */
for (i = 0; i < 6; i++) {
if (pixels[i] == 0)
continue;
@@ -903,11 +895,13 @@ sixel_print_compress_colors(struct sixel_image *si, struct sixel_plane *planes,
dx = x - plane->next_x;
if (plane->pattern != plane->next_pattern || dx != 0) {
sixel_print_repeat(&plane->stream, &plane->stream_len,
&plane->stream_used, plane->count,
sixel_print_repeat(&plane->stream,
&plane->stream_len, &plane->stream_used,
plane->count,
plane->pattern + 0x3f);
sixel_print_repeat(&plane->stream, &plane->stream_len,
&plane->stream_used, dx, '?');
sixel_print_repeat(&plane->stream,
&plane->stream_len, &plane->stream_used,
dx, '?');
plane->pattern = plane->next_pattern;
plane->count = 0;
}
@@ -952,14 +946,11 @@ sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size)
sixel_print_add(&buf, &len, &used, tmp, tmplen);
}
/* The colour panes in the current sixel-row band. */
/* Track colour planes containing pixels in the current six-row band. */
planes = xcalloc(used_colours, sizeof *planes);
/*
* active records which colour planes actually contain pixels
* in the current sixel-row band.
*/
active = xcalloc(used_colours, sizeof *active);
/* Emit the palette before the encoded colour planes. */
for (i = 0; i < ncolours; i++) {
c = colours[i];
tmplen = xsnprintf(tmp, sizeof tmp, "#%u;%u;%u;%u;%u",
@@ -973,6 +964,7 @@ sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size)
plane->stream = xmalloc(plane->stream_len);
}
/* Encode each six-row band using only its active colours. */
for (y = 0; y < si->sy; y += 6) {
nactive = 0;
sixel_print_compress_colors(si, planes, y, active, &nactive);
@@ -1011,7 +1003,7 @@ sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size)
return (buf);
}
/* Split a 5-bit RGB histogram into an adaptive palette using median cut. */
/* Update the occupied bounds and pixel count of a colour region. */
static void
sixel_box_update(struct sixel_box *box, struct sixel_hgram *hg)
{
@@ -1025,7 +1017,8 @@ sixel_box_update(struct sixel_box *box, struct sixel_hgram *hg)
for (red = box->red_min; red <= box->red_max; red++) {
for (green = box->green_min; green <= box->green_max; green++) {
for (blue = box->blue_min; blue <= box->blue_max; blue++) {
for (blue = box->blue_min; blue <= box->blue_max;
blue++) {
index = (red << 10)|(green << 5)|blue;
entry = &hg[index];
if (entry->count == 0)
@@ -1066,6 +1059,7 @@ sixel_box_split(struct sixel_box *box, struct sixel_box *new,
u_int red, green, blue, index, channel, first, last, level;
u_int red_range, green_range, blue_range, count = 0;
/* Split along the channel with the widest occupied range. */
red_range = box->red_max - box->red_min;
green_range = box->green_max - box->green_min;
blue_range = box->blue_max - box->blue_min;
@@ -1080,7 +1074,8 @@ sixel_box_split(struct sixel_box *box, struct sixel_box *new,
for (red = box->red_min; red <= box->red_max; red++) {
for (green = box->green_min; green <= box->green_max; green++) {
for (blue = box->blue_min; blue <= box->blue_max; blue++) {
for (blue = box->blue_min; blue <= box->blue_max;
blue++) {
index = (red << 10)|(green << 5)|blue;
if (channel == 0)
levels[red] += hg[index].count;
@@ -1101,6 +1096,7 @@ sixel_box_split(struct sixel_box *box, struct sixel_box *new,
first = box->blue_min;
last = box->blue_max;
}
/* Find the weighted median without emptying either resulting box. */
for (level = first; level < last; level++) {
count += levels[level];
if (count >= box->count / 2)
@@ -1132,9 +1128,9 @@ sixel_make_palette(struct sixel_hgram *hg,
{
struct sixel_box boxes[SIXEL_PALETTE_SIZE], new;
struct sixel_box *box;
uint64_t best_score, score, red, green, blue, count;
u_int i, nboxes = 1, best, r, g, b, index;
u_int red_range, green_range, blue_range;
uint64_t best_score, score, red, green, blue, count;
u_int i, nboxes = 1, best, r, g, b, index;
u_int red_range, green_range, blue_range;
memset(&boxes[0], 0, sizeof boxes[0]);
boxes[0].red_max = boxes[0].green_max = boxes[0].blue_max =
@@ -1143,6 +1139,7 @@ sixel_make_palette(struct sixel_hgram *hg,
if (boxes[0].count == 0)
return (0);
/* Repeatedly split the most populated and varied colour region. */
while (nboxes < SIXEL_PALETTE_SIZE) {
best = nboxes;
best_score = 0;
@@ -1160,17 +1157,20 @@ sixel_make_palette(struct sixel_hgram *hg,
}
}
if (best == nboxes ||
!sixel_box_split(&boxes[best], &new, hg))
!sixel_box_split(&boxes[best], &new, hg)) {
break;
}
memcpy(&boxes[nboxes++], &new, sizeof new);
}
/* Average each region's colours into one palette entry. */
for (i = 0; i < nboxes; i++) {
box = &boxes[i];
red = green = blue = count = 0;
for (r = box->red_min; r <= box->red_max; r++) {
for (g = box->green_min; g <= box->green_max; g++) {
for (b = box->blue_min; b <= box->blue_max; b++) {
for (b = box->blue_min; b <= box->blue_max;
b++) {
index = (r << 10)|(g << 5)|b;
red += hg[index].red;
green += hg[index].green;
@@ -1243,7 +1243,7 @@ sixel_from_image_pixel(const struct sixel_source *source, u_int source_x,
/* Render an image rectangle as an indexed SIXEL image. */
static struct sixel_image *
sixel_from_image(struct image *im, u_int cell_x, u_int cell_y, u_int cells_x,
u_int cells_y, u_int cell_w, u_int cell_h)
u_int cells_y, u_int cell_w, u_int cell_h)
{
struct sixel_image *si;
struct sixel_hgram *hg, *entry;
@@ -1252,9 +1252,11 @@ sixel_from_image(struct image *im, u_int cell_x, u_int cell_y, u_int cells_x,
const u_char *pixel;
uint16_t *cache;
int *current, *next, *tmp;
int red_error, green_error, blue_error, alpha_error;
int red_error, green_error, blue_error;
int alpha_error;
u_int x, y, sx, sy, index, error_index;
u_int source_x, source_y, source_width, source_height;
u_int source_x, source_y;
u_int source_width, source_height;
u_int red, green, blue, alpha, colour, i, ncolours;
uint64_t destination_width, destination_height;
uint64_t content_width, content_height;
@@ -1275,7 +1277,7 @@ sixel_from_image(struct image *im, u_int cell_x, u_int cell_y, u_int cells_x,
content_height = ((uint64_t)source.height * destination_height +
source.canvas_height - 1) / source.canvas_height;
/* Convert the requested cell rectangle to clipped output pixel bounds. */
/* Clip the requested cell rectangle to output pixel bounds. */
left = (uint64_t)cell_x * cell_w;
top = (uint64_t)cell_y * cell_h;
right = ((uint64_t)cell_x + cells_x) * cell_w;
@@ -1291,8 +1293,9 @@ sixel_from_image(struct image *im, u_int cell_x, u_int cell_y, u_int cells_x,
sx = right - left;
sy = bottom - top;
if (sx == 0 || sy == 0 || sx > SIXEL_WIDTH_LIMIT ||
sy > SIXEL_HEIGHT_LIMIT)
sy > SIXEL_HEIGHT_LIMIT) {
return (NULL);
}
/* Map the requested cell crop to the source image's pixel rectangle. */
image_get_pixel_rect(im, cell_x, cell_y, cells_x, cells_y, &source_x,
@@ -1304,12 +1307,13 @@ sixel_from_image(struct image *im, u_int cell_x, u_int cell_y, u_int cells_x,
hg = xcalloc(SIXEL_HISTOGRAM_SIZE, sizeof *hg);
for (y = 0; y < sy; y++) {
for (x = 0; x < sx; x++) {
pixel = sixel_from_image_pixel(&source, source_x, source_y,
source_width, source_height, sx, sy, x, y);
pixel = sixel_from_image_pixel(&source, source_x,
source_y, source_width, source_height, sx, sy,
x, y);
if (pixel[3] == 0)
continue;
/* Add this opaque pixel to its 5-bit RGB histogram bucket. */
/* Count visible pixels in their RGB histogram bin. */
index = ((pixel[0] >> 3) << 10)|
((pixel[1] >> 3) << 5)|(pixel[2] >> 3);
entry = &hg[index];
@@ -1324,7 +1328,7 @@ sixel_from_image(struct image *im, u_int cell_x, u_int cell_y, u_int cells_x,
if (ncolours == 0)
return (NULL);
/* Create the indexed SIXEL image and convert its palette to SIXEL RGB. */
/* Create the indexed image with a SIXEL RGB palette. */
si = xcalloc(1, sizeof *si);
si->cell_w = cell_w;
si->cell_h = cell_h;
@@ -1349,10 +1353,12 @@ sixel_from_image(struct image *im, u_int cell_x, u_int cell_y, u_int cells_x,
next = xcalloc(((size_t)sx + 2) * 4, sizeof *next);
for (y = 0; y < sy; y++) {
for (x = 0; x < sx; x++) {
pixel = sixel_from_image_pixel(&source, source_x, source_y,
source_width, source_height, sx, sy, x, y);
pixel = sixel_from_image_pixel(&source, source_x,
source_y, source_width, source_height, sx, sy,
x, y);
error_index = (x + 1) * 4;
/* SIXEL pixels are binary, so dither alpha separately. */
/* Dither alpha to SIXEL's binary transparency. */
alpha = sixel_clamp_colour((int)pixel[3] +
current[error_index + 3] / 16);
alpha_error = (int)alpha;
@@ -1364,19 +1370,18 @@ sixel_from_image(struct image *im, u_int cell_x, u_int cell_y, u_int cells_x,
current[error_index + 1] / 16);
blue = sixel_clamp_colour((int)pixel[2] +
current[error_index + 2] / 16);
colour = sixel_nearest_colour(palette, ncolours, cache,
red, green, blue);
colour = sixel_nearest_colour(palette, ncolours,
cache, red, green, blue);
if (sixel_set_pixel(si, x, y, colour + 1) != 0)
goto fail;
/* Calculate the RGB error introduced by palette quantization. */
/* Find the colour error. */
red_error = (int)red - palette[colour].red;
green_error = (int)green - palette[colour].green;
green_error = (int)green -
palette[colour].green;
blue_error = (int)blue - palette[colour].blue;
/*
* Diffuse the error with the Floyd-Steinberg 7/16, 3/16,
* 5/16, 1/16 kernel; the accumulated error is divided by 16.
*/
/* Diffuse error with the 7:3:5:1 kernel. */
current[error_index + 4] += red_error * 7;
current[error_index + 5] += green_error * 7;
current[error_index + 6] += blue_error * 7;
@@ -1411,7 +1416,7 @@ sixel_from_image(struct image *im, u_int cell_x, u_int cell_y, u_int cells_x,
return (si);
fail:
/* Discard a partially built image after an allocation or size failure. */
/* Discard the partially built image after a size failure. */
free(current);
free(next);
free(cache);
@@ -1484,7 +1489,7 @@ sixel_free_output(struct tty *tty, __unused int send)
if (so == NULL)
return;
/* The run is dropped, not written: the geometry it was measured at is gone. */
/* Discard pending rows measured at the old terminal geometry. */
so->pending.image = NULL;
for (cache = so->images; cache != NULL; cache = next) {
next = cache->next;
@@ -1503,7 +1508,7 @@ sixel_render_image(struct image *im, u_int cell_w, u_int cell_h)
u_int sx, sy;
image_get_size_in_cells(im, &sx, &sy);
/* Preserve SIXEL's original palette and indexed pixels when possible. */
/* Preserve the original SIXEL palette and pixels when possible. */
original = image_get_sixel(im);
if (original != NULL)
si = sixel_fit(original, cell_w, cell_h, sx, sy);
@@ -1519,14 +1524,15 @@ 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;
size_t size;
sixel_collect_images(so);
for (cache = so->images; cache != NULL; cache = cache->next) {
if (cache->server_id != image_get_id(im) ||
cache->cell_w != tty->xpixel ||
cache->cell_h != tty->ypixel)
cache->cell_h != tty->ypixel) {
continue;
}
cache->age = ++so->age;
return (cache->si);
}
@@ -1536,7 +1542,7 @@ sixel_get_image(struct tty *tty, struct image *im)
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. */
/* Use the rendered image without caching it. */
return (si);
}
while (so->size > IMAGE_SIZE_LIMIT - size) {
@@ -1617,14 +1623,7 @@ sixel_flush_output(struct tty *tty)
free(data);
}
/*
* Queue an image rectangle for SIXEL output. The redraw loop hands images to
* the backend one grid line at a time, so hold back a run of vertically
* adjacent rows and write them as one SIXEL instead of one per row.
* Anything that is not a continuation flushes the run first, and
* image_draw_flush() flushes what's left at the end of the redraw, so no
* other terminal output is reordered across a pending run.
*/
/* Queue adjacent placement rows for one SIXEL output sequence. */
void
sixel_draw_rect(struct tty *tty, const struct image_rect *rectangle)
{
@@ -1647,6 +1646,7 @@ sixel_draw_rect(struct tty *tty, const struct image_rect *rectangle)
return;
}
/* Flush a completed run before queuing a different rectangle. */
sixel_flush_output(tty);
sp->image = im;
sp->source_x = source_x;

248
image.c
View File

@@ -35,19 +35,19 @@
struct image_rect {
struct image *image;
int32_t z;
u_int source_x;
u_int source_x; /* Origin in image cells. */
u_int source_y;
u_int sx;
u_int sx; /* Size in cells. */
u_int sy;
u_int destination_x;
u_int destination_x; /* Origin in terminal cells. */
u_int destination_y;
};
/* One contiguous row of a placement in the grid. */
struct image_span {
u_int x;
u_int sx;
u_int source_x;
u_int x; /* Owning grid column. */
u_int sx; /* Number of image cells. */
u_int source_x; /* Origin in image cells. */
u_int source_y;
struct image_line *line;
struct image_placement *placement;
@@ -73,11 +73,11 @@ struct image_placement {
struct image_store *store;
struct image *image;
enum image_input input;
int placeholder;
int placeholder; /* Uses Unicode placeholders. */
u_int app_image_id;
u_int app_placement_id;
int32_t z;
uint64_t serial;
uint64_t serial; /* Layer order. */
struct image_spans spans;
TAILQ_ENTRY(image_placement) entry;
};
@@ -89,23 +89,26 @@ struct image_store {
struct image_placements placements;
};
/* Spans saved while overlapping grid cells are moved. */
struct image_move {
struct image_placement *placement;
u_int x;
u_int sx;
u_int source_x;
u_int x; /* Destination grid column. */
u_int sx; /* Number of image cells. */
u_int source_x; /* Origin in image cells. */
u_int source_y;
};
/* Pair source and destination placements during one grid copy. */
struct image_placement_map {
struct image_placement *source;
struct image_placement *destination;
};
/* Placement mappings kept only for the duration of a grid copy. */
struct image_copy_ctx {
struct grid *destination;
struct image_placement_map *maps;
size_t count;
struct grid *destination;
struct image_placement_map *maps;
size_t count; /* Placement mapping count. */
};
static struct images images = RB_INITIALIZER(&images);
@@ -138,8 +141,9 @@ 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)
tty->xpixel != 0 && tty->ypixel != 0) {
return (&image_backend_sixel);
}
return (&image_backend_fallback);
}
@@ -203,8 +207,9 @@ image_backend_flags(struct tty *tty)
/* Image scrolling is granted separately from text margin support. */
if ((tty->image_backend == &image_backend_sixel ||
tty->image_backend == &image_backend_kitty) &&
(tty->term->flags & TERM_IMAGESCROLL))
(tty->term->flags & TERM_IMAGESCROLL)) {
flags |= IMAGE_BACKEND_SCROLLS;
}
return (flags);
}
@@ -265,8 +270,9 @@ image_placement_cmp(const struct image_placement *a,
if (a->input == IMAGE_INPUT_KITTY && a->z != b->z)
return (a->z < b->z ? -1 : 1);
if (a->input == IMAGE_INPUT_KITTY &&
a->app_image_id != b->app_image_id)
a->app_image_id != b->app_image_id) {
return (a->app_image_id < b->app_image_id ? -1 : 1);
}
if (a->serial != b->serial)
return (a->serial < b->serial ? -1 : 1);
return (0);
@@ -302,8 +308,9 @@ image_line_get(struct grid_line *gl)
/* Create a logical image placement. */
static struct image_placement *
image_placement_create(struct grid *gd, struct image *im, enum image_input input,
u_int app_image_id, u_int app_placement_id, int32_t z)
image_placement_create(struct grid *gd, struct image *im,
enum image_input input, u_int app_image_id, u_int app_placement_id,
int32_t z)
{
struct image_store *store = image_store_get(gd);
struct image_placement *placement;
@@ -373,10 +380,7 @@ image_span_free(struct image_span *span)
free(span);
}
/*
* Remove a range from selected spans on a line - those of one placement if
* only is not NULL, otherwise those of the given input type.
*/
/* Remove a cell range from one placement or the selected input type. */
static void
image_line_remove(struct image_line *line, u_int x, u_int width,
enum image_input input,
@@ -394,9 +398,10 @@ image_line_remove(struct image_line *line, u_int x, u_int width,
if (only != NULL && span->placement != only)
continue;
if (input == IMAGE_INPUT_OVERWRITTEN) {
if (span->placement->input != IMAGE_INPUT_SIXEL) {
if (!span->placement->placeholder)
continue;
/* Keep ordinary Kitty placements on text writes. */
if (span->placement->input != IMAGE_INPUT_SIXEL &&
!span->placement->placeholder) {
continue;
}
} else if (input != IMAGE_INPUT_ALL) {
if (span->placement->input != input)
@@ -405,6 +410,8 @@ image_line_remove(struct image_line *line, u_int x, u_int width,
span_end = span->x + span->sx;
if (span_end <= x || span->x >= end)
continue;
/* Preserve pieces on either side of the removed cell range. */
if (span->x < x && span_end > end) {
right = span_end - end;
span->sx = x - span->x;
@@ -453,9 +460,10 @@ image_grid_remove_overwritten_spans(struct grid *gd, u_int x, u_int y,
return;
if (height > gd->hsize + gd->sy - y)
height = gd->hsize + gd->sy - y;
for (row = y; row < y + height; row++)
for (row = y; row < y + height; row++) {
image_line_remove(gd->linedata[row].images, x, width,
IMAGE_INPUT_OVERWRITTEN, NULL);
}
image_store_prune(gd->images);
}
@@ -500,11 +508,14 @@ image_grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py,
u_int start, end, span_end;
if (gd->images == NULL || nx == 0 || px == dx ||
py >= gd->hsize + gd->sy)
py >= gd->hsize + gd->sy) {
return;
}
line = gd->linedata[py].images;
if (line == NULL)
return;
/* Save source spans before removing the overlapping cell ranges. */
end = px + nx;
TAILQ_FOREACH(span, &line->spans, line_entry) {
span_end = span->x + span->sx;
@@ -521,11 +532,14 @@ image_grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py,
moves[count].source_y = span->source_y;
count++;
}
/* Replace both ranges and restore spans at their new columns. */
image_line_remove(line, px, nx, IMAGE_INPUT_ALL, NULL);
image_line_remove(line, dx, nx, IMAGE_INPUT_ALL, NULL);
for (i = 0; i < count; i++)
for (i = 0; i < count; i++) {
image_span_add(line, moves[i].placement, moves[i].x,
moves[i].sx, moves[i].source_x, moves[i].source_y);
}
free(moves);
image_store_prune(gd->images);
}
@@ -594,8 +608,9 @@ image_grid_copy_area(struct grid *dst, u_int destination_x,
if (dst == src || sx == 0 || sy == 0)
return;
if (destination_y >= dst->hsize + dst->sy ||
source_y >= src->hsize + src->sy)
source_y >= src->hsize + src->sy) {
return;
}
if (sy > dst->hsize + dst->sy - destination_y)
sy = dst->hsize + dst->sy - destination_y;
if (sy > src->hsize + src->sy - source_y)
@@ -604,6 +619,7 @@ image_grid_copy_area(struct grid *dst, u_int destination_x,
if (end < source_x)
end = UINT_MAX;
/* Copy clipped spans, sharing one destination placement per source. */
for (row = 0; row < sy; row++) {
source_line = src->linedata[source_y + row].images;
if (source_line == NULL)
@@ -644,8 +660,9 @@ image_grid_area_has_images(struct grid *gd, u_int x, u_int y, u_int width,
u_int row, end;
if (gd->images == NULL || width == 0 || height == 0 ||
y >= gd->hsize + gd->sy)
y >= gd->hsize + gd->sy) {
return (0);
}
end = x + width;
if (height > gd->hsize + gd->sy - y)
height = gd->hsize + gd->sy - y;
@@ -670,8 +687,9 @@ image_grid_get_placeholder(struct grid *gd, u_int x, u_int y,
struct image_span *span, *found = NULL;
if (y >= gd->hsize + gd->sy ||
(line = gd->linedata[y].images) == NULL)
(line = gd->linedata[y].images) == NULL) {
return (0);
}
TAILQ_FOREACH(span, &line->spans, line_entry) {
if (!span->placement->placeholder)
continue;
@@ -703,6 +721,7 @@ image_place_cell_kitty(struct screen_write_ctx *ctx, struct image *im,
struct image_line *line;
struct image_span *span;
/* Reuse a matching placeholder placement in this grid. */
TAILQ_FOREACH_REVERSE(candidate, &store->placements,
image_placements, entry) {
if (!candidate->placeholder)
@@ -721,6 +740,7 @@ image_place_cell_kitty(struct screen_write_ctx *ctx, struct image *im,
image_id, placement_id, z);
placement->placeholder = 1;
}
/* Extend an adjacent source run or add a new span for this cell. */
line = image_line_get(&gd->linedata[gd->hsize + y]);
TAILQ_FOREACH(span, &line->spans, line_entry) {
if (span->placement == placement && span->x + span->sx == x &&
@@ -745,6 +765,7 @@ image_sample(struct image *im, uint64_t sample_x, uint64_t sample_y,
uint64_t brightness = 0, count = 0;
u_int x, y, x0, x1, y0, y1;
/* Map sample bounds to pixels, counting transparent canvas padding. */
x0 = sample_x * im->canvas_width / sample_columns;
x1 = ((sample_x + 1) * im->canvas_width + sample_columns - 1) /
sample_columns;
@@ -763,6 +784,7 @@ image_sample(struct image *im, uint64_t sample_x, uint64_t sample_y,
if (y1 > im->height)
y1 = im->height;
/* Average premultiplied colour and brightness over the sample area. */
for (y = y0; y < y1; y++) {
for (x = x0; x < x1; x++) {
pixel = im->pixels + y * im->stride + x * 4;
@@ -789,7 +811,7 @@ static void
image_make_cells(struct image *im)
{
struct image_cell *cell;
uint64_t columns, rows;
uint64_t columns, rows, column, row;
u_int x, y, sample_x, sample_y;
columns = (uint64_t)im->sx * IMAGE_SAMPLE_COLUMNS;
@@ -799,14 +821,15 @@ image_make_cells(struct image *im)
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);
column = (uint64_t)x * IMAGE_SAMPLE_COLUMNS;
row = (uint64_t)y * IMAGE_SAMPLE_ROWS;
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,
sample_x < IMAGE_SAMPLE_COLUMNS;
sample_x++) {
image_sample(im, column + sample_x,
row + sample_y, columns, rows,
&cell->samples[sample_y][sample_x]);
}
}
@@ -964,13 +987,15 @@ image_create(u_int width, u_int height, u_int canvas_width,
struct image *im;
if (width == 0 || height == 0 || canvas_width < width ||
canvas_height < height || sx == 0 || sy == 0 || pixels == NULL)
canvas_height < height || sx == 0 || sy == 0 || pixels == NULL) {
return (NULL);
}
if ((uint64_t)width * height > SIZE_MAX / 4)
return (NULL);
if ((uint64_t)sx * sy > SIZE_MAX / sizeof *im->cells ||
sx > USHRT_MAX || sy > USHRT_MAX)
sx > USHRT_MAX || sy > USHRT_MAX) {
return (NULL);
}
if ((uint64_t)sx * sy > IMAGE_SIZE_LIMIT / sizeof *im->cells)
return (NULL);
im = image_alloc(width, height, canvas_width, canvas_height, sx, sy,
@@ -989,66 +1014,57 @@ image_create_view(struct image *source, const struct image_view *view)
u_int padded_width, padded_height, x, y, source_x, source_y;
int share_pixels = 1;
/* Validate the source crop and placement dimensions. */
if (source == NULL)
return (NULL);
if (view->x >= source->width)
if (view->x >= source->width || view->y >= source->height)
return (NULL);
if (view->y >= source->height)
if (view->width == 0 || view->height == 0 || view->scaled_width == 0 ||
view->scaled_height == 0 || view->sx == 0 || view->sy == 0) {
return (NULL);
if (view->width == 0)
}
if (view->width > source->width - view->x ||
view->height > source->height - view->y) {
return (NULL);
if (view->width > source->width - view->x)
return (NULL);
if (view->height == 0)
return (NULL);
if (view->height > source->height - view->y)
return (NULL);
if (view->scaled_width == 0)
return (NULL);
if (view->scaled_height == 0)
return (NULL);
if (view->sx == 0)
return (NULL);
if (view->sy == 0)
return (NULL);
if (view->sx > USHRT_MAX)
return (NULL);
if (view->sy > USHRT_MAX)
}
if (view->sx > USHRT_MAX || view->sy > USHRT_MAX)
return (NULL);
if ((uint64_t)view->sx * view->sy > SIZE_MAX / sizeof *im->cells)
return (NULL);
if ((uint64_t)view->sx * view->sy >
IMAGE_SIZE_LIMIT / sizeof *im->cells)
IMAGE_SIZE_LIMIT / sizeof *im->cells) {
return (NULL);
if (view->x_offset > UINT_MAX - view->scaled_width)
return (NULL);
if (view->y_offset > UINT_MAX - view->scaled_height)
}
/* Keep the padded raster inside the logical canvas. */
if (view->x_offset > UINT_MAX - view->scaled_width ||
view->y_offset > UINT_MAX - view->scaled_height) {
return (NULL);
}
padded_width = view->scaled_width + view->x_offset;
padded_height = view->scaled_height + view->y_offset;
if (padded_width > view->canvas_width)
return (NULL);
if (padded_height > view->canvas_height)
if (padded_width > view->canvas_width ||
padded_height > view->canvas_height) {
return (NULL);
}
/* Share pixels when no scaling or padding is needed. */
if (view->scaled_width != view->width)
share_pixels = 0;
if (view->scaled_height != view->height)
share_pixels = 0;
if (view->x_offset != 0)
share_pixels = 0;
if (view->y_offset != 0)
if (view->scaled_width != view->width ||
view->scaled_height != view->height ||
view->x_offset != 0 || view->y_offset != 0) {
share_pixels = 0;
}
if (share_pixels) {
im = image_alloc(view->width, view->height, view->canvas_width,
view->canvas_height, view->sx, view->sy, source->stride,
source->pixels + (size_t)view->y * source->stride +
(size_t)view->x * 4);
} else {
/* Resample the crop with transparent padding. */
if ((uint64_t)padded_width * padded_height >
IMAGE_SIZE_LIMIT / 4)
IMAGE_SIZE_LIMIT / 4) {
return (NULL);
}
pixels = xcalloc((size_t)padded_width * padded_height, 4);
for (y = 0; y < view->scaled_height; y++) {
source_y = view->y + (uint64_t)y * view->height /
@@ -1076,6 +1092,8 @@ image_create_view(struct image *source, const struct image_view *view)
(size_t)padded_width * 4, pixels);
im->flags |= IMAGE_FLAG_OWN_PIXELS;
}
/* Retain the source for both shared pixels and placement identity. */
im->parent_id = source->id;
im->source_id = source->source_id;
image_ref(source->id);
@@ -1140,8 +1158,9 @@ image_get_fallback_at(struct tty *tty, struct screen *s, u_int x, u_int y,
struct image_placement *placement;
if (image_backend_flags(tty) & IMAGE_BACKEND_GRAPHICAL ||
y >= s->grid->sy)
y >= s->grid->sy) {
return (0);
}
line = s->grid->linedata[s->grid->hsize + y].images;
if (line == NULL)
return (0);
@@ -1160,8 +1179,9 @@ image_get_fallback_at(struct tty *tty, struct screen *s, u_int x, u_int y,
return (-1);
}
if (placement->z < IMAGE_Z_BELOW_BACKGROUND &&
!COLOUR_DEFAULT(gc->bg))
!COLOUR_DEFAULT(gc->bg)) {
return (-1);
}
}
image_get_fallback_cell(tty, placement->image,
found->source_x + x - found->x, found->source_y, gc, out);
@@ -1178,8 +1198,9 @@ image_get_pixel_rect(const struct image *im, u_int x, u_int y,
*px = *py = *pwidth = *pheight = 0;
if (im == NULL || x >= im->sx || y >= im->sy || width == 0 ||
height == 0)
height == 0) {
return;
}
if (width > im->sx - x)
width = im->sx - x;
if (height > im->sy - y)
@@ -1245,7 +1266,10 @@ image_base64_decode(const char *data, size_t len, size_t limit, size_t *size)
memcpy(copy, data, len);
memset(copy + len, '=', padding);
copy[padded] = '\0';
out = xmalloc(needed == 0 ? 1 : needed);
if (needed == 0)
out = xmalloc(1);
else
out = xmalloc(needed);
result = b64_pton(copy, out, needed);
free(copy);
if (result < 0) {
@@ -1472,9 +1496,10 @@ image_redraw_area(struct screen_write_ctx *ctx, u_int px, u_int py, u_int nx,
if (wp == NULL)
return;
if (!image_grid_area_has_images(ctx->s->grid, px, ctx->s->grid->hsize + py,
nx, ny))
if (!image_grid_area_has_images(ctx->s->grid, px,
ctx->s->grid->hsize + py, nx, ny)) {
return;
}
redraw_damage_window(wp->window, wp->xoff + px, wp->yoff + py, nx,
ny);
}
@@ -1497,8 +1522,9 @@ image_redraw_scroll(struct screen_write_ctx *ctx)
if (wp == NULL)
return;
if (!image_grid_area_has_images(s->grid, 0, s->grid->hsize + s->rupper,
screen_size_x(s), s->rlower - s->rupper + 1))
screen_size_x(s), s->rlower - s->rupper + 1)) {
return;
}
redraw_damage_window_scroll(wp->window, wp->xoff,
wp->yoff + s->rupper, screen_size_x(s),
s->rlower - s->rupper + 1);
@@ -1568,6 +1594,8 @@ image_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py,
TAILQ_FOREACH(span, &line->spans, line_entry) {
placement = span->placement;
blank_only = 0;
/* Emulate Kitty's negative layers with SIXEL. */
if (placement->input == IMAGE_INPUT_KITTY &&
placement->z < 0) {
if (backend == &image_backend_sixel &&
@@ -1590,6 +1618,7 @@ image_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py,
px, atx, aty);
continue;
}
/* Draw below-text SIXEL layers only on cells without text. */
while (start < span_end) {
while (start < span_end &&
image_cell_has_text(s->grid, start, py))
@@ -1598,9 +1627,10 @@ image_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py,
while (draw_end < span_end &&
!image_cell_has_text(s->grid, draw_end, py))
draw_end++;
if (start < draw_end)
if (start < draw_end) {
image_draw_span(backend, tty, span, start,
draw_end, px, atx, aty);
}
start = draw_end;
}
}
@@ -1619,19 +1649,16 @@ image_cell_has_alpha(struct image *im, u_int x, u_int y)
pixels = im->pixels;
for (yy = py; yy < py + sy; yy++) {
for (xx = px; xx < px + sx; xx++) {
if (pixels[(size_t)yy * im->stride + (size_t)xx * 4 + 3] != 0)
if (pixels[(size_t)yy * im->stride +
(size_t)xx * 4 + 3] != 0) {
return (1);
}
}
}
return (0);
}
/*
* Return whether every visible pixel of one cell of an old image is also
* visible in the same cell of a new image, so that drawing the new image
* over the old one leaves nothing of the old cell showing. Only works if
* both images divide their cells into the same number of pixels.
*/
/* Return whether a new cell's pixels completely cover the old cell. */
static int
image_cell_covers(struct image *new, u_int nx, u_int ny, struct image *old,
u_int ox, u_int oy)
@@ -1640,8 +1667,9 @@ image_cell_covers(struct image *new, u_int nx, u_int ny, struct image *old,
const u_char *np, *op;
if (new->canvas_width / new->sx != old->canvas_width / old->sx ||
new->canvas_height / new->sy != old->canvas_height / old->sy)
new->canvas_height / new->sy != old->canvas_height / old->sy) {
return (0);
}
image_get_pixel_rect(new, nx, ny, 1, 1, &npx, &npy, &nsx, &nsy);
image_get_pixel_rect(old, ox, oy, 1, 1, &opx, &opy, &osx, &osy);
@@ -1660,12 +1688,7 @@ image_cell_covers(struct image *new, u_int nx, u_int ny, struct image *old,
return (1);
}
/*
* Remove the cells at one column of a line that a new SIXEL image completely
* covers. SIXEL is a single bitmap: drawing over another image replaces its
* pixels for good, so there is no point keeping the old cell to be drawn
* again underneath the new one on every repaint.
*/
/* Discard cells of older SIXEL placements fully covered by a new image. */
static void
image_line_cover(struct image_line *line, struct image_placement *placement,
u_int x, u_int source_x, u_int source_y)
@@ -1680,8 +1703,9 @@ image_line_cover(struct image_line *line, struct image_placement *placement,
if (x < span->x || x >= span->x + span->sx)
continue;
if (!image_cell_covers(placement->image, source_x, source_y,
old->image, span->source_x + x - span->x, span->source_y))
old->image, span->source_x + x - span->x, span->source_y)) {
continue;
}
image_line_remove(line, x, 1, IMAGE_INPUT_ALL, old);
}
}
@@ -1707,12 +1731,7 @@ image_extend_row(struct image_line *line, struct image_placement *placement,
}
}
/*
* Extend existing image placements to reveal more of an image after a pane
* grows wider. image_write() only creates spans for as much as fit at the
* time - unlike height, there is no "scroll right" to recover a clipped
* width later, so this is the only way the rest is ever shown.
*/
/* Extend image placements to reveal clipped columns when a pane grows. */
void
image_grid_resize_width(struct grid *gd, u_int new_sx)
{
@@ -1734,6 +1753,7 @@ image_grid_resize_width(struct grid *gd, u_int new_sx)
if (line == NULL)
continue;
/* Collect placements before modifying their spans. */
nseen = 0;
TAILQ_FOREACH(span, &line->spans, line_entry) {
found = 0;
@@ -1755,6 +1775,7 @@ image_grid_resize_width(struct grid *gd, u_int new_sx)
if (placement->placeholder)
continue;
/* Recover the origin and last visible source column. */
cx = end_x = source_y = 0;
found = 0;
TAILQ_FOREACH(span, &line->spans, line_entry) {
@@ -1786,7 +1807,8 @@ image_grid_resize_width(struct grid *gd, u_int new_sx)
/* Place an image at the cursor using the supplied input semantics. */
static void
image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg,
enum image_input input, u_int app_image_id, u_int app_placement_id, int32_t z)
enum image_input input, u_int app_image_id, u_int app_placement_id,
int32_t z)
{
struct screen *s = ctx->s;
struct grid *gd = s->grid;
@@ -1804,6 +1826,7 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg,
if (sx == 0)
return;
/* Scroll images that move the cursor, or clip stationary placements. */
if (im->flags & IMAGE_FLAG_NO_CURSOR) {
if (sy > screen_size_y(s) - cy)
sy = screen_size_y(s) - cy;
@@ -1833,12 +1856,8 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg,
placement = image_placement_create(gd, im, input, app_image_id,
app_placement_id, z);
/*
* Give the origin_y rows scrolled into history spans too, so they
* show when scrolled back to instead of appearing blank - cap to
* gd->hsize since screen_write_scrollup() may not have created a
* history row for every one of them.
*/
/* Attach scrolled-off image rows to the available history lines. */
hist_origin_y = origin_y;
if (hist_origin_y > gd->hsize)
hist_origin_y = gd->hsize;
@@ -1848,6 +1867,8 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg,
image_extend_row(line, placement, cx,
origin_y - hist_origin_y + y, 0, sx);
}
/* Attach visible runs, discarding SIXEL cells they completely cover. */
for (y = 0; y < sy; y++) {
line = image_line_get(&gd->linedata[gd->hsize + cy + y]);
for (x = 0; x < sx; x += run) {
@@ -1857,8 +1878,9 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg,
}
for (run = 1; x + run < sx; run++) {
if (!image_cell_has_alpha(im, x + run,
origin_y + y))
origin_y + y)) {
break;
}
}
if (input == IMAGE_INPUT_SIXEL) {
for (i = 0; i < run; i++) {
@@ -1870,6 +1892,8 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg,
origin_y + y);
}
}
/* Schedule a redraw and apply the input protocol's cursor movement. */
image_store_prune(gd->images);
image_redraw_area(ctx, cx, cy, sx, sy);
if (!(im->flags & IMAGE_FLAG_NO_CURSOR)) {

17
input.c
View File

@@ -2812,15 +2812,11 @@ input_reply_kitty(struct input_ctx *ictx,
if (result->quiet >= 2)
return;
if (result->quiet == 1) {
if (strcmp(message, "OK") == 0)
return;
}
if (result->image_id == 0) {
if (result->image_number == 0) {
if (result->action != 'q')
return;
}
if (result->quiet == 1 && strcmp(message, "OK") == 0)
return;
if (result->image_id == 0 && result->image_number == 0 &&
result->action != 'q') {
return;
}
if (result->image_number != 0) {
if (result->image_id != 0) {
@@ -2893,8 +2889,9 @@ input_exit_apc(struct input_ctx *ictx)
#ifdef ENABLE_IMAGES
if (ictx->input_len > 1 && ictx->input_buf[0] == 'G' &&
input_handle_kitty(ictx, ictx->input_buf + 1,
ictx->input_len - 1))
ictx->input_len - 1)) {
return;
}
#endif
if (wp != NULL &&

View File

@@ -395,12 +395,14 @@ recalculate_size(struct window *w, int now)
*/
if (w->flags & WINDOW_RESIZE) {
if (!now && changed && w->new_sx == sx && w->new_sy == sy &&
w->new_xpixel == xpixel && w->new_ypixel == ypixel)
w->new_xpixel == xpixel && w->new_ypixel == ypixel) {
changed = 0;
}
} else {
if (!now && changed && w->sx == sx && w->sy == sy &&
w->xpixel == xpixel && w->ypixel == ypixel)
w->xpixel == xpixel && w->ypixel == ypixel) {
changed = 0;
}
}
/*

View File

@@ -1171,8 +1171,9 @@ redraw_damage_window_flags(struct window *w, u_int x, u_int y, u_int sx,
TAILQ_FOREACH(rd, &w->damage, entry) {
if (x > rd->x + rd->sx || rd->x > x + sx ||
y > rd->y + rd->sy || rd->y > y + sy)
y > rd->y + rd->sy || rd->y > y + sy) {
continue;
}
x0 = (x < rd->x) ? x : rd->x;
y0 = (y < rd->y) ? y : rd->y;
@@ -1203,6 +1204,7 @@ redraw_damage_window_flags(struct window *w, u_int x, u_int y, u_int sx,
redraw_collapse_damage(w);
}
/* Record ordinary window damage. */
void
redraw_damage_window(struct window *w, u_int x, u_int y, u_int sx, u_int sy)
{
@@ -1647,12 +1649,7 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp,
bottom = scene->sy;
#ifdef ENABLE_IMAGES
/*
* Only erase cells this pane currently owns in the scene, not its raw
* geometry - a floating pane may be occluding part of this pane's
* rectangle, and erasing under it would leave those cells blank with
* nothing to redraw them back in.
*/
/* Erase only pane-owned spans so overlapping panes remain intact. */
if (flags & REDRAW_PANE) {
for (y = top; y < bottom; y++) {
line = &scene->lines[y];
@@ -1671,6 +1668,7 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp,
}
#endif
/* Draw images behind text, then text, then images above it. */
for (phase = REDRAW_IMAGES_BEFORE; phase <= REDRAW_IMAGES_AFTER;
phase++) {
for (y = top; y < bottom; y++) {
@@ -1682,18 +1680,20 @@ redraw_draw_pane_lines(struct redraw_draw_ctx *dctx, struct window_pane *wp,
if (flags & REDRAW_PANE) {
spans = &line->spans[REDRAW_SPAN_PANE];
TAILQ_FOREACH(span, spans, entry) {
if (span->data.p.wp == wp)
if (span->data.p.wp == wp) {
redraw_draw_span(dctx, span, cy,
phase);
}
}
}
if (phase == REDRAW_TEXT &&
(flags & REDRAW_PANE_SCROLLBAR)) {
spans = &line->spans[REDRAW_SPAN_SCROLLBAR];
TAILQ_FOREACH(span, spans, entry) {
if (span->data.sb.wp == wp)
if (span->data.sb.wp == wp) {
redraw_draw_span(dctx, span, cy,
phase);
}
}
}
}
@@ -1718,6 +1718,7 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
enum redraw_image_phase phase;
u_int y, cy, type;
/* Draw each image layer around the text spans. */
for (phase = REDRAW_IMAGES_BEFORE; phase <= REDRAW_IMAGES_AFTER;
phase++) {
for (y = 0; y < scene->sy; y++) {
@@ -1728,8 +1729,9 @@ redraw_draw_lines(struct redraw_draw_ctx *dctx, int flags)
cy = y;
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
if (phase != REDRAW_TEXT &&
type != REDRAW_SPAN_PANE)
type != REDRAW_SPAN_PANE) {
continue;
}
if (!REDRAW_IS_ALL(flags)) {
switch (type) {
case REDRAW_SPAN_PANE:
@@ -2050,8 +2052,9 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
if ((flags & REDRAW_PANE) &&
(image_backend_flags(tty) &
(IMAGE_BACKEND_GRAPHICAL|IMAGE_BACKEND_CLIPPED)) ==
IMAGE_BACKEND_GRAPHICAL)
IMAGE_BACKEND_GRAPHICAL) {
redraw_damage_window_pane_status(scene->w);
}
#endif
if (flags & REDRAW_PANE) {
@@ -2226,14 +2229,7 @@ redraw_draw_damage_rectangle(struct redraw_draw_ctx *dctx, u_int x, u_int y,
return;
#ifdef ENABLE_IMAGES
/*
* Remove stale Kitty placements this redraw is about to replace, the
* same as redraw_draw_pane_lines() does for a full pane redraw - a
* placement persists until explicitly deleted, unlike a plain SIXEL
* overwrite. Every span type is included, not just panes, since a
* moved floating pane can leave a placement over cells that now
* belong to something else.
*/
/* Retire placements on all damaged spans, including uncovered cells. */
for (yy = y; yy < y + sy; yy++) {
line = &scene->lines[yy];
if (dctx->flags & REDRAW_STATUS_TOP)
@@ -2256,6 +2252,7 @@ redraw_draw_damage_rectangle(struct redraw_draw_ctx *dctx, u_int x, u_int y,
}
#endif
/* Compose image layers and text across the damaged spans. */
for (phase = REDRAW_IMAGES_BEFORE; phase <= REDRAW_IMAGES_AFTER;
phase++) {
for (yy = y; yy < y + sy; yy++) {
@@ -2266,8 +2263,9 @@ redraw_draw_damage_rectangle(struct redraw_draw_ctx *dctx, u_int x, u_int y,
cy = yy;
for (type = 0; type < REDRAW_SPAN_TYPES; type++) {
if (phase != REDRAW_TEXT &&
type != REDRAW_SPAN_PANE)
type != REDRAW_SPAN_PANE) {
continue;
}
if (type == REDRAW_SPAN_STATUS)
continue;
spans = &line->spans[type];
@@ -2321,17 +2319,14 @@ redraw_client_damage_rect(struct client *c, struct redraw_draw_ctx *dctx,
u_int x0, y0, x1, y1;
#ifdef ENABLE_IMAGES
/*
* A scroll this terminal is trusted to have done itself has already
* moved everything in the region, text and images alike, so there is
* nothing to draw.
*/
/* Skip scroll damage already handled by this terminal. */
if ((rd->flags & REDRAW_DAMAGE_SCROLL) &&
(image_backend_flags(&c->tty) & IMAGE_BACKEND_SCROLLS) &&
c->tty.image_scroll_window == w &&
c->tty.image_scroll_epoch == w->image_scroll_epoch &&
!c->tty.image_scroll_failed)
!c->tty.image_scroll_failed) {
return;
}
#endif
x0 = (rd->x > ox) ? rd->x : ox;
y0 = (rd->y > oy) ? rd->y : oy;

View File

@@ -403,9 +403,10 @@ 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)
if (ctx->flags & SCREEN_WRITE_INPUT) {
image_grid_remove_overwritten_spans(ctx->s->grid, x,
ctx->s->grid->hsize + y, sx, sy);
}
}
#endif
@@ -1491,9 +1492,10 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
#ifdef ENABLE_IMAGES
image_redraw_all(ctx);
if (ctx->flags & SCREEN_WRITE_INPUT)
if (ctx->flags & SCREEN_WRITE_INPUT) {
image_grid_remove_overwritten_spans(s->grid, 0, s->grid->hsize,
screen_size_x(s), screen_size_y(s));
}
#endif
for (yy = 0; yy < screen_size_y(s); yy++) {

29
tmux.h
View File

@@ -1070,6 +1070,7 @@ struct style {
#define TTY_ACS_IMAGE_SEXTANT_FIRST 0x92
#define TTY_ACS_IMAGE_SEXTANT_LAST 0xcd
/* Averaged RGBA colour and brightness used by the text image backend. */
struct image_sample {
u_char red;
u_char green;
@@ -1079,20 +1080,25 @@ struct image_sample {
};
#define IMAGE_SAMPLE_COLUMNS 2
#define IMAGE_SAMPLE_ROWS 6
/* Whole-cell and subcell samples generated lazily for one image cell. */
struct image_cell {
struct image_sample whole;
struct image_sample samples[IMAGE_SAMPLE_ROWS][IMAGE_SAMPLE_COLUMNS];
};
/* Immutable pixels and derived output data shared by image placements. */
struct image {
u_int id;
u_int references;
u_int flags;
/* Retained parent (zero for sources) and original source IDs. */
u_int parent_id;
u_int source_id;
/* Pixel dimensions, including the logical transparent canvas. */
u_int width;
u_int height;
u_int canvas_width;
u_int canvas_height;
/* Grid dimensions and pixel buffer lengths in bytes. */
u_int sx;
u_int sy;
size_t stride;
@@ -1107,18 +1113,19 @@ struct image {
RB_HEAD(images, image);
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
/* Source crop and padded destination canvas for creating an image view. */
struct image_view {
u_int x;
u_int x; /* Crop origin in source pixels. */
u_int y;
u_int width;
u_int width; /* Crop size in source pixels. */
u_int height;
u_int scaled_width;
u_int scaled_width; /* Resampled crop size in pixels. */
u_int scaled_height;
u_int canvas_width;
u_int canvas_width; /* Padded canvas size in pixels. */
u_int canvas_height;
u_int sx;
u_int sx; /* Placement size in cells. */
u_int sy;
u_int x_offset;
u_int x_offset; /* Crop origin in canvas pixels. */
u_int y_offset;
};
@@ -1130,13 +1137,14 @@ enum kitty_parse_status {
KITTY_PARSE_UNSUPPORTED
};
/* Reply fields and placement actions returned by one Kitty command. */
struct kitty_parse_result {
u_int image_id;
u_int image_number;
u_int replace_id;
u_int replace_id; /* Old server ID, or zero. */
u_int placement_id;
u_int quiet;
u_int x;
u_int x; /* Deletion coordinates. */
u_int y;
int32_t z;
char action;
@@ -1144,9 +1152,10 @@ struct kitty_parse_result {
enum kitty_parse_status status;
};
/* Resolved source cell and application IDs of one Unicode placeholder. */
struct kitty_placeholder {
struct image *image;
u_int source_x;
u_int source_x; /* Origin in image cells. */
u_int source_y;
u_int image_id;
u_int placement_id;
@@ -4416,7 +4425,7 @@ void image_grid_copy_area(struct grid *, u_int, u_int, struct grid *,
u_int, u_int, u_int, u_int);
void image_grid_resize_width(struct grid *, u_int);
int image_grid_line_has_images(const struct grid_line *);
int image_grid_area_has_images(struct grid *, u_int, u_int, u_int,
int image_grid_area_has_images(struct grid *, u_int, u_int, u_int,
u_int);
int image_grid_get_placeholder(struct grid *, u_int, u_int,
struct kitty_placeholder *);

View File

@@ -94,6 +94,7 @@ static const struct tty_acs_entry tty_acs_table[] = {
#ifdef ENABLE_IMAGES
static char tty_acs_image_sextants[60][5];
/* Encode the Unicode sextant characters used by the image fallback. */
static void
tty_acs_image_sextants_init(void)
{
@@ -110,6 +111,7 @@ tty_acs_image_sextants_init(void)
}
}
/* Return the sextant ACS key for a six-bit foreground mask. */
u_char
tty_acs_image_sextant(u_int mask)
{

View File

@@ -272,18 +272,18 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
/* Get the current cell. */
grid_view_get_cell(gd, px + i, py, &gc);
#ifdef ENABLE_IMAGES
if (kitty_cell_is_placeholder(&gc)) {
if (image_grid_get_placeholder(gd,
px + i, gd->hsize + py,
&placeholder))
utf8_set(&gc.data, ' ');
if (kitty_cell_is_placeholder(&gc) &&
image_grid_get_placeholder(gd, px + i,
gd->hsize + py, &placeholder)) {
utf8_set(&gc.data, ' ');
}
#endif
gcp = &gc;
#ifdef ENABLE_IMAGES
if (image_get_fallback_at(tty, s, px + i, py, &gc,
&image_gc) == 1)
&image_gc) == 1) {
gcp = &image_gc;
}
#endif
/* Work out empty cells. */

19
tty.c
View File

@@ -158,9 +158,10 @@ 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;
int geometry_changed = 0;
geometry_changed = (tty->xpixel != xpixel || tty->ypixel != ypixel);
if (tty->xpixel != xpixel || tty->ypixel != ypixel)
geometry_changed = 1;
#endif
tty->sx = sx;
tty->sy = sy;
@@ -1940,14 +1941,12 @@ tty_cmd_cell(struct tty *tty, const struct tty_ctx *ctx)
struct grid_cell placeholder_cell;
struct kitty_placeholder placeholder;
if (kitty_cell_is_placeholder(cell)) {
if (image_grid_get_placeholder(ctx->s->grid, ctx->ocx,
ctx->s->grid->hsize + ctx->ocy, &placeholder)) {
memcpy(&placeholder_cell, cell,
sizeof placeholder_cell);
utf8_set(&placeholder_cell.data, ' ');
cell = &placeholder_cell;
}
if (kitty_cell_is_placeholder(cell) &&
image_grid_get_placeholder(ctx->s->grid, ctx->ocx,
ctx->s->grid->hsize + ctx->ocy, &placeholder)) {
memcpy(&placeholder_cell, cell, sizeof placeholder_cell);
utf8_set(&placeholder_cell.data, ' ');
cell = &placeholder_cell;
}
#endif

View File

@@ -298,7 +298,7 @@ struct window_copy_mode_data {
u_int oy; /* number of lines scrolled up */
u_int image_base; /* hsize - oy images were last drawn for */
int image_base_set;
int image_base_set; /* image_base is valid */
int image_refresh; /* current redraw needs image refresh */
u_int selx; /* beginning of selection */
@@ -5591,13 +5591,7 @@ window_copy_write_one(struct window_mode_entry *wme,
grid_get_cell(gd, fx, fy, &gc);
if (fx + gc.data.width <= nx) {
#ifdef ENABLE_IMAGES
/*
* Write image-covered cells directly into the grid,
* skipping window_copy_update_style() (a highlight
* must not sweep over the image) and
* screen_write_cell() (its image-damage call would
* re-damage the image on every redraw for nothing).
*/
/* Write image cells without styling or damage. */
if (image_grid_area_has_images(gd, fx, fy, gc.data.width,
1)) {
grid_view_set_cell(ctx->s->grid, px + fx, py,
@@ -5856,13 +5850,7 @@ window_copy_write_line(struct window_mode_entry *wme,
content_sx, &mgc, &cgc, &mkgc, &clgc);
#ifdef ENABLE_IMAGES
/*
* Copy the backing line's image layers separately: the text write
* above knows nothing about image content. Only redraw them when the
* view has actually moved (data->image_refresh) - otherwise they are
* already correct and redrawing would just flash them on every
* unrelated redraw.
*/
/* Copy image spans; refresh only when the history view moves. */
image_grid_free_line(s->grid,
&s->grid->linedata[s->grid->hsize + py]);
image_grid_copy_area(s->grid, width, s->grid->hsize + py,
@@ -5922,31 +5910,21 @@ window_copy_redraw_selection(struct window_mode_entry *wme, u_int old_y)
}
#ifdef ENABLE_IMAGES
/*
* Only rows whose underlying history position has moved since the last
* call need their images recomposited, to avoid flashing them on every
* unrelated redraw. Every caller of window_copy_write_line()/
* window_copy_write_lines() must call this first - it is not implied by
* them, since some write directly rather than via
* window_copy_redraw_lines().
*/
/* Refresh images only when the visible history range has moved. */
static void
window_copy_update_image_refresh(struct window_copy_mode_data *data)
{
u_int base;
base = screen_hsize(data->backing) - data->oy;
data->image_refresh = !data->image_base_set || base != data->image_base;
data->image_refresh = 0;
if (!data->image_base_set || base != data->image_base)
data->image_refresh = 1;
data->image_base = base;
data->image_base_set = 1;
}
/*
* Whether any part of the currently visible backing range carries image
* data. A scrolled insert/delete-line fast path only shifts character
* cells, leaving image content stale, so callers should fall back to a
* full window_copy_redraw_screen() when this returns true.
*/
/* Return whether the visible backing range contains image spans. */
static int
window_copy_visible_has_images(struct window_copy_mode_data *data)
{