mirror of
https://github.com/tmux/tmux.git
synced 2026-09-02 03:43:36 +00:00
1527 lines
40 KiB
C
1527 lines
40 KiB
C
/* $OpenBSD$ */
|
|
|
|
/*
|
|
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
|
|
* Copyright (c) 2026 Michael Grant <mgrant@grant.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <limits.h>
|
|
#include <png.h>
|
|
#include <resolv.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "tmux.h"
|
|
|
|
/* An average of part of an image cell. RGB is premultiplied. */
|
|
struct image_sample {
|
|
u_char red;
|
|
u_char green;
|
|
u_char blue;
|
|
u_char alpha;
|
|
u_char brightness;
|
|
};
|
|
|
|
/* Half blocks, quadrants and sextants all divide evenly into a 2 by 6 grid. */
|
|
#define IMAGE_SAMPLE_COLUMNS 2
|
|
#define IMAGE_SAMPLE_ROWS 6
|
|
#define IMAGE_FLAG_NO_CURSOR 0x1
|
|
#define IMAGE_Z_BELOW_BACKGROUND (INT32_MIN / 2)
|
|
struct image_cell {
|
|
struct image_sample whole;
|
|
struct image_sample samples[IMAGE_SAMPLE_ROWS][IMAGE_SAMPLE_COLUMNS];
|
|
};
|
|
|
|
/* Immutable image data and cell geometry. */
|
|
struct image {
|
|
u_int id;
|
|
u_int references;
|
|
u_int flags;
|
|
u_int parent_id;
|
|
u_int source_id;
|
|
u_int width;
|
|
u_int height;
|
|
u_int canvas_width;
|
|
u_int canvas_height;
|
|
u_int sx;
|
|
u_int sy;
|
|
size_t stride;
|
|
size_t size;
|
|
u_char *pixels;
|
|
/* Original indexed SIXEL data, if this image arrived as SIXEL. */
|
|
struct sixel_image *sixel;
|
|
struct image_cell *cells;
|
|
|
|
RB_ENTRY(image) entry;
|
|
};
|
|
RB_HEAD(images, image);
|
|
|
|
/* A cell-aligned part of an image to draw at a terminal position. */
|
|
struct image_rect {
|
|
struct image *image;
|
|
struct grid_cell cell;
|
|
int32_t z;
|
|
u_int source_x;
|
|
u_int source_y;
|
|
u_int sx;
|
|
u_int sy;
|
|
u_int destination_x;
|
|
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 source_y;
|
|
struct image_line *line;
|
|
struct image_placement *placement;
|
|
TAILQ_ENTRY(image_span) line_entry;
|
|
TAILQ_ENTRY(image_span) placement_entry;
|
|
};
|
|
TAILQ_HEAD(image_spans, image_span);
|
|
|
|
/* Image spans attached to one grid line. */
|
|
struct image_line {
|
|
struct image_spans spans;
|
|
};
|
|
|
|
#define IMAGE_INPUT_SIXEL 0
|
|
#define IMAGE_INPUT_KITTY 1
|
|
|
|
/* One logical image placement, shared by all of its row spans. */
|
|
struct image_placement {
|
|
struct image_store *store;
|
|
struct image *image;
|
|
u_int input;
|
|
u_int app_image_id;
|
|
u_int app_placement_id;
|
|
int32_t z;
|
|
uint64_t serial;
|
|
struct image_spans spans;
|
|
TAILQ_ENTRY(image_placement) entry;
|
|
};
|
|
TAILQ_HEAD(image_placements, image_placement);
|
|
|
|
/* Placements belonging to one grid. */
|
|
struct image_store {
|
|
struct grid *grid;
|
|
uint64_t next_serial;
|
|
struct image_placements placements;
|
|
};
|
|
|
|
static struct images images = RB_INITIALIZER(&images);
|
|
static u_int image_next_id;
|
|
|
|
struct image_backend {
|
|
const char *name;
|
|
int flags;
|
|
void (*draw_rect)(struct tty *,
|
|
const struct image_rect *, const struct tty_style_ctx *);
|
|
void (*free)(struct tty *, int);
|
|
};
|
|
|
|
static const struct image_backend image_backend_fallback = {
|
|
"fallback", IMAGE_BACKEND_SCROLLS, NULL, NULL
|
|
};
|
|
static const struct image_backend image_backend_kitty = {
|
|
"kitty", IMAGE_BACKEND_GRAPHICAL|IMAGE_BACKEND_SCROLLS,
|
|
kitty_draw_rect, kitty_free_output_state
|
|
};
|
|
static const struct image_backend image_backend_sixel = {
|
|
"sixel", IMAGE_BACKEND_GRAPHICAL,
|
|
sixel_draw_rect, sixel_free_output
|
|
};
|
|
|
|
/* Find the image backend supported by a terminal. */
|
|
static const struct image_backend *
|
|
image_tty_find_backend(struct tty *tty)
|
|
{
|
|
if (tty->term != NULL && tty->term->flags & TERM_KITTY)
|
|
return (&image_backend_kitty);
|
|
if (tty->term != NULL && tty->term->flags & TERM_SIXEL &&
|
|
tty->xpixel != 0 && tty->ypixel != 0)
|
|
return (&image_backend_sixel);
|
|
return (&image_backend_fallback);
|
|
}
|
|
|
|
/* Update a terminal's image backend after its capabilities change. */
|
|
void
|
|
image_tty_update(struct tty *tty)
|
|
{
|
|
const struct image_backend *backend;
|
|
|
|
backend = image_tty_find_backend(tty);
|
|
if (tty->image_backend == backend)
|
|
return;
|
|
|
|
if (tty->image_backend != NULL && tty->image_backend->free != NULL)
|
|
tty->image_backend->free(tty, !!(tty->flags & TTY_OPENED));
|
|
tty->image_data = NULL;
|
|
tty->image_backend = backend;
|
|
log_debug("%s: %s image backend is %s", __func__,
|
|
tty->client->name, backend->name);
|
|
}
|
|
|
|
/* Remove Kitty placements which will be replaced by a redraw. */
|
|
void
|
|
image_redraw_start(struct tty *tty, u_int x, u_int y, u_int width,
|
|
u_int height)
|
|
{
|
|
image_tty_update(tty);
|
|
if (tty->image_backend == &image_backend_kitty)
|
|
kitty_redraw_start(tty, x, y, width, height);
|
|
else if (tty->image_backend == &image_backend_sixel)
|
|
sixel_redraw_start(tty, x, y, width, height);
|
|
}
|
|
|
|
/* Return the flags for a terminal's image backend. */
|
|
int
|
|
image_backend_flags(struct tty *tty)
|
|
{
|
|
image_tty_update(tty);
|
|
return (tty->image_backend->flags);
|
|
}
|
|
|
|
/* Discard image backend state after a terminal geometry change. */
|
|
void
|
|
image_tty_geometry_changed(struct tty *tty)
|
|
{
|
|
image_tty_update(tty);
|
|
if (tty->image_backend->free != NULL)
|
|
tty->image_backend->free(tty, !!(tty->flags & TTY_OPENED));
|
|
}
|
|
|
|
/* Free image backend state for a terminal. */
|
|
void
|
|
image_tty_free(struct tty *tty, int send)
|
|
{
|
|
if (tty->image_backend != NULL && tty->image_backend->free != NULL)
|
|
tty->image_backend->free(tty, send);
|
|
tty->image_backend = NULL;
|
|
tty->image_data = NULL;
|
|
}
|
|
|
|
/* Compare images by server ID. */
|
|
static int
|
|
image_cmp(struct image *a, struct image *b)
|
|
{
|
|
if (a->id < b->id)
|
|
return (-1);
|
|
if (a->id > b->id)
|
|
return (1);
|
|
return (0);
|
|
}
|
|
RB_GENERATE_STATIC(images, image, entry, image_cmp);
|
|
|
|
/* Return the ordering band for a placement. */
|
|
static int
|
|
image_placement_band(const struct image_placement *placement)
|
|
{
|
|
if (placement->input == IMAGE_INPUT_SIXEL)
|
|
return (1);
|
|
if (placement->z < 0)
|
|
return (0);
|
|
return (2);
|
|
}
|
|
|
|
/* Compare two placements in logical drawing order. */
|
|
static int
|
|
image_placement_cmp(const struct image_placement *a,
|
|
const struct image_placement *b)
|
|
{
|
|
int aband, bband;
|
|
|
|
aband = image_placement_band(a);
|
|
bband = image_placement_band(b);
|
|
if (aband != bband)
|
|
return (aband < bband ? -1 : 1);
|
|
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)
|
|
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);
|
|
}
|
|
|
|
/* Allocate the image store for a grid when first needed. */
|
|
static struct image_store *
|
|
image_store_get(struct grid *gd)
|
|
{
|
|
struct image_store *store = gd->images;
|
|
|
|
if (store == NULL) {
|
|
store = xcalloc(1, sizeof *store);
|
|
store->grid = gd;
|
|
TAILQ_INIT(&store->placements);
|
|
gd->images = store;
|
|
}
|
|
return (store);
|
|
}
|
|
|
|
/* Allocate the image span list for a grid line when first needed. */
|
|
static struct image_line *
|
|
image_line_get(struct grid_line *gl)
|
|
{
|
|
struct image_line *line = gl->images;
|
|
|
|
if (line == NULL) {
|
|
line = xcalloc(1, sizeof *line);
|
|
TAILQ_INIT(&line->spans);
|
|
gl->images = line;
|
|
}
|
|
return (line);
|
|
}
|
|
|
|
/* Create a logical image placement. */
|
|
static struct image_placement *
|
|
image_placement_create(struct grid *gd, struct image *im, u_int 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;
|
|
|
|
placement = xcalloc(1, sizeof *placement);
|
|
placement->store = store;
|
|
placement->image = im;
|
|
placement->input = input;
|
|
placement->app_image_id = app_image_id;
|
|
placement->app_placement_id = app_placement_id;
|
|
placement->z = z;
|
|
placement->serial = ++store->next_serial;
|
|
if (placement->serial == 0)
|
|
placement->serial = ++store->next_serial;
|
|
TAILQ_INIT(&placement->spans);
|
|
TAILQ_INSERT_TAIL(&store->placements, placement, entry);
|
|
image_ref(im->id);
|
|
return (placement);
|
|
}
|
|
|
|
/* Free a placement which no longer has any spans. */
|
|
static void
|
|
image_placement_free(struct image_placement *placement)
|
|
{
|
|
if (!TAILQ_EMPTY(&placement->spans))
|
|
fatalx("freeing image placement with spans");
|
|
TAILQ_REMOVE(&placement->store->placements, placement, entry);
|
|
image_free(placement->image->id);
|
|
free(placement);
|
|
}
|
|
|
|
/* Insert a span into both its line and placement lists. */
|
|
static struct image_span *
|
|
image_span_add(struct image_line *line, struct image_placement *placement,
|
|
u_int x, u_int width, u_int source_x, u_int source_y)
|
|
{
|
|
struct image_span *span, *at;
|
|
|
|
if (width == 0)
|
|
return (NULL);
|
|
span = xcalloc(1, sizeof *span);
|
|
span->x = x;
|
|
span->sx = width;
|
|
span->source_x = source_x;
|
|
span->source_y = source_y;
|
|
span->line = line;
|
|
span->placement = placement;
|
|
TAILQ_FOREACH(at, &line->spans, line_entry) {
|
|
if (image_placement_cmp(placement, at->placement) < 0 ||
|
|
(placement == at->placement && x < at->x)) {
|
|
TAILQ_INSERT_BEFORE(at, span, line_entry);
|
|
goto inserted;
|
|
}
|
|
}
|
|
TAILQ_INSERT_TAIL(&line->spans, span, line_entry);
|
|
inserted:
|
|
TAILQ_INSERT_TAIL(&placement->spans, span, placement_entry);
|
|
return (span);
|
|
}
|
|
|
|
/* Unlink and free one span without pruning its placement. */
|
|
static void
|
|
image_span_free(struct image_span *span)
|
|
{
|
|
TAILQ_REMOVE(&span->line->spans, span, line_entry);
|
|
TAILQ_REMOVE(&span->placement->spans, span, placement_entry);
|
|
free(span);
|
|
}
|
|
|
|
/* Remove a range from selected spans on a line. */
|
|
static void
|
|
image_line_remove(struct image_line *line, u_int x, u_int width, int input)
|
|
{
|
|
struct image_span *span, *next;
|
|
u_int end, span_end, right;
|
|
|
|
if (line == NULL || width == 0)
|
|
return;
|
|
end = x + width;
|
|
if (end < x)
|
|
end = UINT_MAX;
|
|
TAILQ_FOREACH_SAFE(span, &line->spans, line_entry, next) {
|
|
if (input != -1 && span->placement->input != (u_int)input)
|
|
continue;
|
|
span_end = span->x + span->sx;
|
|
if (span_end <= x || span->x >= end)
|
|
continue;
|
|
if (span->x < x && span_end > end) {
|
|
right = span_end - end;
|
|
span->sx = x - span->x;
|
|
image_span_add(line, span->placement, end, right,
|
|
span->source_x + end - span->x, span->source_y);
|
|
continue;
|
|
}
|
|
if (span->x < x) {
|
|
span->sx = x - span->x;
|
|
continue;
|
|
}
|
|
if (span_end > end) {
|
|
span->source_x += end - span->x;
|
|
span->sx = span_end - end;
|
|
span->x = end;
|
|
continue;
|
|
}
|
|
image_span_free(span);
|
|
}
|
|
}
|
|
|
|
/* Remove placement records which have no remaining spans. */
|
|
static void
|
|
image_store_prune(struct image_store *store)
|
|
{
|
|
struct image_placement *placement, *next;
|
|
|
|
if (store == NULL)
|
|
return;
|
|
TAILQ_FOREACH_SAFE(placement, &store->placements, entry, next) {
|
|
if (TAILQ_EMPTY(&placement->spans))
|
|
image_placement_free(placement);
|
|
}
|
|
}
|
|
|
|
/* Remove temporal image data overwritten by text. */
|
|
void
|
|
image_grid_damage(struct grid *gd, u_int x, u_int y, u_int width,
|
|
u_int height)
|
|
{
|
|
u_int row;
|
|
|
|
if (gd->images == NULL || width == 0 || height == 0)
|
|
return;
|
|
if (y >= gd->hsize + gd->sy)
|
|
return;
|
|
if (height > gd->hsize + gd->sy - y)
|
|
height = gd->hsize + gd->sy - y;
|
|
for (row = y; row < y + height; row++)
|
|
image_line_remove(gd->linedata[row].images, x, width,
|
|
IMAGE_INPUT_SIXEL);
|
|
image_store_prune(gd->images);
|
|
}
|
|
|
|
/* Free all image spans belonging to a grid line. */
|
|
void
|
|
image_grid_free_line(struct grid *gd, struct grid_line *gl)
|
|
{
|
|
struct image_line *line = gl->images;
|
|
struct image_span *span, *next;
|
|
|
|
if (line == NULL)
|
|
return;
|
|
TAILQ_FOREACH_SAFE(span, &line->spans, line_entry, next)
|
|
image_span_free(span);
|
|
free(line);
|
|
gl->images = NULL;
|
|
image_store_prune(gd->images);
|
|
}
|
|
|
|
/* Free the empty image store when a grid is destroyed. */
|
|
void
|
|
image_grid_free(struct grid *gd)
|
|
{
|
|
if (gd->images == NULL)
|
|
return;
|
|
image_store_prune(gd->images);
|
|
if (!TAILQ_EMPTY(&gd->images->placements))
|
|
fatalx("freeing grid with image placements");
|
|
free(gd->images);
|
|
gd->images = NULL;
|
|
}
|
|
|
|
/* Move image spans with a range of grid cells. */
|
|
void
|
|
image_grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py,
|
|
u_int nx)
|
|
{
|
|
struct image_line *line;
|
|
struct image_span *span;
|
|
struct image_move {
|
|
struct image_placement *placement;
|
|
u_int x, sx, source_x, source_y;
|
|
} *moves = NULL;
|
|
size_t count = 0;
|
|
u_int start, end, span_end;
|
|
|
|
if (gd->images == NULL || nx == 0 || px == dx ||
|
|
py >= gd->hsize + gd->sy)
|
|
return;
|
|
line = gd->linedata[py].images;
|
|
if (line == NULL)
|
|
return;
|
|
end = px + nx;
|
|
TAILQ_FOREACH(span, &line->spans, line_entry) {
|
|
span_end = span->x + span->sx;
|
|
start = (span->x > px ? span->x : px);
|
|
if (start >= end || span_end <= px)
|
|
continue;
|
|
if (span_end > end)
|
|
span_end = end;
|
|
moves = xreallocarray(moves, count + 1, sizeof *moves);
|
|
moves[count].placement = span->placement;
|
|
moves[count].x = dx + start - px;
|
|
moves[count].sx = span_end - start;
|
|
moves[count].source_x = span->source_x + start - span->x;
|
|
moves[count].source_y = span->source_y;
|
|
count++;
|
|
}
|
|
image_line_remove(line, px, nx, -1);
|
|
image_line_remove(line, dx, nx, -1);
|
|
for (size_t 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);
|
|
}
|
|
|
|
/* Duplicate image spans alongside a group of grid lines. */
|
|
void
|
|
image_grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src,
|
|
u_int sy, u_int ny)
|
|
{
|
|
struct image_map {
|
|
struct image_placement *source;
|
|
struct image_placement *destination;
|
|
} *maps = NULL;
|
|
struct image_placement *placement;
|
|
struct image_line *source_line, *destination_line;
|
|
struct image_span *span;
|
|
size_t count = 0, i;
|
|
u_int row;
|
|
|
|
for (row = 0; row < ny; row++) {
|
|
source_line = src->linedata[sy + row].images;
|
|
if (source_line == NULL)
|
|
continue;
|
|
destination_line = image_line_get(&dst->linedata[dy + row]);
|
|
TAILQ_FOREACH(span, &source_line->spans, line_entry) {
|
|
placement = NULL;
|
|
for (i = 0; i < count; i++) {
|
|
if (maps[i].source == span->placement) {
|
|
placement = maps[i].destination;
|
|
break;
|
|
}
|
|
}
|
|
if (placement == NULL) {
|
|
placement = image_placement_create(dst,
|
|
span->placement->image, span->placement->input,
|
|
span->placement->app_image_id,
|
|
span->placement->app_placement_id,
|
|
span->placement->z);
|
|
maps = xreallocarray(maps, count + 1, sizeof *maps);
|
|
maps[count].source = span->placement;
|
|
maps[count].destination = placement;
|
|
count++;
|
|
}
|
|
image_span_add(destination_line, placement, span->x,
|
|
span->sx, span->source_x, span->source_y);
|
|
}
|
|
}
|
|
free(maps);
|
|
}
|
|
|
|
/* Copy clipped image spans between grid areas. */
|
|
void
|
|
image_grid_copy_area(struct grid *dst, u_int destination_x,
|
|
u_int destination_y, struct grid *src, u_int source_x, u_int source_y,
|
|
u_int sx, u_int sy)
|
|
{
|
|
struct image_map {
|
|
struct image_placement *source;
|
|
struct image_placement *destination;
|
|
} *maps = NULL;
|
|
struct image_placement *placement;
|
|
struct image_line *source_line, *destination_line;
|
|
struct image_span *span;
|
|
size_t count = 0, i;
|
|
u_int row, start, end, span_end;
|
|
|
|
if (dst == src || sx == 0 || sy == 0)
|
|
return;
|
|
if (destination_y >= dst->hsize + dst->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)
|
|
sy = src->hsize + src->sy - source_y;
|
|
end = source_x + sx;
|
|
if (end < source_x)
|
|
end = UINT_MAX;
|
|
|
|
for (row = 0; row < sy; row++) {
|
|
source_line = src->linedata[source_y + row].images;
|
|
if (source_line == NULL)
|
|
continue;
|
|
destination_line = image_line_get(
|
|
&dst->linedata[destination_y + row]);
|
|
TAILQ_FOREACH(span, &source_line->spans, line_entry) {
|
|
span_end = span->x + span->sx;
|
|
start = (span->x > source_x ? span->x : source_x);
|
|
if (start >= end || span_end <= source_x)
|
|
continue;
|
|
if (span_end > end)
|
|
span_end = end;
|
|
|
|
placement = NULL;
|
|
for (i = 0; i < count; i++) {
|
|
if (maps[i].source == span->placement) {
|
|
placement = maps[i].destination;
|
|
break;
|
|
}
|
|
}
|
|
if (placement == NULL) {
|
|
placement = image_placement_create(dst,
|
|
span->placement->image, span->placement->input,
|
|
span->placement->app_image_id,
|
|
span->placement->app_placement_id,
|
|
span->placement->z);
|
|
maps = xreallocarray(maps, count + 1,
|
|
sizeof *maps);
|
|
maps[count].source = span->placement;
|
|
maps[count].destination = placement;
|
|
count++;
|
|
}
|
|
image_span_add(destination_line, placement,
|
|
destination_x + start - source_x, span_end - start,
|
|
span->source_x + start - span->x, span->source_y);
|
|
}
|
|
}
|
|
free(maps);
|
|
}
|
|
|
|
/* Return whether a grid line contains any image spans. */
|
|
int
|
|
image_grid_line_has_images(const struct grid_line *gl)
|
|
{
|
|
return (gl->images != NULL && !TAILQ_EMPTY(&gl->images->spans));
|
|
}
|
|
|
|
/* Return whether a grid rectangle contains any image spans. */
|
|
int
|
|
image_grid_check_area(struct grid *gd, u_int x, u_int y, u_int width,
|
|
u_int height)
|
|
{
|
|
struct image_line *line;
|
|
struct image_span *span;
|
|
u_int row, end;
|
|
|
|
if (gd->images == NULL || width == 0 || height == 0 ||
|
|
y >= gd->hsize + gd->sy)
|
|
return (0);
|
|
end = x + width;
|
|
if (height > gd->hsize + gd->sy - y)
|
|
height = gd->hsize + gd->sy - y;
|
|
for (row = y; row < y + height; row++) {
|
|
line = gd->linedata[row].images;
|
|
if (line == NULL)
|
|
continue;
|
|
TAILQ_FOREACH(span, &line->spans, line_entry) {
|
|
if (span->x < end && span->x + span->sx > x)
|
|
return (1);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/* Find source coordinates for an image span at one grid cell. */
|
|
int
|
|
image_grid_get_source(struct grid *gd, u_int x, u_int y, struct image *im,
|
|
u_int *source_x, u_int *source_y)
|
|
{
|
|
struct image_line *line;
|
|
struct image_span *span, *found = NULL;
|
|
|
|
if (y >= gd->hsize + gd->sy ||
|
|
(line = gd->linedata[y].images) == NULL)
|
|
return (0);
|
|
TAILQ_FOREACH(span, &line->spans, line_entry) {
|
|
if (span->placement->image == im && x >= span->x &&
|
|
x < span->x + span->sx)
|
|
found = span;
|
|
}
|
|
if (found == NULL)
|
|
return (0);
|
|
*source_x = found->source_x + x - found->x;
|
|
*source_y = found->source_y;
|
|
return (1);
|
|
}
|
|
|
|
/* Add one Kitty Unicode-placeholder cell to an image placement. */
|
|
void
|
|
image_place_cell_kitty(struct screen_write_ctx *ctx, struct image *im,
|
|
u_int x, u_int y, u_int source_x, u_int source_y, u_int image_id,
|
|
u_int placement_id, int32_t z)
|
|
{
|
|
struct grid *gd = ctx->s->grid;
|
|
struct image_store *store = image_store_get(gd);
|
|
struct image_placement *placement = NULL, *candidate;
|
|
struct image_line *line;
|
|
struct image_span *span;
|
|
|
|
TAILQ_FOREACH_REVERSE(candidate, &store->placements,
|
|
image_placements, entry) {
|
|
if (candidate->input == IMAGE_INPUT_KITTY &&
|
|
candidate->image == im &&
|
|
candidate->app_image_id == image_id &&
|
|
candidate->app_placement_id == placement_id &&
|
|
candidate->z == z) {
|
|
placement = candidate;
|
|
break;
|
|
}
|
|
}
|
|
if (placement == NULL)
|
|
placement = image_placement_create(gd, im, IMAGE_INPUT_KITTY,
|
|
image_id, placement_id, z);
|
|
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 &&
|
|
span->source_y == source_y &&
|
|
span->source_x + span->sx == source_x) {
|
|
span->sx++;
|
|
image_redraw_area(ctx, x, y, 1, 1);
|
|
return;
|
|
}
|
|
}
|
|
image_span_add(line, placement, x, 1, source_x, source_y);
|
|
image_redraw_area(ctx, x, y, 1, 1);
|
|
}
|
|
|
|
/* Average a rectangle of image pixels into one fallback sample. */
|
|
static void
|
|
image_sample(struct image *im, uint64_t sample_x, uint64_t sample_y,
|
|
uint64_t sample_columns, uint64_t sample_rows, struct image_sample *sample)
|
|
{
|
|
const u_char *pixel;
|
|
uint64_t red = 0, green = 0, blue = 0, alpha = 0;
|
|
uint64_t brightness = 0, count = 0;
|
|
u_int x, y, x0, x1, y0, y1;
|
|
|
|
x0 = sample_x * im->canvas_width / sample_columns;
|
|
x1 = ((sample_x + 1) * im->canvas_width + sample_columns - 1) /
|
|
sample_columns;
|
|
y0 = sample_y * im->canvas_height / sample_rows;
|
|
y1 = ((sample_y + 1) * im->canvas_height + sample_rows - 1) /
|
|
sample_rows;
|
|
if (x1 <= x0)
|
|
x1 = x0 + 1;
|
|
if (y1 <= y0)
|
|
y1 = y0 + 1;
|
|
count = (uint64_t)(x1 - x0) * (y1 - y0);
|
|
if (x0 >= im->width || y0 >= im->height)
|
|
return;
|
|
if (x1 > im->width)
|
|
x1 = im->width;
|
|
if (y1 > im->height)
|
|
y1 = im->height;
|
|
|
|
for (y = y0; y < y1; y++) {
|
|
for (x = x0; x < x1; x++) {
|
|
pixel = im->pixels + y * im->stride + x * 4;
|
|
red += pixel[0] * pixel[3] / 255;
|
|
green += pixel[1] * pixel[3] / 255;
|
|
blue += pixel[2] * pixel[3] / 255;
|
|
alpha += pixel[3];
|
|
brightness += ((2126ULL * pixel[0] +
|
|
7152ULL * pixel[1] + 722ULL * pixel[2]) / 10000) *
|
|
pixel[3] / 255;
|
|
}
|
|
}
|
|
if (count == 0)
|
|
return;
|
|
sample->red = red / count;
|
|
sample->green = green / count;
|
|
sample->blue = blue / count;
|
|
sample->alpha = alpha / count;
|
|
sample->brightness = brightness / count;
|
|
}
|
|
|
|
/* Build fallback samples for every image cell. */
|
|
static void
|
|
image_make_cells(struct image *im)
|
|
{
|
|
struct image_cell *cell;
|
|
uint64_t columns, rows;
|
|
u_int x, y, sample_x, sample_y;
|
|
|
|
columns = (uint64_t)im->sx * IMAGE_SAMPLE_COLUMNS;
|
|
rows = (uint64_t)im->sy * IMAGE_SAMPLE_ROWS;
|
|
im->cells = xcalloc((size_t)im->sx * im->sy, sizeof *im->cells);
|
|
for (y = 0; y < im->sy; y++) {
|
|
for (x = 0; x < im->sx; x++) {
|
|
cell = &im->cells[(size_t)y * im->sx + x];
|
|
image_sample(im, x, y, im->sx, im->sy, &cell->whole);
|
|
for (sample_y = 0; sample_y < IMAGE_SAMPLE_ROWS;
|
|
sample_y++) {
|
|
for (sample_x = 0;
|
|
sample_x < IMAGE_SAMPLE_COLUMNS; sample_x++) {
|
|
image_sample(im,
|
|
(uint64_t)x * IMAGE_SAMPLE_COLUMNS + sample_x,
|
|
(uint64_t)y * IMAGE_SAMPLE_ROWS + sample_y,
|
|
columns, rows,
|
|
&cell->samples[sample_y][sample_x]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Find an image by server ID. */
|
|
struct image *
|
|
image_find(u_int id)
|
|
{
|
|
struct image find;
|
|
|
|
find.id = id;
|
|
return (RB_FIND(images, &images, &find));
|
|
}
|
|
|
|
/* Return an image's server ID. */
|
|
u_int
|
|
image_get_id(const struct image *im)
|
|
{
|
|
return (im->id);
|
|
}
|
|
|
|
/* Return an image's pixel dimensions. */
|
|
void
|
|
image_get_size(const struct image *im, u_int *width, u_int *height)
|
|
{
|
|
if (width != NULL)
|
|
*width = im->width;
|
|
if (height != NULL)
|
|
*height = im->height;
|
|
}
|
|
|
|
/* Return an image canvas's pixel dimensions. */
|
|
void
|
|
image_get_canvas_size(const struct image *im, u_int *width,
|
|
u_int *height)
|
|
{
|
|
if (width != NULL)
|
|
*width = im->canvas_width;
|
|
if (height != NULL)
|
|
*height = im->canvas_height;
|
|
}
|
|
|
|
/* Return an image's cell dimensions. */
|
|
void
|
|
image_get_size_in_cells(const struct image *im, u_int *sx, u_int *sy)
|
|
{
|
|
if (sx != NULL)
|
|
*sx = im->sx;
|
|
if (sy != NULL)
|
|
*sy = im->sy;
|
|
}
|
|
|
|
/* Return an image's RGBA pixels and layout. */
|
|
const u_char *
|
|
image_get_pixels(const struct image *im, size_t *stride, size_t *size)
|
|
{
|
|
if (stride != NULL)
|
|
*stride = im->stride;
|
|
if (size != NULL)
|
|
*size = im->size;
|
|
return (im->pixels);
|
|
}
|
|
|
|
/* Suppress cursor movement when writing an image. */
|
|
void
|
|
image_set_no_cursor(struct image *im)
|
|
{
|
|
im->flags |= IMAGE_FLAG_NO_CURSOR;
|
|
}
|
|
|
|
/* Return an image's original SIXEL data. */
|
|
struct sixel_image *
|
|
image_get_sixel(const struct image *im)
|
|
{
|
|
return (im->sixel);
|
|
}
|
|
|
|
/* Associate original SIXEL data with an image. */
|
|
void
|
|
image_set_sixel(struct image *im, struct sixel_image *si)
|
|
{
|
|
im->sixel = si;
|
|
}
|
|
|
|
/* Return the image for a drawing rectangle. */
|
|
struct image *
|
|
image_rect_get_image(const struct image_rect *rectangle)
|
|
{
|
|
return (rectangle->image);
|
|
}
|
|
|
|
/* Return the source grid cell for a drawing rectangle. */
|
|
const struct grid_cell *
|
|
image_rect_get_cell(const struct image_rect *rectangle)
|
|
{
|
|
return (&rectangle->cell);
|
|
}
|
|
|
|
/* Return the source and destination coordinates of a drawing rectangle. */
|
|
void
|
|
image_rect_get_coords(const struct image_rect *rectangle,
|
|
u_int *source_x, u_int *source_y, u_int *width, u_int *height,
|
|
u_int *destination_x, u_int *destination_y)
|
|
{
|
|
*source_x = rectangle->source_x;
|
|
*source_y = rectangle->source_y;
|
|
*width = rectangle->sx;
|
|
*height = rectangle->sy;
|
|
*destination_x = rectangle->destination_x;
|
|
*destination_y = rectangle->destination_y;
|
|
}
|
|
|
|
/* Return the output z-index for a drawing rectangle. */
|
|
int32_t
|
|
image_rect_get_z(const struct image_rect *rectangle)
|
|
{
|
|
return (rectangle->z);
|
|
}
|
|
|
|
/* Create and register an immutable image. */
|
|
static struct image *
|
|
image_create1(u_int width, u_int height, u_int canvas_width,
|
|
u_int canvas_height, u_int sx, u_int sy, size_t stride, u_char *pixels)
|
|
{
|
|
struct image *im;
|
|
|
|
im = xcalloc(1, sizeof *im);
|
|
do {
|
|
if (++image_next_id == 0)
|
|
image_next_id++;
|
|
im->id = image_next_id;
|
|
} while (image_find(im->id) != NULL);
|
|
|
|
im->references = 1;
|
|
im->source_id = im->id;
|
|
im->width = width;
|
|
im->height = height;
|
|
im->canvas_width = canvas_width;
|
|
im->canvas_height = canvas_height;
|
|
im->sx = sx;
|
|
im->sy = sy;
|
|
im->stride = stride;
|
|
im->size = (size_t)width * height * 4;
|
|
im->pixels = pixels;
|
|
|
|
RB_INSERT(images, &images, im);
|
|
log_debug("%s: image %u is %ux%u pixels on %ux%u canvas, "
|
|
"%ux%u cells", __func__, im->id, width, height, canvas_width,
|
|
canvas_height, sx, sy);
|
|
return (im);
|
|
}
|
|
|
|
/* Create an image from decoded pixel data. */
|
|
struct image *
|
|
image_create(u_int width, u_int height, u_int canvas_width,
|
|
u_int canvas_height, u_int sx, u_int sy, u_char *pixels)
|
|
{
|
|
struct image *im;
|
|
|
|
if (width == 0 || height == 0 || canvas_width < width ||
|
|
canvas_height < height || sx == 0 || sy == 0 || pixels == NULL)
|
|
return (NULL);
|
|
if ((uint64_t)width * height * 4 > SIZE_MAX)
|
|
return (NULL);
|
|
if ((uint64_t)sx * sy > SIZE_MAX / sizeof *im->cells ||
|
|
sx > USHRT_MAX || sy > USHRT_MAX)
|
|
return (NULL);
|
|
im = image_create1(width, height, canvas_width, canvas_height, sx, sy,
|
|
(size_t)width * 4, pixels);
|
|
return (im);
|
|
}
|
|
|
|
/* Create a cell-aligned view of an existing image with an optional offset. */
|
|
struct image *
|
|
image_create_view(struct image *source, u_int x, u_int y, u_int width,
|
|
u_int height, u_int canvas_width, u_int canvas_height, u_int sx, u_int sy,
|
|
u_int x_offset, u_int y_offset)
|
|
{
|
|
struct image *im;
|
|
u_char *pixels;
|
|
u_int padded_width, padded_height, yy;
|
|
|
|
if (source == NULL || x >= source->width || y >= source->height ||
|
|
width == 0 || width > source->width - x || height == 0 ||
|
|
height > source->height - y || canvas_width < width ||
|
|
canvas_height < height || sx == 0 || sy == 0)
|
|
return (NULL);
|
|
if ((uint64_t)sx * sy > SIZE_MAX / sizeof *im->cells ||
|
|
sx > USHRT_MAX || sy > USHRT_MAX)
|
|
return (NULL);
|
|
|
|
if (x_offset == 0 && y_offset == 0) {
|
|
im = image_create1(width, height, canvas_width, canvas_height,
|
|
sx, sy, source->stride, source->pixels +
|
|
(size_t)y * source->stride + (size_t)x * 4);
|
|
} else {
|
|
if (x_offset > UINT_MAX - width ||
|
|
y_offset > UINT_MAX - height)
|
|
return (NULL);
|
|
padded_width = width + x_offset;
|
|
padded_height = height + y_offset;
|
|
if ((uint64_t)padded_width * padded_height * 4 > SIZE_MAX)
|
|
return (NULL);
|
|
pixels = xcalloc((size_t)padded_width * padded_height, 4);
|
|
for (yy = 0; yy < height; yy++) {
|
|
memcpy(pixels + (size_t)(yy + y_offset) * padded_width * 4 +
|
|
(size_t)x_offset * 4,
|
|
source->pixels + (size_t)(y + yy) * source->stride +
|
|
(size_t)x * 4, (size_t)width * 4);
|
|
}
|
|
im = image_create1(padded_width, padded_height, canvas_width,
|
|
canvas_height, sx, sy, (size_t)padded_width * 4, pixels);
|
|
}
|
|
if (im == NULL)
|
|
return (NULL);
|
|
im->parent_id = source->id;
|
|
im->source_id = source->source_id;
|
|
image_ref(source->id);
|
|
return (im);
|
|
}
|
|
|
|
/* Add a reference to an image. */
|
|
void
|
|
image_ref(u_int id)
|
|
{
|
|
struct image *im = image_find(id);
|
|
|
|
if (im == NULL)
|
|
fatalx("reference to missing image %u", id);
|
|
if (im->references == UINT_MAX)
|
|
fatalx("too many references to image %u", id);
|
|
im->references++;
|
|
}
|
|
|
|
/* Drop a reference to an image. */
|
|
void
|
|
image_free(u_int id)
|
|
{
|
|
struct image *im = image_find(id);
|
|
|
|
if (im == NULL)
|
|
fatalx("free of missing image %u", id);
|
|
if (--im->references != 0)
|
|
return;
|
|
|
|
log_debug("%s: freeing image %u", __func__, id);
|
|
RB_REMOVE(images, &images, im);
|
|
if (im->parent_id == 0)
|
|
free(im->pixels);
|
|
else
|
|
image_free(im->parent_id);
|
|
if (im->sixel != NULL)
|
|
sixel_free(im->sixel);
|
|
free(im->cells);
|
|
free(im);
|
|
}
|
|
|
|
/* Return a precomputed fallback cell sample. */
|
|
static const struct image_cell *
|
|
image_get_cell(struct image *im, u_int x, u_int y)
|
|
{
|
|
if (im == NULL || x >= im->sx || y >= im->sy)
|
|
return (NULL);
|
|
if (im->cells == NULL)
|
|
image_make_cells(im);
|
|
return (&im->cells[(size_t)y * im->sx + x]);
|
|
}
|
|
|
|
/* Fill a grid cell with a fallback image glyph. */
|
|
void
|
|
image_get_fallback_cell(__unused struct tty *tty, struct image *im, u_int x,
|
|
u_int y, const struct grid_cell *gc, struct grid_cell *out,
|
|
__unused const struct tty_style_ctx *style_ctx)
|
|
{
|
|
static const char ramp[] = " .:-=+*#%@";
|
|
const struct image_cell *cell;
|
|
u_int level = 0;
|
|
|
|
memcpy(out, gc, sizeof *out);
|
|
cell = image_get_cell(im, x, y);
|
|
if (cell != NULL)
|
|
level = cell->whole.brightness * (sizeof ramp - 2) / 255;
|
|
utf8_set(&out->data, ramp[level]);
|
|
}
|
|
|
|
/* Return one for a fallback cell, minus one to continue along an image line. */
|
|
int
|
|
image_get_fallback_at(struct tty *tty, struct screen *s, u_int x, u_int y,
|
|
const struct grid_cell *gc, struct grid_cell *out,
|
|
const struct tty_style_ctx *style_ctx)
|
|
{
|
|
struct image_line *line;
|
|
struct image_span *span, *found = NULL;
|
|
struct image_placement *placement;
|
|
|
|
if (image_backend_flags(tty) & IMAGE_BACKEND_GRAPHICAL ||
|
|
y >= s->grid->sy)
|
|
return (0);
|
|
line = s->grid->linedata[s->grid->hsize + y].images;
|
|
if (line == NULL)
|
|
return (0);
|
|
TAILQ_FOREACH(span, &line->spans, line_entry) {
|
|
if (x >= span->x && x < span->x + span->sx)
|
|
found = span;
|
|
}
|
|
if (found == NULL)
|
|
return (-1);
|
|
placement = found->placement;
|
|
if (placement->input == IMAGE_INPUT_KITTY && placement->z < 0) {
|
|
if (gc->data.size != 1 || gc->data.data[0] != ' ')
|
|
return (-1);
|
|
if (placement->z < IMAGE_Z_BELOW_BACKGROUND &&
|
|
!COLOUR_DEFAULT(gc->bg))
|
|
return (-1);
|
|
}
|
|
image_get_fallback_cell(tty, placement->image,
|
|
found->source_x + x - found->x, found->source_y, gc, out,
|
|
style_ctx);
|
|
return (1);
|
|
}
|
|
|
|
/* Convert an image cell rectangle to pixel coordinates. */
|
|
void
|
|
image_get_pixel_rect(const struct image *im, u_int x, u_int y,
|
|
u_int width, u_int height, u_int *px, u_int *py, u_int *pwidth,
|
|
u_int *pheight)
|
|
{
|
|
u_int x1, y1;
|
|
|
|
*px = *py = *pwidth = *pheight = 0;
|
|
if (im == NULL || x >= im->sx || y >= im->sy || width == 0 ||
|
|
height == 0)
|
|
return;
|
|
if (width > im->sx - x)
|
|
width = im->sx - x;
|
|
if (height > im->sy - y)
|
|
height = im->sy - y;
|
|
|
|
*px = (uint64_t)x * im->canvas_width / im->sx;
|
|
*py = (uint64_t)y * im->canvas_height / im->sy;
|
|
x1 = ((uint64_t)(x + width) * im->canvas_width + im->sx - 1) /
|
|
im->sx;
|
|
y1 = ((uint64_t)(y + height) * im->canvas_height + im->sy - 1) /
|
|
im->sy;
|
|
if (*px >= im->width || *py >= im->height) {
|
|
*px = *py = 0;
|
|
return;
|
|
}
|
|
if (x1 <= *px)
|
|
x1 = *px + 1;
|
|
if (y1 <= *py)
|
|
y1 = *py + 1;
|
|
if (x1 > im->width)
|
|
x1 = im->width;
|
|
if (y1 > im->height)
|
|
y1 = im->height;
|
|
*pwidth = x1 - *px;
|
|
*pheight = y1 - *py;
|
|
}
|
|
|
|
/* Calculate the cell dimensions required for pixel dimensions. */
|
|
void
|
|
image_size_in_cells(u_int width, u_int height, u_int xpixel, u_int ypixel,
|
|
u_int *sx, u_int *sy)
|
|
{
|
|
if (xpixel == 0)
|
|
xpixel = 8;
|
|
if (ypixel == 0)
|
|
ypixel = 16;
|
|
*sx = width / xpixel + (width % xpixel != 0);
|
|
*sy = height / ypixel + (height % ypixel != 0);
|
|
}
|
|
|
|
/* Decode a base64 payload with a size limit. */
|
|
u_char *
|
|
image_base64_decode(const char *data, size_t len, size_t limit, size_t *size)
|
|
{
|
|
char *copy;
|
|
u_char *out;
|
|
size_t needed, padded, padding;
|
|
int result;
|
|
|
|
if (len > SIZE_MAX - 3)
|
|
return (NULL);
|
|
padding = (4 - len % 4) % 4;
|
|
if (padding == 3)
|
|
return (NULL);
|
|
padded = len + padding;
|
|
needed = padded / 4 * 3;
|
|
if (needed > limit || needed > INT_MAX)
|
|
return (NULL);
|
|
|
|
copy = xmalloc(padded + 1);
|
|
memcpy(copy, data, len);
|
|
memset(copy + len, '=', padding);
|
|
copy[padded] = '\0';
|
|
out = xmalloc(needed == 0 ? 1 : needed);
|
|
result = b64_pton(copy, out, needed);
|
|
free(copy);
|
|
if (result < 0) {
|
|
free(out);
|
|
return (NULL);
|
|
}
|
|
*size = result;
|
|
return (out);
|
|
}
|
|
|
|
/* Decode a PNG payload into RGBA pixels. */
|
|
u_char *
|
|
image_png_decode(const u_char *data, size_t size, size_t limit, u_int *width,
|
|
u_int *height)
|
|
{
|
|
png_image pi;
|
|
u_char *pixels;
|
|
|
|
if (size == 0 || size > limit)
|
|
return (NULL);
|
|
memset(&pi, 0, sizeof pi);
|
|
pi.version = PNG_IMAGE_VERSION;
|
|
if (!png_image_begin_read_from_memory(&pi, data, size))
|
|
return (NULL);
|
|
pi.format = PNG_FORMAT_RGBA;
|
|
if (pi.width == 0 || pi.height == 0 ||
|
|
(uint64_t)pi.width * pi.height * 4 > limit) {
|
|
png_image_free(&pi);
|
|
return (NULL);
|
|
}
|
|
pixels = xmalloc(PNG_IMAGE_SIZE(pi));
|
|
if (!png_image_finish_read(&pi, NULL, pixels, 0, NULL)) {
|
|
free(pixels);
|
|
png_image_free(&pi);
|
|
return (NULL);
|
|
}
|
|
*width = pi.width;
|
|
*height = pi.height;
|
|
png_image_free(&pi);
|
|
return (pixels);
|
|
}
|
|
|
|
/* Remove one placement and all of its spans. */
|
|
static void
|
|
image_remove_placement(struct image_placement *placement)
|
|
{
|
|
struct image_span *span, *next;
|
|
|
|
TAILQ_FOREACH_SAFE(span, &placement->spans, placement_entry, next)
|
|
image_span_free(span);
|
|
image_placement_free(placement);
|
|
}
|
|
|
|
/* Clear image placements with an internal image ID from a screen. */
|
|
void
|
|
image_clear(struct screen_write_ctx *ctx, u_int id)
|
|
{
|
|
struct image_store *store = ctx->s->grid->images;
|
|
struct image_placement *placement, *next;
|
|
|
|
if (store == NULL)
|
|
return;
|
|
TAILQ_FOREACH_SAFE(placement, &store->placements, entry, next) {
|
|
if (id != 0 && placement->image->id != id &&
|
|
placement->image->source_id != id)
|
|
continue;
|
|
image_remove_placement(placement);
|
|
}
|
|
if (ctx->wp != NULL)
|
|
ctx->wp->flags |= PANE_REDRAW;
|
|
}
|
|
|
|
/* Clear Kitty placements selected by application identity or z-index. */
|
|
void
|
|
image_clear_kitty(struct screen_write_ctx *ctx, char how, u_int image_id,
|
|
u_int placement_id, int32_t z)
|
|
{
|
|
struct image_store *store = ctx->s->grid->images;
|
|
struct image_placement *placement, *next;
|
|
int matched;
|
|
|
|
if (store == NULL)
|
|
return;
|
|
TAILQ_FOREACH_SAFE(placement, &store->placements, entry, next) {
|
|
if (placement->input != IMAGE_INPUT_KITTY)
|
|
continue;
|
|
matched = 0;
|
|
switch (how) {
|
|
case 'a': case 'A':
|
|
matched = 1;
|
|
break;
|
|
case 'i':
|
|
matched = (placement->app_image_id == image_id &&
|
|
(placement_id == 0 ||
|
|
placement->app_placement_id == placement_id));
|
|
break;
|
|
case 'I':
|
|
matched = (placement->app_image_id == image_id);
|
|
break;
|
|
case 'z': case 'Z':
|
|
matched = (placement->z == z);
|
|
break;
|
|
}
|
|
if (matched)
|
|
image_remove_placement(placement);
|
|
}
|
|
if (ctx->wp != NULL)
|
|
ctx->wp->flags |= PANE_REDRAW;
|
|
}
|
|
|
|
/* Redraw image layers in a screen area. */
|
|
void
|
|
image_redraw_area(struct screen_write_ctx *ctx, u_int px, u_int py, u_int nx,
|
|
u_int ny)
|
|
{
|
|
if (ctx->wp != NULL && image_grid_check_area(ctx->s->grid, px,
|
|
ctx->s->grid->hsize + py, nx, ny))
|
|
ctx->wp->flags |= PANE_REDRAW;
|
|
}
|
|
|
|
/* Redraw all image layers on a screen. */
|
|
void
|
|
image_redraw_all(struct screen_write_ctx *ctx)
|
|
{
|
|
image_redraw_area(ctx, 0, 0, screen_size_x(ctx->s),
|
|
screen_size_y(ctx->s));
|
|
}
|
|
|
|
/* Redraw images after a scrolling operation. */
|
|
void
|
|
image_redraw_scroll(struct screen_write_ctx *ctx, __unused u_int lines)
|
|
{
|
|
image_redraw_all(ctx);
|
|
}
|
|
|
|
/* Draw a clipped part of one image span. */
|
|
static void
|
|
image_draw_span(const struct image_backend *backend, struct tty *tty,
|
|
struct screen *s, struct image_span *span, u_int start, u_int end,
|
|
u_int px, u_int py, u_int atx, u_int aty,
|
|
const struct tty_style_ctx *style_ctx)
|
|
{
|
|
struct image_placement *placement = span->placement;
|
|
struct image_rect rectangle;
|
|
|
|
rectangle.image = placement->image;
|
|
grid_view_get_cell(s->grid, start, py, &rectangle.cell);
|
|
if (placement->input == IMAGE_INPUT_SIXEL)
|
|
rectangle.z = 0;
|
|
else if (placement->z >= 0 && placement->z < INT32_MAX)
|
|
rectangle.z = placement->z + 1;
|
|
else
|
|
rectangle.z = placement->z;
|
|
rectangle.source_x = span->source_x + start - span->x;
|
|
rectangle.source_y = span->source_y;
|
|
rectangle.sx = end - start;
|
|
rectangle.sy = 1;
|
|
rectangle.destination_x = atx + start - px;
|
|
rectangle.destination_y = aty;
|
|
backend->draw_rect(tty, &rectangle, style_ctx);
|
|
}
|
|
|
|
/* Return whether a cell contains a glyph or text decoration. */
|
|
static int
|
|
image_cell_has_text(struct grid *gd, u_int x, u_int y)
|
|
{
|
|
struct grid_cell gc;
|
|
|
|
grid_view_get_cell(gd, x, y, &gc);
|
|
if (gc.data.size != 1 || gc.data.data[0] != ' ')
|
|
return (1);
|
|
return (gc.attr != 0);
|
|
}
|
|
|
|
/* Draw a span's graphical image layers before or after its text. */
|
|
void
|
|
image_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py,
|
|
u_int nx, u_int atx, u_int aty, int before,
|
|
const struct tty_style_ctx *style_ctx)
|
|
{
|
|
const struct image_backend *backend;
|
|
struct image_line *line;
|
|
struct image_span *span;
|
|
struct image_placement *placement;
|
|
u_int start, end, span_end, draw_end;
|
|
int blank_only;
|
|
|
|
image_tty_update(tty);
|
|
backend = tty->image_backend;
|
|
if (~backend->flags & IMAGE_BACKEND_GRAPHICAL)
|
|
return;
|
|
if (py >= s->grid->sy)
|
|
return;
|
|
line = s->grid->linedata[s->grid->hsize + py].images;
|
|
if (line == NULL)
|
|
return;
|
|
end = px + nx;
|
|
TAILQ_FOREACH(span, &line->spans, line_entry) {
|
|
placement = span->placement;
|
|
blank_only = 0;
|
|
if (placement->input == IMAGE_INPUT_KITTY &&
|
|
placement->z < 0) {
|
|
if (backend == &image_backend_sixel &&
|
|
placement->z >= IMAGE_Z_BELOW_BACKGROUND) {
|
|
if (before)
|
|
continue;
|
|
blank_only = 1;
|
|
} else if (!before)
|
|
continue;
|
|
} else if (before)
|
|
continue;
|
|
span_end = span->x + span->sx;
|
|
start = (span->x > px ? span->x : px);
|
|
if (start >= end || span_end <= px)
|
|
continue;
|
|
if (span_end > end)
|
|
span_end = end;
|
|
if (!blank_only) {
|
|
image_draw_span(backend, tty, s, span, start, span_end,
|
|
px, py, atx, aty, style_ctx);
|
|
continue;
|
|
}
|
|
while (start < span_end) {
|
|
while (start < span_end &&
|
|
image_cell_has_text(s->grid, start, py))
|
|
start++;
|
|
draw_end = start;
|
|
while (draw_end < span_end &&
|
|
!image_cell_has_text(s->grid, draw_end, py))
|
|
draw_end++;
|
|
if (start < draw_end)
|
|
image_draw_span(backend, tty, s, span, start,
|
|
draw_end, px, py, atx, aty, style_ctx);
|
|
start = draw_end;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Return whether an image cell contains at least one nontransparent pixel. */
|
|
static int
|
|
image_cell_has_alpha(struct image *im, u_int x, u_int y)
|
|
{
|
|
u_int px, py, sx, sy, xx, yy;
|
|
const u_char *pixels;
|
|
|
|
image_get_pixel_rect(im, x, y, 1, 1, &px, &py, &sx, &sy);
|
|
if (sx == 0 || sy == 0)
|
|
return (0);
|
|
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)
|
|
return (1);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/* 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,
|
|
u_int input, u_int app_image_id, u_int app_placement_id, int32_t z)
|
|
{
|
|
struct screen *s = ctx->s;
|
|
struct grid *gd = s->grid;
|
|
struct image_placement *placement;
|
|
struct image_line *line;
|
|
u_int cx = s->cx, cy = s->cy;
|
|
u_int x, y, run, sx, sy, lines, origin_y = 0;
|
|
|
|
sx = im->sx;
|
|
if (sx > screen_size_x(s) - cx)
|
|
sx = screen_size_x(s) - cx;
|
|
sy = im->sy;
|
|
if (sx == 0)
|
|
return;
|
|
|
|
if (im->flags & IMAGE_FLAG_NO_CURSOR) {
|
|
if (sy > screen_size_y(s) - cy)
|
|
sy = screen_size_y(s) - cy;
|
|
} else if (screen_size_y(s) - cy <= sy) {
|
|
lines = sy - (screen_size_y(s) - cy) + 1;
|
|
screen_write_scrollup(ctx, lines, bg);
|
|
if (lines > cy) {
|
|
origin_y = lines - cy;
|
|
screen_write_cursormove(ctx, -1, 0, 0);
|
|
} else
|
|
screen_write_cursormove(ctx, -1, cy - lines, 0);
|
|
cy = s->cy;
|
|
sy -= origin_y;
|
|
}
|
|
|
|
placement = image_placement_create(gd, im, input, app_image_id,
|
|
app_placement_id, z);
|
|
for (y = 0; y < sy; y++) {
|
|
line = image_line_get(&gd->linedata[gd->hsize + cy + y]);
|
|
for (x = 0; x < sx; x += run) {
|
|
if (!image_cell_has_alpha(im, x, origin_y + y)) {
|
|
run = 1;
|
|
continue;
|
|
}
|
|
for (run = 1; x + run < sx; run++) {
|
|
if (!image_cell_has_alpha(im, x + run,
|
|
origin_y + y))
|
|
break;
|
|
}
|
|
image_span_add(line, placement, cx + x, run, x,
|
|
origin_y + y);
|
|
}
|
|
}
|
|
image_store_prune(gd->images);
|
|
image_redraw_area(ctx, cx, cy, sx, sy);
|
|
if (!(im->flags & IMAGE_FLAG_NO_CURSOR))
|
|
screen_write_cursormove(ctx, 0, cy + sy, 0);
|
|
}
|
|
|
|
/* Place an image received through SIXEL. */
|
|
void
|
|
image_write_sixel(struct screen_write_ctx *ctx, struct image *im, u_int bg)
|
|
{
|
|
image_write(ctx, im, bg, IMAGE_INPUT_SIXEL, 0, 0, 0);
|
|
}
|
|
|
|
/* Place an image received through the Kitty graphics protocol. */
|
|
void
|
|
image_write_kitty(struct screen_write_ctx *ctx, struct image *im, u_int bg,
|
|
u_int image_id, u_int placement_id, int32_t z)
|
|
{
|
|
image_write(ctx, im, bg, IMAGE_INPUT_KITTY, image_id, placement_id, z);
|
|
}
|