mirror of
https://github.com/tmux/tmux.git
synced 2026-10-14 23:34:22 +00:00
Fallback image drawing.
This commit is contained in:
@@ -252,7 +252,11 @@ endif
|
|||||||
|
|
||||||
# Enable image support.
|
# Enable image support.
|
||||||
if ENABLE_IMAGES
|
if ENABLE_IMAGES
|
||||||
dist_tmux_SOURCES += image.c image-grid.c image-sixel.c
|
dist_tmux_SOURCES += \
|
||||||
|
image.c \
|
||||||
|
image-grid.c \
|
||||||
|
image-sixel.c \
|
||||||
|
image-fallback.c
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Add bits for fuzzing if enabled.
|
# Add bits for fuzzing if enabled.
|
||||||
|
|||||||
164
image-fallback.c
Normal file
164
image-fallback.c
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
/* $OpenBSD$ */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 <string.h>
|
||||||
|
|
||||||
|
#include "tmux.h"
|
||||||
|
|
||||||
|
/* UTF-8 characters used for image fallback. */
|
||||||
|
static const struct utf8_data fallback_shades[5] = {
|
||||||
|
{ " ", 1, 1, 1 },
|
||||||
|
{ "\342\226\221", 3, 3, 1 },
|
||||||
|
{ "\342\226\222", 3, 3, 1 },
|
||||||
|
{ "\342\226\223", 3, 3, 1 },
|
||||||
|
{ "\342\226\210", 3, 3, 1 }
|
||||||
|
};
|
||||||
|
static const struct utf8_data fallback_half_lower = {
|
||||||
|
"\342\226\204", 3, 3, 1
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Return the average colour and brightness of an area. */
|
||||||
|
static int
|
||||||
|
fallback_sample(struct image *im, uint64_t px, uint64_t py, uint64_t pw,
|
||||||
|
uint64_t ph, u_char *brightness)
|
||||||
|
{
|
||||||
|
const u_char *pixels, *p;
|
||||||
|
uint64_t red = 0, green = 0, blue = 0, sum = 0, count;
|
||||||
|
u_int w, h, cw, ch, x0, x1, y0, y1, x, y;
|
||||||
|
size_t stride;
|
||||||
|
|
||||||
|
image_get_size(im, &w, &h);
|
||||||
|
image_get_canvas_size(im, &cw, &ch);
|
||||||
|
pixels = image_get_pixels(im, &stride, NULL);
|
||||||
|
|
||||||
|
x0 = px * cw / pw;
|
||||||
|
x1 = ((px + 1) * cw + pw - 1) / pw;
|
||||||
|
y0 = py * ch / ph;
|
||||||
|
y1 = ((py + 1) * ch + ph - 1) / ph;
|
||||||
|
if (x1 <= x0)
|
||||||
|
x1 = x0 + 1;
|
||||||
|
if (y1 <= y0)
|
||||||
|
y1 = y0 + 1;
|
||||||
|
if (x0 >= w || y0 >= h) {
|
||||||
|
if (brightness != NULL)
|
||||||
|
*brightness = 0;
|
||||||
|
return (colour_join_rgb(0, 0, 0));
|
||||||
|
}
|
||||||
|
count = (uint64_t)(x1 - x0) * (y1 - y0);
|
||||||
|
|
||||||
|
if (x1 > w)
|
||||||
|
x1 = w;
|
||||||
|
if (y1 > h)
|
||||||
|
y1 = h;
|
||||||
|
for (y = y0; y < y1; y++) {
|
||||||
|
for (x = x0; x < x1; x++) {
|
||||||
|
p = pixels + y * stride + x * 4;
|
||||||
|
|
||||||
|
red += p[0] * p[3] / 255;
|
||||||
|
green += p[1] * p[3] / 255;
|
||||||
|
blue += p[2] * p[3] / 255;
|
||||||
|
|
||||||
|
sum += ((2126ULL * p[0] + 7152ULL * p[1] +
|
||||||
|
722ULL * p[2]) / 10000) * p[3] / 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (brightness != NULL)
|
||||||
|
*brightness = sum / count;
|
||||||
|
return (colour_join_rgb(red / count, green / count, blue / count));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set a cell to a character representing part of an image. */
|
||||||
|
static void
|
||||||
|
fallback_get_cell(struct tty *tty, struct image *im, u_int x, u_int y,
|
||||||
|
const struct grid_cell *gc, struct grid_cell *out)
|
||||||
|
{
|
||||||
|
static const char a[] = " .:-=+*#%@";
|
||||||
|
static const u_char bold_shades[8] = { 0, 1, 1, 2, 2, 3, 4, 4 };
|
||||||
|
u_int ch, level, nlevels, sx, sy, colours;
|
||||||
|
u_char brightness;
|
||||||
|
|
||||||
|
if (out != gc)
|
||||||
|
memcpy(out, gc, sizeof *out);
|
||||||
|
out->flags &= GRID_FLAG_SELECTED;
|
||||||
|
out->attr = 0;
|
||||||
|
out->link = 0;
|
||||||
|
image_get_size_in_cells(im, &sx, &sy);
|
||||||
|
if (x >= sx || y >= sy) {
|
||||||
|
utf8_set(&out->data, ' ');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tty_acs_needed(tty)) {
|
||||||
|
fallback_sample(im, x, y, sx, sy, &brightness);
|
||||||
|
utf8_set(&out->data, a[brightness * (sizeof a - 2) / 255]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tty->term->flags & (TERM_RGBCOLOURS|TERM_256COLOURS))
|
||||||
|
colours = 256;
|
||||||
|
else
|
||||||
|
colours = tty_term_number(tty->term, TTYC_COLORS);
|
||||||
|
if (colours >= 16) {
|
||||||
|
out->bg = fallback_sample(im, x, y * 2, sx, sy * 2, NULL);
|
||||||
|
out->fg = fallback_sample(im, x, y * 2 + 1, sx, sy * 2, NULL);
|
||||||
|
utf8_copy(&out->data, &fallback_half_lower);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tty_term_has(tty->term, TTYC_NOBR))
|
||||||
|
nlevels = 5;
|
||||||
|
else
|
||||||
|
nlevels = 8;
|
||||||
|
fallback_sample(im, x, y, sx, sy, &brightness);
|
||||||
|
level = (brightness * (nlevels - 1) + 127) / 255;
|
||||||
|
|
||||||
|
if (nlevels == 8)
|
||||||
|
ch = bold_shades[level];
|
||||||
|
else
|
||||||
|
ch = level;
|
||||||
|
utf8_copy(&out->data, &fallback_shades[ch]);
|
||||||
|
|
||||||
|
out->fg = 7;
|
||||||
|
out->bg = 0;
|
||||||
|
out->attr &= ~GRID_ATTR_BRIGHT;
|
||||||
|
if (nlevels == 8 && (level == 2 || level == 4 || level == 7))
|
||||||
|
out->attr |= GRID_ATTR_BRIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return a text cell for an image grid cell, if there is one. */
|
||||||
|
const struct grid_cell *
|
||||||
|
fallback_map_cell(struct tty *tty, struct grid *gd, u_int x, u_int y,
|
||||||
|
const struct grid_cell *gc, struct grid_cell *out, int *empty)
|
||||||
|
{
|
||||||
|
struct image *im;
|
||||||
|
u_int src_x, src_y;
|
||||||
|
int status;
|
||||||
|
|
||||||
|
status = image_grid_get_at(gd, x, y, &im, &src_x, &src_y);
|
||||||
|
if (status == 1) {
|
||||||
|
fallback_get_cell(tty, im, src_x, src_y, gc, out);
|
||||||
|
if (empty != NULL)
|
||||||
|
*empty = 0;
|
||||||
|
return (out);
|
||||||
|
}
|
||||||
|
if (status == -1 && empty != NULL)
|
||||||
|
*empty = 1;
|
||||||
|
return (gc);
|
||||||
|
}
|
||||||
27
image-grid.c
27
image-grid.c
@@ -460,7 +460,7 @@ image_damage_screen(struct screen_write_ctx *ctx, u_int x, u_int y, u_int sx,
|
|||||||
image_grid_remove(s->grid, x, s->grid->hsize + y, sx, sy);
|
image_grid_remove(s->grid, x, s->grid->hsize + y, sx, sy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return whether an image cell has any visible pixels. */
|
/* Check whether an image cell has any visible pixels. */
|
||||||
static int
|
static int
|
||||||
image_cell_has_visible_pixels(struct image *im, u_int x, u_int y)
|
image_cell_has_visible_pixels(struct image *im, u_int x, u_int y)
|
||||||
{
|
{
|
||||||
@@ -569,3 +569,28 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg)
|
|||||||
image_redraw_area(ctx, cx, cy, sx, sy);
|
image_redraw_area(ctx, cx, cy, sx, sy);
|
||||||
screen_write_cursormove(ctx, 0, cy + sy, 0);
|
screen_write_cursormove(ctx, 0, cy + sy, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Find the cell within an image at a grid position. */
|
||||||
|
int
|
||||||
|
image_grid_get_at(struct grid *gd, u_int x, u_int y, struct image **im,
|
||||||
|
u_int *src_x, u_int *src_y)
|
||||||
|
{
|
||||||
|
struct image_line *line;
|
||||||
|
struct image_span *span, *found = NULL;
|
||||||
|
|
||||||
|
if (y >= gd->hsize + gd->sy)
|
||||||
|
return (0);
|
||||||
|
line = gd->linedata[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);
|
||||||
|
*im = found->placement->image;
|
||||||
|
*src_x = found->source_x + x - found->x;
|
||||||
|
*src_y = found->source_y;
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|||||||
14
image.c
14
image.c
@@ -86,7 +86,7 @@ image_find(u_int id)
|
|||||||
return (RB_FIND(images, &images, &find));
|
return (RB_FIND(images, &images, &find));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return the ID of an image. */
|
/* Get the ID of an image. */
|
||||||
u_int
|
u_int
|
||||||
image_get_id(struct image *im)
|
image_get_id(struct image *im)
|
||||||
{
|
{
|
||||||
@@ -139,7 +139,7 @@ image_create(u_int w, u_int h, u_int cw, u_int ch, u_int sx, u_int sy,
|
|||||||
return (NULL);
|
return (NULL);
|
||||||
if (sx > USHRT_MAX || sy > USHRT_MAX)
|
if (sx > USHRT_MAX || sy > USHRT_MAX)
|
||||||
return (NULL);
|
return (NULL);
|
||||||
if (sx * sy > IMAGE_SIZE_LIMIT)
|
if ((uint64_t)sx * sy > IMAGE_SIZE_LIMIT)
|
||||||
return (NULL);
|
return (NULL);
|
||||||
|
|
||||||
im = image_alloc(w, h, cw, ch, sx, sy, w * 4, pixels);
|
im = image_alloc(w, h, cw, ch, sx, sy, w * 4, pixels);
|
||||||
@@ -198,7 +198,7 @@ image_create_view(struct image *src, struct image_view *view)
|
|||||||
view->height > src->height - view->y ||
|
view->height > src->height - view->y ||
|
||||||
view->sx > USHRT_MAX ||
|
view->sx > USHRT_MAX ||
|
||||||
view->sy > USHRT_MAX ||
|
view->sy > USHRT_MAX ||
|
||||||
view->sx * view->sy > IMAGE_SIZE_LIMIT ||
|
(uint64_t)view->sx * view->sy > IMAGE_SIZE_LIMIT ||
|
||||||
view->canvas_xoff > view->canvas_width ||
|
view->canvas_xoff > view->canvas_width ||
|
||||||
view->scaled_width > view->canvas_width - view->canvas_xoff ||
|
view->scaled_width > view->canvas_width - view->canvas_xoff ||
|
||||||
view->canvas_yoff > view->canvas_height ||
|
view->canvas_yoff > view->canvas_height ||
|
||||||
@@ -255,7 +255,7 @@ image_free(struct image *im)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return image size in pixels. */
|
/* Get image size in pixels. */
|
||||||
void
|
void
|
||||||
image_get_size(struct image *im, u_int *w, u_int *h)
|
image_get_size(struct image *im, u_int *w, u_int *h)
|
||||||
{
|
{
|
||||||
@@ -263,7 +263,7 @@ image_get_size(struct image *im, u_int *w, u_int *h)
|
|||||||
*h = im->height;
|
*h = im->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return canvas size in pixels. */
|
/* Get canvas size in pixels. */
|
||||||
void
|
void
|
||||||
image_get_canvas_size(struct image *im, u_int *w, u_int *h)
|
image_get_canvas_size(struct image *im, u_int *w, u_int *h)
|
||||||
{
|
{
|
||||||
@@ -271,7 +271,7 @@ image_get_canvas_size(struct image *im, u_int *w, u_int *h)
|
|||||||
*h = im->canvas_height;
|
*h = im->canvas_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return image size in cells. */
|
/* Get image size in cells. */
|
||||||
void
|
void
|
||||||
image_get_size_in_cells(struct image *im, u_int *sx, u_int *sy)
|
image_get_size_in_cells(struct image *im, u_int *sx, u_int *sy)
|
||||||
{
|
{
|
||||||
@@ -279,7 +279,7 @@ image_get_size_in_cells(struct image *im, u_int *sx, u_int *sy)
|
|||||||
*sy = im->sy;
|
*sy = im->sy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return image pixels. */
|
/* Get image pixels. */
|
||||||
const u_char *
|
const u_char *
|
||||||
image_get_pixels(struct image *im, size_t *stride, size_t *size)
|
image_get_pixels(struct image *im, size_t *stride, size_t *size)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#error image support is not ready yet
|
#error image support is not ready yet
|
||||||
|
|
||||||
struct grid;
|
struct grid;
|
||||||
|
struct grid_cell;
|
||||||
struct grid_line;
|
struct grid_line;
|
||||||
struct image;
|
struct image;
|
||||||
struct image_line;
|
struct image_line;
|
||||||
@@ -31,6 +32,7 @@ struct image_store;
|
|||||||
struct screen;
|
struct screen;
|
||||||
struct screen_write_ctx;
|
struct screen_write_ctx;
|
||||||
struct sixel_image;
|
struct sixel_image;
|
||||||
|
struct tty;
|
||||||
|
|
||||||
/* Maximum image size in bytes. */
|
/* Maximum image size in bytes. */
|
||||||
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
|
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
|
||||||
@@ -95,6 +97,12 @@ void image_redraw_scroll(struct screen_write_ctx *);
|
|||||||
void image_damage_screen(struct screen_write_ctx *, u_int, u_int,
|
void image_damage_screen(struct screen_write_ctx *, u_int, u_int,
|
||||||
u_int, u_int);
|
u_int, u_int);
|
||||||
void image_write(struct screen_write_ctx *, struct image *, u_int);
|
void image_write(struct screen_write_ctx *, struct image *, u_int);
|
||||||
|
int image_grid_get_at(struct grid *, u_int, u_int, struct image **,
|
||||||
|
u_int *, u_int *);
|
||||||
|
|
||||||
|
/* image-fallback.c */
|
||||||
|
const struct grid_cell *fallback_map_cell(struct tty *, struct grid *, u_int,
|
||||||
|
u_int, const struct grid_cell *, struct grid_cell *, int *);
|
||||||
|
|
||||||
/* image-sixel.c */
|
/* image-sixel.c */
|
||||||
#define SIXEL_COLOUR_REGISTERS 1024
|
#define SIXEL_COLOUR_REGISTERS 1024
|
||||||
|
|||||||
29
tty-draw.c
29
tty-draw.c
@@ -131,6 +131,10 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
|
|||||||
enum tty_draw_line_state current_state, next_state;
|
enum tty_draw_line_state current_state, next_state;
|
||||||
struct tty_style_ctx default_style_ctx = { 0 };
|
struct tty_style_ctx default_style_ctx = { 0 };
|
||||||
const struct grid_cell *defaults;
|
const struct grid_cell *defaults;
|
||||||
|
#ifdef ENABLE_IMAGES
|
||||||
|
struct image *im;
|
||||||
|
u_int x, y;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (style_ctx == NULL) {
|
if (style_ctx == NULL) {
|
||||||
default_style_ctx.defaults = &grid_default_cell;
|
default_style_ctx.defaults = &grid_default_cell;
|
||||||
@@ -188,6 +192,10 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
|
|||||||
grid_view_get_cell(gd, i, py, &gc);
|
grid_view_get_cell(gd, i, py, &gc);
|
||||||
if (~gc.flags & GRID_FLAG_PADDING)
|
if (~gc.flags & GRID_FLAG_PADDING)
|
||||||
break;
|
break;
|
||||||
|
#ifdef ENABLE_IMAGES
|
||||||
|
if (image_grid_get_at(gd, i, gd->hsize + py, &im, &x, &y) == 1)
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
cx++;
|
cx++;
|
||||||
}
|
}
|
||||||
if (cx != 0) {
|
if (cx != 0) {
|
||||||
@@ -254,21 +262,32 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
|
|||||||
empty = nx - i;
|
empty = nx - i;
|
||||||
gcp = &grid_default_cell;
|
gcp = &grid_default_cell;
|
||||||
|
|
||||||
|
#ifdef ENABLE_IMAGES
|
||||||
|
/* Map an image cell if needed. */
|
||||||
|
gcp = fallback_map_cell(tty, gd, px + i,
|
||||||
|
gd->hsize + py, gcp, &gc, &empty);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Flush the background before clearing. */
|
/* Flush the background before clearing. */
|
||||||
if (empty != 0 && gcp->bg != last.bg)
|
if (empty != 0 && gcp->bg != last.bg)
|
||||||
empty = 0;
|
empty = 0;
|
||||||
} else {
|
} else {
|
||||||
/* Get the current cell. */
|
/* Get the current cell. */
|
||||||
grid_view_get_cell(gd, px + i, py, &gc);
|
grid_view_get_cell(gd, px + i, py, &gc);
|
||||||
|
gcp = &gc;
|
||||||
|
|
||||||
|
#ifdef ENABLE_IMAGES
|
||||||
|
/* Map an image cell if needed. */
|
||||||
|
gcp = fallback_map_cell(tty, gd, px + i,
|
||||||
|
gd->hsize + py, gcp, &gc, NULL);
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Work out empty cells. */
|
/* Work out empty cells. */
|
||||||
empty = tty_draw_line_get_empty(&gc, &last,
|
empty = tty_draw_line_get_empty(gcp, &last,
|
||||||
nx - i);
|
nx - i);
|
||||||
if (empty != 0)
|
if (empty == 0) {
|
||||||
gcp = &gc;
|
|
||||||
else {
|
|
||||||
/* Update for codeset if needed. */
|
/* Update for codeset if needed. */
|
||||||
gcp = tty_check_codeset(tty, &gc);
|
gcp = tty_check_codeset(tty, gcp);
|
||||||
|
|
||||||
/* And for selection. */
|
/* And for selection. */
|
||||||
if (gcp->flags & GRID_FLAG_SELECTED) {
|
if (gcp->flags & GRID_FLAG_SELECTED) {
|
||||||
|
|||||||
Reference in New Issue
Block a user