mirror of
https://github.com/tmux/tmux.git
synced 2026-10-11 13:04:22 +00:00
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:
3
grid.c
3
grid.c
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
340
image-kitty.c
340
image-kitty.c
@@ -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;
|
||||
}
|
||||
|
||||
182
image-sixel.c
182
image-sixel.c
@@ -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
248
image.c
@@ -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
17
input.c
@@ -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 &&
|
||||
|
||||
6
resize.c
6
resize.c
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
29
tmux.h
@@ -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 *);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
12
tty-draw.c
12
tty-draw.c
@@ -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
19
tty.c
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user