mirror of
https://github.com/tmux/tmux.git
synced 2026-10-14 15:04:50 +00:00
Image grid infrastructure.
This commit is contained in:
422
image.c
422
image.c
@@ -2,6 +2,7 @@
|
||||
|
||||
/*
|
||||
* 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
|
||||
@@ -18,210 +19,289 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
static struct images all_images = TAILQ_HEAD_INITIALIZER(all_images);
|
||||
static u_int all_images_count;
|
||||
#define MAX_IMAGE_COUNT 20
|
||||
/* Maximum image size in bytes. */
|
||||
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
|
||||
|
||||
static void printflike(3, 4)
|
||||
image_log(struct image *im, const char* from, const char* fmt, ...)
|
||||
/* An image. */
|
||||
struct image {
|
||||
u_int id;
|
||||
u_int references;
|
||||
u_int flags;
|
||||
#define IMAGE_FLAG_OWN_PIXELS 0x1
|
||||
|
||||
struct image *parent;
|
||||
u_int source_id;
|
||||
u_int order_id;
|
||||
|
||||
/* Size in cells. */
|
||||
u_int sx;
|
||||
u_int sy;
|
||||
|
||||
/* Size in pixels. */
|
||||
u_int width;
|
||||
u_int height;
|
||||
|
||||
/* Canvas size in pixels. */
|
||||
u_int canvas_width;
|
||||
u_int canvas_height;
|
||||
|
||||
/*
|
||||
* RGBA pixels use four bytes each. Stride is the number of bytes
|
||||
* between the start of each row. Size is the number of bytes
|
||||
* containing all of this image's pixels - it may include pixels that
|
||||
* are part of the parent image but not part of this image.
|
||||
*/
|
||||
size_t stride;
|
||||
size_t size;
|
||||
u_char *pixels;
|
||||
|
||||
RB_ENTRY(image) entry;
|
||||
};
|
||||
RB_HEAD(images, image);
|
||||
static struct images images = RB_INITIALIZER(&images);
|
||||
static u_int image_next_id;
|
||||
|
||||
/* Compare images by IDs. */
|
||||
static int
|
||||
image_cmp(struct image *a, struct image *b)
|
||||
{
|
||||
va_list ap;
|
||||
char s[128];
|
||||
if (a->id < b->id)
|
||||
return (-1);
|
||||
if (a->id > b->id)
|
||||
return (1);
|
||||
return (0);
|
||||
}
|
||||
RB_GENERATE_STATIC(images, image, entry, image_cmp);
|
||||
|
||||
if (log_get_level() == 0)
|
||||
return;
|
||||
/* Find an image by its ID. */
|
||||
struct image *
|
||||
image_find(u_int id)
|
||||
{
|
||||
struct image find;
|
||||
|
||||
if (fmt == NULL) {
|
||||
log_debug("%s: %p (%ux%u %u,%u)", from, im, im->sx, im->sy,
|
||||
im->px, im->py);
|
||||
return;
|
||||
}
|
||||
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(s, sizeof s, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
log_debug("%s: %p (%ux%u %u,%u): %s", from, im, im->sx, im->sy,
|
||||
im->px, im->py, s);
|
||||
find.id = id;
|
||||
return (RB_FIND(images, &images, &find));
|
||||
}
|
||||
|
||||
static void
|
||||
image_free(struct image *im)
|
||||
/* Return the ID of an image. */
|
||||
u_int
|
||||
image_get_id(struct image *im)
|
||||
{
|
||||
image_log(im, __func__, NULL);
|
||||
|
||||
TAILQ_REMOVE(&all_images, im, all_entry);
|
||||
all_images_count--;
|
||||
|
||||
TAILQ_REMOVE(im->list, im, entry);
|
||||
sixel_free(im->data);
|
||||
free(im->fallback);
|
||||
free(im);
|
||||
return (im->id);
|
||||
}
|
||||
|
||||
int
|
||||
image_free_all(struct screen *s)
|
||||
{
|
||||
struct image *im, *im1;
|
||||
int redraw = !TAILQ_EMPTY(&s->images);
|
||||
|
||||
if (redraw)
|
||||
log_debug ("%s", __func__);
|
||||
TAILQ_FOREACH_SAFE(im, &s->images, entry, im1)
|
||||
image_free(im);
|
||||
return (redraw);
|
||||
}
|
||||
|
||||
/* Create text placeholder for an image. */
|
||||
static void
|
||||
image_fallback(char **ret, u_int sx, u_int sy)
|
||||
{
|
||||
char *buf, *label;
|
||||
u_int py, size, lsize;
|
||||
|
||||
if (sy == 0) {
|
||||
*ret = xstrdup("");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Allocate first line. */
|
||||
lsize = xasprintf(&label, "SIXEL IMAGE (%ux%u)\r\n", sx, sy) + 1;
|
||||
if (sx < lsize - 3)
|
||||
size = lsize - 1;
|
||||
else
|
||||
size = sx + 2;
|
||||
|
||||
/* Remaining lines. Every placeholder line has \r\n at the end. */
|
||||
size += (sx + 2) * (sy - 1) + 1;
|
||||
*ret = buf = xmalloc(size);
|
||||
|
||||
/* Render first line. */
|
||||
if (sx < lsize - 3) {
|
||||
memcpy(buf, label, lsize);
|
||||
buf += lsize - 1;
|
||||
} else {
|
||||
memcpy(buf, label, lsize - 3);
|
||||
buf += lsize - 3;
|
||||
memset(buf, '+', sx - lsize + 3);
|
||||
buf += sx - lsize + 3;
|
||||
snprintf(buf, 3, "\r\n");
|
||||
buf += 2;
|
||||
}
|
||||
|
||||
/* Remaining lines. */
|
||||
for (py = 1; py < sy; py++) {
|
||||
memset(buf, '+', sx);
|
||||
buf += sx;
|
||||
snprintf(buf, 3, "\r\n");
|
||||
buf += 2;
|
||||
}
|
||||
|
||||
free(label);
|
||||
}
|
||||
|
||||
struct image*
|
||||
image_store(struct screen *s, struct sixel_image *si)
|
||||
/* Allocate an image and assign a unique ID. */
|
||||
static struct image *
|
||||
image_alloc(u_int w, u_int h, u_int cw, u_int ch, u_int sx, u_int sy,
|
||||
size_t stride, u_char *pixels)
|
||||
{
|
||||
struct image *im;
|
||||
u_int id;
|
||||
|
||||
do {
|
||||
if (++image_next_id == 0)
|
||||
image_next_id++;
|
||||
id = image_next_id;
|
||||
} while (image_find(id) != NULL);
|
||||
|
||||
im = xcalloc(1, sizeof *im);
|
||||
im->s = s;
|
||||
im->data = si;
|
||||
|
||||
im->px = s->cx;
|
||||
im->py = s->cy;
|
||||
sixel_size_in_cells(si, &im->sx, &im->sy);
|
||||
|
||||
image_fallback(&im->fallback, im->sx, im->sy);
|
||||
|
||||
image_log(im, __func__, NULL);
|
||||
im->list = &s->images;
|
||||
TAILQ_INSERT_TAIL(im->list, im, entry);
|
||||
|
||||
TAILQ_INSERT_TAIL(&all_images, im, all_entry);
|
||||
if (++all_images_count == MAX_IMAGE_COUNT)
|
||||
image_free(TAILQ_FIRST(&all_images));
|
||||
|
||||
im->id = id;
|
||||
im->references = 1;
|
||||
im->source_id = im->order_id = id;
|
||||
im->width = w;
|
||||
im->height = h;
|
||||
im->canvas_width = cw;
|
||||
im->canvas_height = ch;
|
||||
im->sx = sx;
|
||||
im->sy = sy;
|
||||
im->stride = stride;
|
||||
im->size = (h - 1) * stride + w * 4;
|
||||
im->pixels = pixels;
|
||||
RB_INSERT(images, &images, im);
|
||||
return (im);
|
||||
}
|
||||
|
||||
int
|
||||
image_check_line(struct screen *s, u_int py, u_int ny)
|
||||
/* Create an image with the provided pixels. */
|
||||
struct image *
|
||||
image_create(u_int w, u_int h, u_int cw, u_int ch, u_int sx, u_int sy,
|
||||
u_char *pixels)
|
||||
{
|
||||
struct image *im, *im1;
|
||||
int redraw = 0, in;
|
||||
struct image *im;
|
||||
|
||||
TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) {
|
||||
in = (py + ny > im->py && py < im->py + im->sy);
|
||||
image_log(im, __func__, "py=%u, ny=%u, in=%d", py, ny, in);
|
||||
if (in) {
|
||||
image_free(im);
|
||||
redraw = 1;
|
||||
}
|
||||
}
|
||||
return (redraw);
|
||||
if (w == 0 || h == 0 || cw < w || ch < h)
|
||||
return (NULL);
|
||||
if (sx == 0 || sy == 0 || pixels == NULL)
|
||||
return (NULL);
|
||||
if ((uint64_t)w * h > IMAGE_SIZE_LIMIT / 4)
|
||||
return (NULL);
|
||||
if (sx > USHRT_MAX || sy > USHRT_MAX)
|
||||
return (NULL);
|
||||
if (sx * sy > IMAGE_SIZE_LIMIT)
|
||||
return (NULL);
|
||||
|
||||
im = image_alloc(w, h, cw, ch, sx, sy, w * 4, pixels);
|
||||
im->flags |= IMAGE_FLAG_OWN_PIXELS;
|
||||
return (im);
|
||||
}
|
||||
|
||||
int
|
||||
image_check_area(struct screen *s, u_int px, u_int py, u_int nx, u_int ny)
|
||||
/* Resample the crop into transparent, padded RGBA pixels. */
|
||||
static u_char *
|
||||
image_view_resample(struct image *src, struct image_view *view, u_int w,
|
||||
u_int h)
|
||||
{
|
||||
struct image *im, *im1;
|
||||
int redraw = 0, in;
|
||||
u_char *pixels, *row, *src_row;
|
||||
u_int src_x, src_y;
|
||||
uint64_t x, y, off;
|
||||
|
||||
TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) {
|
||||
in = (py < im->py + im->sy &&
|
||||
py + ny > im->py &&
|
||||
px < im->px + im->sx &&
|
||||
px + nx > im->px);
|
||||
image_log(im, __func__, "py=%u, ny=%u, in=%d", py, ny, in);
|
||||
if (in) {
|
||||
image_free(im);
|
||||
redraw = 1;
|
||||
pixels = xcalloc(w * h, 4);
|
||||
for (y = 0; y < view->scaled_height; y++) {
|
||||
src_y = view->y + y * view->height / view->scaled_height;
|
||||
src_row = src->pixels + src_y * src->stride + view->x * 4;
|
||||
|
||||
off = ((y + view->canvas_yoff) * w + view->canvas_xoff) * 4;
|
||||
row = pixels + off;
|
||||
|
||||
if (view->scaled_width == view->width)
|
||||
memcpy(row, src_row, view->width * 4);
|
||||
else {
|
||||
for (x = 0; x < view->scaled_width; x++) {
|
||||
src_x = x * view->width / view->scaled_width;
|
||||
memcpy(row + x * 4, src_row + src_x * 4, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (redraw);
|
||||
return (pixels);
|
||||
}
|
||||
|
||||
int
|
||||
image_scroll_up(struct screen *s, u_int lines)
|
||||
/* Crop and resample an image. The view takes a reference to the image. */
|
||||
struct image *
|
||||
image_create_view(struct image *src, struct image_view *view)
|
||||
{
|
||||
struct image *im, *im1;
|
||||
int redraw = 0;
|
||||
u_int sx, sy;
|
||||
struct sixel_image *new;
|
||||
struct image *im;
|
||||
u_char *pixels;
|
||||
u_int w, h;
|
||||
|
||||
TAILQ_FOREACH_SAFE(im, &s->images, entry, im1) {
|
||||
if (im->py >= lines) {
|
||||
image_log(im, __func__, "1, lines=%u", lines);
|
||||
im->py -= lines;
|
||||
redraw = 1;
|
||||
continue;
|
||||
}
|
||||
if (im->py + im->sy <= lines) {
|
||||
image_log(im, __func__, "2, lines=%u", lines);
|
||||
image_free(im);
|
||||
redraw = 1;
|
||||
continue;
|
||||
}
|
||||
sx = im->sx;
|
||||
sy = (im->py + im->sy) - lines;
|
||||
image_log(im, __func__, "3, lines=%u, sy=%u", lines, sy);
|
||||
if (src == NULL ||
|
||||
view == NULL ||
|
||||
view->x >= src->width ||
|
||||
view->y >= src->height ||
|
||||
view->width == 0 ||
|
||||
view->height == 0 ||
|
||||
view->scaled_width == 0 ||
|
||||
view->scaled_height == 0 ||
|
||||
view->sx == 0 ||
|
||||
view->sy == 0 ||
|
||||
view->width > src->width - view->x ||
|
||||
view->height > src->height - view->y ||
|
||||
view->sx > USHRT_MAX ||
|
||||
view->sy > USHRT_MAX ||
|
||||
view->sx * view->sy > IMAGE_SIZE_LIMIT ||
|
||||
view->canvas_xoff > view->canvas_width ||
|
||||
view->scaled_width > view->canvas_width - view->canvas_xoff ||
|
||||
view->canvas_yoff > view->canvas_height ||
|
||||
view->scaled_height > view->canvas_height - view->canvas_yoff)
|
||||
return (NULL);
|
||||
|
||||
new = sixel_scale(im->data, 0, 0, 0, im->sy - sy, sx, sy, 1);
|
||||
sixel_free(im->data);
|
||||
im->data = new;
|
||||
|
||||
im->py = 0;
|
||||
sixel_size_in_cells(im->data, &im->sx, &im->sy);
|
||||
|
||||
free(im->fallback);
|
||||
image_fallback(&im->fallback, im->sx, im->sy);
|
||||
redraw = 1;
|
||||
if (view->scaled_width == view->width &&
|
||||
view->scaled_height == view->height &&
|
||||
view->canvas_xoff == 0 &&
|
||||
view->canvas_yoff == 0) {
|
||||
pixels = src->pixels + view->y * src->stride + view->x * 4;
|
||||
im = image_alloc(view->width, view->height, view->canvas_width,
|
||||
view->canvas_height, view->sx, view->sy, src->stride,
|
||||
pixels);
|
||||
} else {
|
||||
w = view->canvas_xoff + view->scaled_width;
|
||||
h = view->canvas_yoff + view->scaled_height;
|
||||
if ((uint64_t)w * h > IMAGE_SIZE_LIMIT / 4)
|
||||
return (NULL);
|
||||
pixels = image_view_resample(src, view, w, h);
|
||||
im = image_alloc(w, h, view->canvas_width, view->canvas_height,
|
||||
view->sx, view->sy, w * 4, pixels);
|
||||
im->flags |= IMAGE_FLAG_OWN_PIXELS;
|
||||
}
|
||||
return (redraw);
|
||||
im->parent = src;
|
||||
im->source_id = src->source_id;
|
||||
im->order_id = src->order_id;
|
||||
image_ref(src);
|
||||
return (im);
|
||||
}
|
||||
|
||||
/* Add a reference to an image. */
|
||||
void
|
||||
image_ref(struct image *im)
|
||||
{
|
||||
if (im->references == UINT_MAX)
|
||||
fatalx("invalid image reference %u", im->id);
|
||||
im->references++;
|
||||
}
|
||||
|
||||
/* Free an image. */
|
||||
void
|
||||
image_free(struct image *im)
|
||||
{
|
||||
if (im->references == 0)
|
||||
fatalx("invalid image reference %u", im->id);
|
||||
if (--im->references == 0) {
|
||||
RB_REMOVE(images, &images, im);
|
||||
if (im->flags & IMAGE_FLAG_OWN_PIXELS)
|
||||
free(im->pixels);
|
||||
if (im->parent != NULL)
|
||||
image_free(im->parent);
|
||||
free(im);
|
||||
}
|
||||
}
|
||||
|
||||
/* Return image size in pixels. */
|
||||
void
|
||||
image_get_size(struct image *im, u_int *w, u_int *h)
|
||||
{
|
||||
*w = im->width;
|
||||
*h = im->height;
|
||||
}
|
||||
|
||||
/* Return canvas size in pixels. */
|
||||
void
|
||||
image_get_canvas_size(struct image *im, u_int *w, u_int *h)
|
||||
{
|
||||
*w = im->canvas_width;
|
||||
*h = im->canvas_height;
|
||||
}
|
||||
|
||||
/* Return image size in cells. */
|
||||
void
|
||||
image_get_size_in_cells(struct image *im, u_int *sx, u_int *sy)
|
||||
{
|
||||
*sx = im->sx;
|
||||
*sy = im->sy;
|
||||
}
|
||||
|
||||
/* Return image pixels. */
|
||||
const u_char *
|
||||
image_get_pixels(struct image *im, size_t *stride, size_t *size)
|
||||
{
|
||||
if (stride != NULL)
|
||||
*stride = im->stride;
|
||||
if (size != NULL)
|
||||
*size = im->size;
|
||||
return (im->pixels);
|
||||
}
|
||||
|
||||
/* Calculate the cell size. */
|
||||
void
|
||||
image_size_in_cells(u_int w, u_int h, u_int xpixel, u_int ypixel, u_int *sx,
|
||||
u_int *sy)
|
||||
{
|
||||
if (xpixel == 0)
|
||||
xpixel = 8;
|
||||
if (ypixel == 0)
|
||||
ypixel = 16;
|
||||
*sx = w / xpixel + (w % xpixel != 0);
|
||||
*sy = h / ypixel + (h % ypixel != 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user