mirror of
https://github.com/tmux/tmux.git
synced 2026-09-25 06:06:29 +00:00
Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
866a7bf8f0 | ||
|
|
5a6fefb427 | ||
|
|
f2ca8ade2a | ||
|
|
beea19dc97 | ||
|
|
3f6e4efe1b | ||
|
|
3c5a24c858 | ||
|
|
0aad77e779 | ||
|
|
6dfbaf2731 | ||
|
|
f1fa6ab545 | ||
|
|
a5a638314a | ||
|
|
2ec3ad0c09 | ||
|
|
997288a5d9 | ||
|
|
3cb9bd438d | ||
|
|
5b631b4f2d | ||
|
|
d8d5093790 | ||
|
|
59ab8a5160 | ||
|
|
bbbac41c70 | ||
|
|
0007c52d57 | ||
|
|
3df288d4dc | ||
|
|
35b856652d | ||
|
|
09250ea86f | ||
|
|
2dd4618bbb | ||
|
|
f76e9d29ce | ||
|
|
7c6f5d838f | ||
|
|
d9ff36408c | ||
|
|
9cc09b2a9f | ||
|
|
718e281820 | ||
|
|
507ecd320a | ||
|
|
49be26557d | ||
|
|
00ffe744b3 | ||
|
|
6820eceee5 | ||
|
|
c336f49585 | ||
|
|
aa2dd64873 | ||
|
|
832d2beb2d | ||
|
|
08f0c0485b | ||
|
|
c4bdd8eca1 |
+3
-3
@@ -246,9 +246,9 @@ if HAVE_UTF8PROC
|
||||
nodist_tmux_SOURCES += compat/utf8proc.c
|
||||
endif
|
||||
|
||||
# Enable sixel support.
|
||||
if ENABLE_SIXEL
|
||||
dist_tmux_SOURCES += image.c image-sixel.c
|
||||
# Enable image support.
|
||||
if ENABLE_IMAGES
|
||||
dist_tmux_SOURCES += image.c image-sixel.c image-fallback.c
|
||||
endif
|
||||
|
||||
# Add bits for fuzzing if enabled.
|
||||
|
||||
+16
-7
@@ -530,15 +530,24 @@ if test "x$enable_cgroups" = xyes; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Enable sixel support.
|
||||
# Enable image support.
|
||||
AC_ARG_ENABLE(
|
||||
images,
|
||||
AS_HELP_STRING(--enable-images, enable SIXEL and ASCII images)
|
||||
)
|
||||
|
||||
# Keep the old option as an alias for one compatibility cycle.
|
||||
AC_ARG_ENABLE(
|
||||
sixel,
|
||||
AS_HELP_STRING(--enable-sixel, enable sixel images)
|
||||
AS_HELP_STRING(--enable-sixel, deprecated alias for --enable-images)
|
||||
)
|
||||
if test "x$enable_sixel" = xyes; then
|
||||
AC_DEFINE(ENABLE_SIXEL)
|
||||
enable_images=yes
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_SIXEL, [test "x$enable_sixel" = xyes])
|
||||
if test "x$enable_images" = xyes; then
|
||||
AC_DEFINE(ENABLE_IMAGES)
|
||||
fi
|
||||
AM_CONDITIONAL(ENABLE_IMAGES, [test "x$enable_images" = xyes])
|
||||
|
||||
# Check for b64_ntop. If we have b64_ntop, we assume b64_pton as well.
|
||||
AC_MSG_CHECKING(for b64_ntop)
|
||||
@@ -1137,10 +1146,10 @@ if test "x$enable_asan" = xyes; then
|
||||
else
|
||||
AC_MSG_NOTICE([ASAN: off])
|
||||
fi
|
||||
if test "x$enable_sixel" = xyes; then
|
||||
AC_MSG_NOTICE([SIXEL: on])
|
||||
if test "x$enable_images" = xyes; then
|
||||
AC_MSG_NOTICE([images: on])
|
||||
else
|
||||
AC_MSG_NOTICE([SIXEL: off])
|
||||
AC_MSG_NOTICE([images: off])
|
||||
fi
|
||||
if test "x$enable_debug" = xyes; then
|
||||
AC_MSG_NOTICE([debug: on])
|
||||
|
||||
@@ -2964,6 +2964,17 @@ format_cb_sixel_support(__unused struct format_tree *ft)
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Callback for image_support. */
|
||||
static void *
|
||||
format_cb_image_support(__unused struct format_tree *ft)
|
||||
{
|
||||
#ifdef ENABLE_IMAGES
|
||||
return (xstrdup("1"));
|
||||
#else
|
||||
return (xstrdup("0"));
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Callback for active_window_index. */
|
||||
static void *
|
||||
format_cb_active_window_index(struct format_tree *ft)
|
||||
@@ -3627,6 +3638,9 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "host_short", FORMAT_TABLE_STRING,
|
||||
format_cb_host_short
|
||||
},
|
||||
{ "image_support", FORMAT_TABLE_STRING,
|
||||
format_cb_image_support
|
||||
},
|
||||
{ "insert_flag", FORMAT_TABLE_STRING,
|
||||
format_cb_insert_flag
|
||||
},
|
||||
|
||||
@@ -41,6 +41,9 @@
|
||||
/* Default grid cell data. */
|
||||
const struct grid_cell grid_default_cell = {
|
||||
{ { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 8, 0
|
||||
#ifdef ENABLE_IMAGES
|
||||
, 0, 0, 0
|
||||
#endif
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -49,16 +52,38 @@ const struct grid_cell grid_default_cell = {
|
||||
*/
|
||||
static const struct grid_cell grid_padding_cell = {
|
||||
{ { '!' }, 0, 0, 0 }, 0, GRID_FLAG_PADDING, 8, 8, 8, 0
|
||||
#ifdef ENABLE_IMAGES
|
||||
, 0, 0, 0
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Cleared grid cell data. */
|
||||
static const struct grid_cell grid_cleared_cell = {
|
||||
{ { ' ' }, 0, 1, 1 }, 0, GRID_FLAG_CLEARED, 8, 8, 8, 0
|
||||
#ifdef ENABLE_IMAGES
|
||||
, 0, 0, 0
|
||||
#endif
|
||||
};
|
||||
static const struct grid_cell_entry grid_cleared_entry = {
|
||||
{ .data = { 0, 8, 8, ' ' } }, GRID_FLAG_CLEARED
|
||||
};
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Return the image referenced by a stored cell, or zero. */
|
||||
static u_int
|
||||
grid_entry_image(struct grid_line *gl, struct grid_cell_entry *gce)
|
||||
{
|
||||
struct grid_extd_entry *gee;
|
||||
|
||||
if (~gce->flags & GRID_FLAG_EXTENDED || gce->offset >= gl->extdsize)
|
||||
return (0);
|
||||
gee = &gl->extddata[gce->offset];
|
||||
if (~gee->flags & GRID_FLAG_IMAGE)
|
||||
return (0);
|
||||
return (image_get_id_by_grid_id(gee->image_id));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
void
|
||||
grid_check_is_clear(struct grid *gd)
|
||||
@@ -134,6 +159,10 @@ grid_need_extended_cell(const struct grid_cell_entry *gce,
|
||||
return (1);
|
||||
if (gc->flags & GRID_FLAG_TAB)
|
||||
return (1);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (gc->flags & GRID_FLAG_IMAGE)
|
||||
return (1);
|
||||
#endif
|
||||
return (0);
|
||||
}
|
||||
|
||||
@@ -181,6 +210,11 @@ grid_extended_cell(struct grid_line *gl, struct grid_cell_entry *gce,
|
||||
gee->bg = gc->bg;
|
||||
gee->us = gc->us;
|
||||
gee->link = gc->link;
|
||||
#ifdef ENABLE_IMAGES
|
||||
gee->image_id = image_get_grid_id(gc->image_id);
|
||||
gee->image_x = gc->image_x;
|
||||
gee->image_y = gc->image_y;
|
||||
#endif
|
||||
return (gee);
|
||||
}
|
||||
|
||||
@@ -268,6 +302,12 @@ grid_clear_cell(struct grid *gd, u_int px, u_int py, u_int bg, int moved)
|
||||
struct grid_extd_entry *gee;
|
||||
u_int old_offset = gce->offset;
|
||||
int had_extd = (gce->flags & GRID_FLAG_EXTENDED);
|
||||
#ifdef ENABLE_IMAGES
|
||||
u_int image_id = grid_entry_image(gl, gce);
|
||||
|
||||
if (!moved && image_id != 0)
|
||||
image_free(image_id);
|
||||
#endif
|
||||
|
||||
memcpy(gce, &grid_cleared_entry, sizeof *gce);
|
||||
if (!moved && had_extd && old_offset < gl->extdsize) {
|
||||
@@ -314,6 +354,13 @@ grid_cells_look_equal(const struct grid_cell *gc1, const struct grid_cell *gc2)
|
||||
return (0);
|
||||
if (gc1->link != gc2->link)
|
||||
return (0);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if ((gc1->flags & GRID_FLAG_IMAGE) &&
|
||||
(gc1->image_id != gc2->image_id ||
|
||||
gc1->image_x != gc2->image_x ||
|
||||
gc1->image_y != gc2->image_y))
|
||||
return (0);
|
||||
#endif
|
||||
return (1);
|
||||
}
|
||||
|
||||
@@ -346,6 +393,10 @@ static void
|
||||
grid_free_line(struct grid *gd, u_int py)
|
||||
{
|
||||
struct grid_line *gl = &gd->linedata[py];
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct grid_cell_entry *gce;
|
||||
u_int image_id, px;
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
assert(gl->cellused <= gl->cellsize);
|
||||
@@ -353,6 +404,14 @@ grid_free_line(struct grid *gd, u_int py)
|
||||
assert(gl->cellsize == 0 || gl->celldata != NULL);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
for (px = 0; px < gl->cellsize; px++) {
|
||||
gce = &gl->celldata[px];
|
||||
image_id = grid_entry_image(gl, gce);
|
||||
if (image_id != 0)
|
||||
image_free(image_id);
|
||||
}
|
||||
#endif
|
||||
free(gl->celldata);
|
||||
free(gl->extddata);
|
||||
memset(gl, 0, sizeof *gl);
|
||||
@@ -624,6 +683,11 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc)
|
||||
gc->bg = gee->bg;
|
||||
gc->us = gee->us;
|
||||
gc->link = gee->link;
|
||||
#ifdef ENABLE_IMAGES
|
||||
gc->image_id = image_get_id_by_grid_id(gee->image_id);
|
||||
gc->image_x = gee->image_x;
|
||||
gc->image_y = gee->image_y;
|
||||
#endif
|
||||
|
||||
if (gc->flags & GRID_FLAG_TAB)
|
||||
grid_set_tab(gc, gee->data);
|
||||
@@ -644,6 +708,11 @@ grid_get_cell1(struct grid_line *gl, u_int px, struct grid_cell *gc)
|
||||
gc->us = 8;
|
||||
utf8_set(&gc->data, gce->data.data);
|
||||
gc->link = 0;
|
||||
#ifdef ENABLE_IMAGES
|
||||
gc->image_id = 0;
|
||||
gc->image_x = 0;
|
||||
gc->image_y = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Get cell for reading. */
|
||||
@@ -663,6 +732,9 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc)
|
||||
{
|
||||
struct grid_line *gl;
|
||||
struct grid_cell_entry *gce;
|
||||
#ifdef ENABLE_IMAGES
|
||||
u_int old_id, new_id = 0;
|
||||
#endif
|
||||
|
||||
if (grid_check_y(gd, __func__, py) != 0)
|
||||
return;
|
||||
@@ -674,6 +746,17 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc)
|
||||
gl->cellused = px + 1;
|
||||
|
||||
gce = &gl->celldata[px];
|
||||
#ifdef ENABLE_IMAGES
|
||||
old_id = grid_entry_image(gl, gce);
|
||||
if (gc->flags & GRID_FLAG_IMAGE)
|
||||
new_id = gc->image_id;
|
||||
if (old_id != new_id) {
|
||||
if (old_id != 0)
|
||||
image_free(old_id);
|
||||
if (new_id != 0)
|
||||
image_ref(new_id);
|
||||
}
|
||||
#endif
|
||||
if (grid_need_extended_cell(gce, gc))
|
||||
grid_extended_cell(gl, gce, gc);
|
||||
else
|
||||
@@ -695,6 +778,10 @@ grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc,
|
||||
struct grid_line *gl;
|
||||
struct grid_cell_entry *gce;
|
||||
struct grid_extd_entry *gee;
|
||||
const struct grid_cell *new_gc;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct grid_cell old_gc, image_gc;
|
||||
#endif
|
||||
u_int i;
|
||||
|
||||
if (grid_check_y(gd, __func__, py) != 0)
|
||||
@@ -708,11 +795,39 @@ grid_set_cells(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc,
|
||||
|
||||
for (i = 0; i < slen; i++) {
|
||||
gce = &gl->celldata[px + i];
|
||||
if (grid_need_extended_cell(gce, gc)) {
|
||||
gee = grid_extended_cell(gl, gce, gc);
|
||||
new_gc = gc;
|
||||
#ifdef ENABLE_IMAGES
|
||||
{
|
||||
u_int old_id = grid_entry_image(gl, gce);
|
||||
u_int new_id;
|
||||
|
||||
if (old_id != 0 && (~gc->flags & GRID_FLAG_IMAGE)) {
|
||||
grid_get_cell1(gl, px + i, &old_gc);
|
||||
memcpy(&image_gc, gc, sizeof image_gc);
|
||||
image_gc.flags |= GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED;
|
||||
image_gc.image_id = old_gc.image_id;
|
||||
image_gc.image_x = old_gc.image_x;
|
||||
image_gc.image_y = old_gc.image_y;
|
||||
new_gc = &image_gc;
|
||||
}
|
||||
if (new_gc->flags & GRID_FLAG_IMAGE)
|
||||
new_id = new_gc->image_id;
|
||||
else
|
||||
new_id = 0;
|
||||
|
||||
if (old_id != new_id) {
|
||||
if (old_id != 0)
|
||||
image_free(old_id);
|
||||
if (new_id != 0)
|
||||
image_ref(new_id);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (grid_need_extended_cell(gce, new_gc)) {
|
||||
gee = grid_extended_cell(gl, gce, new_gc);
|
||||
gee->data = utf8_build_one(s[i]);
|
||||
} else
|
||||
grid_store_cell(gce, gc, s[i]);
|
||||
grid_store_cell(gce, new_gc, s[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,6 +942,9 @@ grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx,
|
||||
{
|
||||
struct grid_line *gl;
|
||||
u_int xx;
|
||||
#ifdef ENABLE_IMAGES
|
||||
u_int image_id;
|
||||
#endif
|
||||
|
||||
if (nx == 0 || px == dx)
|
||||
return;
|
||||
@@ -837,6 +955,16 @@ grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx,
|
||||
|
||||
grid_expand_line(gd, py, px + nx, 8);
|
||||
grid_expand_line(gd, py, dx + nx, 8);
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* References in the source range are transferred, not duplicated. */
|
||||
for (xx = dx; xx < dx + nx; xx++) {
|
||||
if (xx >= px && xx < px + nx)
|
||||
continue;
|
||||
image_id = grid_entry_image(gl, &gl->celldata[xx]);
|
||||
if (image_id != 0)
|
||||
image_free(image_id);
|
||||
}
|
||||
#endif
|
||||
memmove(&gl->celldata[dx], &gl->celldata[px],
|
||||
nx * sizeof *gl->celldata);
|
||||
if (dx + nx > gl->cellused)
|
||||
@@ -1209,6 +1337,13 @@ grid_string_cells(struct grid *gd, u_int px, u_int py, u_int nx,
|
||||
grid_get_cell(gd, xx, py, &gc);
|
||||
if (gc.flags & GRID_FLAG_PADDING)
|
||||
continue;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (gc.flags & GRID_FLAG_IMAGE) {
|
||||
utf8_set(&gc.data, ' ');
|
||||
gc.flags &= ~(GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED);
|
||||
gc.image_id = gc.image_x = gc.image_y = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (lastgc != NULL && (flags & GRID_STRING_WITH_SEQUENCES)) {
|
||||
grid_string_cells_code(*lastgc, &gc, code, sizeof code,
|
||||
@@ -1276,6 +1411,9 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
|
||||
{
|
||||
struct grid_line *dstl, *srcl;
|
||||
u_int yy;
|
||||
#ifdef ENABLE_IMAGES
|
||||
u_int px, image_id;
|
||||
#endif
|
||||
|
||||
if (dy + ny > dst->hsize + dst->sy)
|
||||
ny = dst->hsize + dst->sy - dy;
|
||||
@@ -1304,6 +1442,13 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
|
||||
} else
|
||||
dstl->extddata = NULL;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
for (px = 0; px < dstl->cellsize; px++) {
|
||||
image_id = grid_entry_image(dstl, &dstl->celldata[px]);
|
||||
if (image_id != 0)
|
||||
image_ref(image_id);
|
||||
}
|
||||
#endif
|
||||
sy++;
|
||||
dy++;
|
||||
}
|
||||
@@ -1317,6 +1462,30 @@ grid_reflow_dead(struct grid_line *gl)
|
||||
gl->flags = GRID_LINE_DEAD;
|
||||
}
|
||||
|
||||
/* Image rows are cell-aligned and must not be reflowed like text. */
|
||||
static int
|
||||
grid_reflow_has_image(struct grid_line *gl)
|
||||
{
|
||||
struct grid_cell gc;
|
||||
u_int i;
|
||||
|
||||
if (~gl->flags & GRID_LINE_EXTENDED)
|
||||
return (0);
|
||||
for (i = 0; i < gl->cellused; i++) {
|
||||
grid_get_cell1(gl, i, &gc);
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (gc.flags & GRID_FLAG_IMAGE)
|
||||
return (1);
|
||||
#endif
|
||||
/* 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)
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Add lines, return the first new one. */
|
||||
static struct grid_line *
|
||||
grid_reflow_add(struct grid *gd, u_int n)
|
||||
@@ -1379,6 +1548,10 @@ grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy,
|
||||
break;
|
||||
line = yy + 1 + lines;
|
||||
|
||||
/* Do not join wrapped text to a cell-aligned image row. */
|
||||
if (grid_reflow_has_image(&gd->linedata[line]))
|
||||
break;
|
||||
|
||||
/* If the next line is empty, skip it. */
|
||||
if (~gd->linedata[line].flags & GRID_LINE_WRAPPED)
|
||||
wrapped = 0;
|
||||
@@ -1439,8 +1612,7 @@ grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy,
|
||||
|
||||
/* Remove the lines that were completely consumed. */
|
||||
for (i = yy + 1; i < yy + 1 + lines; i++) {
|
||||
free(gd->linedata[i].celldata);
|
||||
free(gd->linedata[i].extddata);
|
||||
grid_free_line(gd, i);
|
||||
grid_reflow_dead(&gd->linedata[i]);
|
||||
}
|
||||
|
||||
@@ -1496,6 +1668,15 @@ grid_reflow_split(struct grid *target, struct grid *gd, u_int sx, u_int yy,
|
||||
}
|
||||
width += gc.data.width;
|
||||
grid_set_cell(target, xx, line, &gc);
|
||||
#ifdef ENABLE_IMAGES
|
||||
/*
|
||||
* The tail of the original line is discarded below. Transfer
|
||||
* its image reference to the new cell rather than duplicating
|
||||
* it.
|
||||
*/
|
||||
if (gc.flags & GRID_FLAG_IMAGE)
|
||||
image_free(gc.image_id);
|
||||
#endif
|
||||
xx++;
|
||||
}
|
||||
if (flags & GRID_LINE_WRAPPED)
|
||||
@@ -1542,6 +1723,12 @@ grid_reflow(struct grid *gd, u_int sx)
|
||||
if (gl->flags & GRID_LINE_DEAD)
|
||||
continue;
|
||||
|
||||
/* Keep image markers at their original cell coordinates. */
|
||||
if (grid_reflow_has_image(gl)) {
|
||||
grid_reflow_move(target, gl);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Work out the width of this line. at is the point at which
|
||||
* the available width is hit, and width is the full line
|
||||
|
||||
@@ -0,0 +1,460 @@
|
||||
/* $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 <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
/* Character-cell fallback for clients without a graphical image protocol. */
|
||||
|
||||
enum image_glyph_detail {
|
||||
IMAGE_GLYPH_ASCII,
|
||||
IMAGE_GLYPH_SHADE5,
|
||||
IMAGE_GLYPH_SHADE8,
|
||||
IMAGE_GLYPH_HALF,
|
||||
IMAGE_GLYPH_QUADRANT,
|
||||
IMAGE_GLYPH_SEXTANT
|
||||
};
|
||||
|
||||
enum image_glyph_palette {
|
||||
IMAGE_GLYPH_PALETTE_8,
|
||||
IMAGE_GLYPH_PALETTE_16,
|
||||
IMAGE_GLYPH_PALETTE_256,
|
||||
IMAGE_GLYPH_PALETTE_RGB
|
||||
};
|
||||
|
||||
struct image_rgb {
|
||||
u_char r;
|
||||
u_char g;
|
||||
u_char b;
|
||||
};
|
||||
|
||||
struct image_fallback_data {
|
||||
u_char *shade5;
|
||||
u_char *shade8;
|
||||
};
|
||||
|
||||
static const struct image_rgb image_ansi_colours[16] = {
|
||||
{ 0x00, 0x00, 0x00 }, { 0x80, 0x00, 0x00 },
|
||||
{ 0x00, 0x80, 0x00 }, { 0x80, 0x80, 0x00 },
|
||||
{ 0x00, 0x00, 0x80 }, { 0x80, 0x00, 0x80 },
|
||||
{ 0x00, 0x80, 0x80 }, { 0xc0, 0xc0, 0xc0 },
|
||||
{ 0x80, 0x80, 0x80 }, { 0xff, 0x00, 0x00 },
|
||||
{ 0x00, 0xff, 0x00 }, { 0xff, 0xff, 0x00 },
|
||||
{ 0x00, 0x00, 0xff }, { 0xff, 0x00, 0xff },
|
||||
{ 0x00, 0xff, 0xff }, { 0xff, 0xff, 0xff }
|
||||
};
|
||||
|
||||
/* Return squared distance between two RGB colours. */
|
||||
static u_int
|
||||
image_glyph_distance(struct image_rgb a, struct image_rgb b)
|
||||
{
|
||||
int r = a.r - b.r, g = a.g - b.g, bl = a.b - b.b;
|
||||
|
||||
return (r * r + g * g + bl * bl);
|
||||
}
|
||||
|
||||
/* Return if an RGB colour is close to grey. */
|
||||
static int
|
||||
image_glyph_low_saturation(struct image_rgb colour)
|
||||
{
|
||||
u_int minimum, maximum;
|
||||
|
||||
minimum = maximum = colour.r;
|
||||
if (colour.g < minimum)
|
||||
minimum = colour.g;
|
||||
if (colour.b < minimum)
|
||||
minimum = colour.b;
|
||||
if (colour.g > maximum)
|
||||
maximum = colour.g;
|
||||
if (colour.b > maximum)
|
||||
maximum = colour.b;
|
||||
return (maximum == 0 || (maximum - minimum) * 4 <= maximum);
|
||||
}
|
||||
|
||||
/* Find the closest ANSI colour. */
|
||||
static u_int
|
||||
image_glyph_nearest_ansi(struct image_rgb colour, u_int colours)
|
||||
{
|
||||
u_int i, distance, best_distance = UINT_MAX, best = 0;
|
||||
int neutral = image_glyph_low_saturation(colour);
|
||||
|
||||
for (i = 0; i < colours; i++) {
|
||||
if (neutral && i != 0 && i != 7 &&
|
||||
(colours != 16 || (i != 8 && i != 15)))
|
||||
continue;
|
||||
distance = image_glyph_distance(colour, image_ansi_colours[i]);
|
||||
if (distance < best_distance) {
|
||||
best_distance = distance;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
return (best);
|
||||
}
|
||||
|
||||
/* Quantize an RGB colour for a fallback palette. */
|
||||
static struct image_rgb
|
||||
image_glyph_quantize(struct image_rgb colour, enum image_glyph_palette palette,
|
||||
int *output)
|
||||
{
|
||||
struct image_rgb result;
|
||||
int value;
|
||||
u_int index;
|
||||
|
||||
if (palette == IMAGE_GLYPH_PALETTE_RGB) {
|
||||
*output = colour_join_rgb(colour.r, colour.g, colour.b);
|
||||
return (colour);
|
||||
}
|
||||
if (palette == IMAGE_GLYPH_PALETTE_256) {
|
||||
*output = colour_find_rgb(colour.r, colour.g, colour.b);
|
||||
value = colour_256toRGB(*output);
|
||||
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);
|
||||
result = image_ansi_colours[index];
|
||||
if (index < 8)
|
||||
*output = index;
|
||||
else
|
||||
*output = 90 + index - 8;
|
||||
return (result);
|
||||
}
|
||||
|
||||
/* Fit two representative colours to image samples. */
|
||||
static void
|
||||
image_glyph_fit_colours(const struct image_rgb *samples, u_int count,
|
||||
struct image_rgb centres[2])
|
||||
{
|
||||
u_int sum[2][3], counts[2], group, i, j, iteration, distance;
|
||||
u_int maximum = 0, first = 0, second = 0;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
for (j = i + 1; j < count; j++) {
|
||||
distance = image_glyph_distance(samples[i], samples[j]);
|
||||
if (distance > maximum) {
|
||||
maximum = distance;
|
||||
first = i;
|
||||
second = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
centres[0] = samples[first];
|
||||
centres[1] = samples[second];
|
||||
for (iteration = 0; iteration < 4; iteration++) {
|
||||
memset(sum, 0, sizeof sum);
|
||||
memset(counts, 0, sizeof counts);
|
||||
for (i = 0; i < count; i++) {
|
||||
group = image_glyph_distance(samples[i], centres[1]) <
|
||||
image_glyph_distance(samples[i], centres[0]);
|
||||
sum[group][0] += samples[i].r;
|
||||
sum[group][1] += samples[i].g;
|
||||
sum[group][2] += samples[i].b;
|
||||
counts[group]++;
|
||||
}
|
||||
for (i = 0; i < 2; i++) {
|
||||
if (counts[i] == 0)
|
||||
continue;
|
||||
centres[i].r = (sum[i][0] + counts[i] / 2) / counts[i];
|
||||
centres[i].g = (sum[i][1] + counts[i] / 2) / counts[i];
|
||||
centres[i].b = (sum[i][2] + counts[i] / 2) / counts[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Set UTF-8 data from an ACS key. */
|
||||
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);
|
||||
|
||||
if (s == NULL)
|
||||
return (0);
|
||||
ud = utf8_fromcstr(s);
|
||||
if (ud[0].size == 0) {
|
||||
free(ud);
|
||||
return (0);
|
||||
}
|
||||
memcpy(data, &ud[0], sizeof *data);
|
||||
free(ud);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Return the ACS key for a fallback block mask. */
|
||||
static u_char
|
||||
image_glyph_block_key(enum image_glyph_detail detail, u_int mask)
|
||||
{
|
||||
static const u_char half[4] = {
|
||||
0, TTY_ACS_IMAGE_HALF_UPPER, TTY_ACS_IMAGE_HALF_LOWER,
|
||||
TTY_ACS_IMAGE_BLOCK
|
||||
};
|
||||
static const u_char quadrant[16] = {
|
||||
0, TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT, TTY_ACS_IMAGE_HALF_UPPER,
|
||||
TTY_ACS_IMAGE_QUADRANT_LOWER_LEFT, TTY_ACS_IMAGE_HALF_LEFT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_LEFT,
|
||||
TTY_ACS_IMAGE_QUADRANT_LOWER_RIGHT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_RIGHT,
|
||||
TTY_ACS_IMAGE_HALF_RIGHT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_RIGHT,
|
||||
TTY_ACS_IMAGE_HALF_LOWER,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_LEFT_LOWER_RIGHT,
|
||||
TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT_LOWER_RIGHT,
|
||||
TTY_ACS_IMAGE_BLOCK
|
||||
};
|
||||
|
||||
if (detail == IMAGE_GLYPH_HALF)
|
||||
return (half[mask]);
|
||||
if (detail == IMAGE_GLYPH_QUADRANT)
|
||||
return (quadrant[mask]);
|
||||
if (mask == 0)
|
||||
return (0);
|
||||
if (mask == 21)
|
||||
return (TTY_ACS_IMAGE_HALF_LEFT);
|
||||
if (mask == 42)
|
||||
return (TTY_ACS_IMAGE_HALF_RIGHT);
|
||||
if (mask == 63)
|
||||
return (TTY_ACS_IMAGE_BLOCK);
|
||||
return (tty_acs_image_sextant(mask));
|
||||
}
|
||||
|
||||
/* Dither image brightness into cached shade levels. */
|
||||
static u_char *
|
||||
image_glyph_make_shades(struct image *im, u_int levels)
|
||||
{
|
||||
struct image_fallback_data *data;
|
||||
int *values, value, represented, error;
|
||||
size_t cells, index;
|
||||
u_int x, y, scan, level, minimum = 255, maximum = 0;
|
||||
u_int sx, sy;
|
||||
int reverse;
|
||||
u_char *result;
|
||||
|
||||
data = image_get_fallback_data(im);
|
||||
if (data == NULL) {
|
||||
data = xcalloc(1, sizeof *data);
|
||||
image_set_fallback_data(im, data);
|
||||
}
|
||||
result = (levels == 5 ? data->shade5 : data->shade8);
|
||||
if (result != NULL)
|
||||
return (result);
|
||||
image_get_size_in_cells(im, &sx, &sy);
|
||||
cells = (size_t)sx * sy;
|
||||
values = xcalloc(cells, sizeof *values);
|
||||
result = xcalloc(cells, 1);
|
||||
for (y = 0; y < sy; y++) {
|
||||
for (x = 0; x < sx; x++) {
|
||||
index = (size_t)y * sx + x;
|
||||
value = image_get_brightness(im, x, y);
|
||||
if ((u_int)value < minimum)
|
||||
minimum = value;
|
||||
if ((u_int)value > maximum)
|
||||
maximum = value;
|
||||
values[index] = value;
|
||||
}
|
||||
}
|
||||
if (maximum > minimum) {
|
||||
for (index = 0; index < cells; index++)
|
||||
values[index] = (values[index] - minimum) * 255 /
|
||||
(maximum - minimum);
|
||||
}
|
||||
for (y = 0; y < sy; y++) {
|
||||
reverse = (y & 1);
|
||||
for (scan = 0; scan < sx; scan++) {
|
||||
x = (reverse ? sx - scan - 1 : scan);
|
||||
index = (size_t)y * sx + x;
|
||||
value = values[index];
|
||||
if (value < 0)
|
||||
value = 0;
|
||||
if (value > 255)
|
||||
value = 255;
|
||||
level = (value * (levels - 1) + 127) / 255;
|
||||
result[index] = level;
|
||||
represented = level * 255 / (levels - 1);
|
||||
error = value - represented;
|
||||
if (!reverse && x + 1 < sx)
|
||||
values[index + 1] += error * 7 / 16;
|
||||
if (reverse && x != 0)
|
||||
values[index - 1] += error * 7 / 16;
|
||||
if (y + 1 == sy)
|
||||
continue;
|
||||
if (!reverse && x != 0)
|
||||
values[index + sx - 1] += error * 3 / 16;
|
||||
if (reverse && x + 1 < sx)
|
||||
values[index + sx + 1] += error * 3 / 16;
|
||||
values[index + sx] += error * 5 / 16;
|
||||
if (!reverse && x + 1 < sx)
|
||||
values[index + sx + 1] += error / 16;
|
||||
if (reverse && x != 0)
|
||||
values[index + sx - 1] += error / 16;
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
if (levels == 5)
|
||||
data->shade5 = result;
|
||||
else
|
||||
data->shade8 = result;
|
||||
return (result);
|
||||
}
|
||||
|
||||
/* Select the best fallback colour palette for a terminal. */
|
||||
static enum image_glyph_palette
|
||||
image_glyph_get_palette(struct tty *tty)
|
||||
{
|
||||
int colours;
|
||||
|
||||
if (tty->term->flags & TERM_RGBCOLOURS)
|
||||
return (IMAGE_GLYPH_PALETTE_RGB);
|
||||
if (tty->term->flags & TERM_256COLOURS)
|
||||
return (IMAGE_GLYPH_PALETTE_256);
|
||||
colours = tty_term_number(tty->term, TTYC_COLORS);
|
||||
if (colours >= 256)
|
||||
return (IMAGE_GLYPH_PALETTE_256);
|
||||
if (colours >= 16)
|
||||
return (IMAGE_GLYPH_PALETTE_16);
|
||||
return (IMAGE_GLYPH_PALETTE_8);
|
||||
}
|
||||
|
||||
/* Select the best fallback glyph detail for a terminal. */
|
||||
static enum image_glyph_detail
|
||||
image_glyph_get_detail(struct tty *tty, enum image_glyph_palette palette)
|
||||
{
|
||||
if (tty_acs_needed(tty))
|
||||
return (IMAGE_GLYPH_ASCII);
|
||||
if (tty->term->flags & TERM_IMAGE_SEXTANTS)
|
||||
return (IMAGE_GLYPH_SEXTANT);
|
||||
if (tty->term->flags & TERM_IMAGE_QUADRANTS)
|
||||
return (IMAGE_GLYPH_QUADRANT);
|
||||
if (palette != IMAGE_GLYPH_PALETTE_8)
|
||||
return (IMAGE_GLYPH_HALF);
|
||||
if (tty_term_has(tty->term, TTYC_NOBR))
|
||||
return (IMAGE_GLYPH_SHADE5);
|
||||
return (IMAGE_GLYPH_SHADE8);
|
||||
}
|
||||
|
||||
/* Draw one image cell with a coloured 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,
|
||||
struct grid_cell *out)
|
||||
{
|
||||
struct image_rgb samples[6], centres[2], quantized[2];
|
||||
u_int columns, rows, sx, sy, i, n, mask;
|
||||
int colours[2];
|
||||
u_char key;
|
||||
|
||||
columns = (detail == IMAGE_GLYPH_HALF ? 1 : 2);
|
||||
rows = (detail == IMAGE_GLYPH_HALF ? 2 :
|
||||
detail == IMAGE_GLYPH_QUADRANT ? 2 : 3);
|
||||
i = 0;
|
||||
for (sy = 0; sy < rows; sy++) {
|
||||
for (sx = 0; sx < columns; sx++) {
|
||||
image_get_cell_average(im, x, y, sx, sy, columns, rows,
|
||||
&samples[i].r, &samples[i].g, &samples[i].b);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
n = columns * rows;
|
||||
image_glyph_fit_colours(samples, n, centres);
|
||||
quantized[0] = image_glyph_quantize(centres[0], palette, &colours[0]);
|
||||
quantized[1] = image_glyph_quantize(centres[1], palette, &colours[1]);
|
||||
mask = 0;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (image_glyph_distance(samples[i], quantized[1]) <
|
||||
image_glyph_distance(samples[i], quantized[0]))
|
||||
mask |= (1U << i);
|
||||
}
|
||||
key = image_glyph_block_key(detail, mask);
|
||||
if (key == 0)
|
||||
utf8_set(&out->data, ' ');
|
||||
else if (!image_glyph_set_acs(tty, &out->data, key))
|
||||
utf8_set(&out->data, ' ');
|
||||
out->fg = colours[1];
|
||||
out->bg = colours[0];
|
||||
}
|
||||
|
||||
/* Fill a grid cell with an image fallback glyph. */
|
||||
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,
|
||||
__unused const struct tty_style_ctx *style_ctx)
|
||||
{
|
||||
static const char ascii[] = " .:-=+*#%@";
|
||||
static const u_char shades[5] = { 0, TTY_ACS_IMAGE_SHADE_LIGHT,
|
||||
TTY_ACS_IMAGE_SHADE_MEDIUM, TTY_ACS_IMAGE_SHADE_DARK,
|
||||
TTY_ACS_IMAGE_BLOCK };
|
||||
static const u_char bold_shades[8] = { 0,
|
||||
TTY_ACS_IMAGE_SHADE_LIGHT, TTY_ACS_IMAGE_SHADE_LIGHT,
|
||||
TTY_ACS_IMAGE_SHADE_MEDIUM, TTY_ACS_IMAGE_SHADE_MEDIUM,
|
||||
TTY_ACS_IMAGE_SHADE_DARK, TTY_ACS_IMAGE_BLOCK,
|
||||
TTY_ACS_IMAGE_BLOCK };
|
||||
enum image_glyph_palette palette;
|
||||
enum image_glyph_detail detail;
|
||||
u_char *levels, key;
|
||||
u_int level = 0, sx;
|
||||
|
||||
memcpy(out, gc, sizeof *out);
|
||||
out->flags &= ~GRID_FLAG_IMAGE;
|
||||
palette = image_glyph_get_palette(tty);
|
||||
detail = image_glyph_get_detail(tty, palette);
|
||||
if (detail == IMAGE_GLYPH_ASCII) {
|
||||
level = image_get_brightness(im, x, y) * (sizeof ascii - 2) / 255;
|
||||
utf8_set(&out->data, ascii[level]);
|
||||
return;
|
||||
}
|
||||
if (detail == IMAGE_GLYPH_SHADE5 || detail == IMAGE_GLYPH_SHADE8) {
|
||||
levels = image_glyph_make_shades(im,
|
||||
detail == IMAGE_GLYPH_SHADE5 ? 5 : 8);
|
||||
image_get_size_in_cells(im, &sx, NULL);
|
||||
level = levels[(size_t)y * sx + x];
|
||||
key = (detail == IMAGE_GLYPH_SHADE5 ? shades[level] :
|
||||
bold_shades[level]);
|
||||
if (key == 0)
|
||||
utf8_set(&out->data, ' ');
|
||||
else if (!image_glyph_set_acs(tty, &out->data, key))
|
||||
utf8_set(&out->data, ' ');
|
||||
out->fg = 7;
|
||||
out->bg = 0;
|
||||
out->attr &= ~GRID_ATTR_BRIGHT;
|
||||
if (detail == IMAGE_GLYPH_SHADE8 &&
|
||||
(level == 2 || level == 4 || level == 7))
|
||||
out->attr |= GRID_ATTR_BRIGHT;
|
||||
return;
|
||||
}
|
||||
image_glyph_block(tty, im, x, y, detail, palette, out);
|
||||
}
|
||||
|
||||
/* Free cached fallback rendering data for an image. */
|
||||
void
|
||||
image_free_fallback(struct image *im)
|
||||
{
|
||||
struct image_fallback_data *data;
|
||||
|
||||
data = image_get_fallback_data(im);
|
||||
if (data == NULL)
|
||||
return;
|
||||
free(data->shade5);
|
||||
free(data->shade8);
|
||||
free(data);
|
||||
}
|
||||
+788
-12
@@ -2,6 +2,7 @@
|
||||
|
||||
/*
|
||||
* Copyright (c) 2019 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,6 +19,7 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -25,6 +27,10 @@
|
||||
|
||||
#define SIXEL_WIDTH_LIMIT 10000
|
||||
#define SIXEL_HEIGHT_LIMIT 10000
|
||||
#define SIXEL_PALETTE_SIZE 256
|
||||
#define SIXEL_HISTOGRAM_LEVELS 32
|
||||
#define SIXEL_HISTOGRAM_SIZE (SIXEL_HISTOGRAM_LEVELS * \
|
||||
SIXEL_HISTOGRAM_LEVELS * SIXEL_HISTOGRAM_LEVELS)
|
||||
|
||||
struct sixel_line {
|
||||
u_int x;
|
||||
@@ -66,6 +72,57 @@ struct sixel_chunk {
|
||||
char *data;
|
||||
};
|
||||
|
||||
struct sixel_image_cache {
|
||||
u_int server_id;
|
||||
u_int xpixel;
|
||||
u_int ypixel;
|
||||
size_t size;
|
||||
uint64_t age;
|
||||
struct sixel_image *si;
|
||||
struct sixel_image_cache *next;
|
||||
};
|
||||
|
||||
struct sixel_output {
|
||||
struct sixel_image_cache *images;
|
||||
size_t size;
|
||||
uint64_t age;
|
||||
};
|
||||
|
||||
struct sixel_hgram {
|
||||
u_int count;
|
||||
uint64_t red;
|
||||
uint64_t green;
|
||||
uint64_t blue;
|
||||
};
|
||||
|
||||
struct sixel_box {
|
||||
u_int red_min;
|
||||
u_int red_max;
|
||||
u_int green_min;
|
||||
u_int green_max;
|
||||
u_int blue_min;
|
||||
u_int blue_max;
|
||||
u_int count;
|
||||
};
|
||||
|
||||
struct sixel_rgb {
|
||||
u_char red;
|
||||
u_char green;
|
||||
u_char blue;
|
||||
};
|
||||
|
||||
struct sixel_source {
|
||||
const u_char *pixels;
|
||||
size_t stride;
|
||||
u_int width;
|
||||
u_int height;
|
||||
u_int canvas_width;
|
||||
u_int canvas_height;
|
||||
u_int sx;
|
||||
u_int sy;
|
||||
};
|
||||
|
||||
/* Grow a SIXEL image to contain a line. */
|
||||
static int
|
||||
sixel_parse_expand_lines(struct sixel_image *si, u_int y)
|
||||
{
|
||||
@@ -78,6 +135,7 @@ sixel_parse_expand_lines(struct sixel_image *si, u_int y)
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Grow a SIXEL line to contain a pixel. */
|
||||
static int
|
||||
sixel_parse_expand_line(struct sixel_image *si, struct sixel_line *sl, u_int x)
|
||||
{
|
||||
@@ -92,6 +150,7 @@ sixel_parse_expand_line(struct sixel_image *si, struct sixel_line *sl, u_int x)
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Return a SIXEL palette index at a pixel. */
|
||||
static u_int
|
||||
sixel_get_pixel(struct sixel_image *si, u_int x, u_int y)
|
||||
{
|
||||
@@ -105,6 +164,7 @@ sixel_get_pixel(struct sixel_image *si, u_int x, u_int y)
|
||||
return (sl->data[x]);
|
||||
}
|
||||
|
||||
/* Set a SIXEL palette index at a pixel. */
|
||||
static int
|
||||
sixel_set_pixel(struct sixel_image *si, u_int x, u_int y, u_int c)
|
||||
{
|
||||
@@ -119,6 +179,7 @@ sixel_set_pixel(struct sixel_image *si, u_int x, u_int y, u_int c)
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Write a SIXEL six-pixel column. */
|
||||
static int
|
||||
sixel_parse_write(struct sixel_image *si, u_int ch)
|
||||
{
|
||||
@@ -133,6 +194,7 @@ sixel_parse_write(struct sixel_image *si, u_int ch)
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Parse a SIXEL raster attribute sequence. */
|
||||
static const char *
|
||||
sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end)
|
||||
{
|
||||
@@ -186,6 +248,7 @@ sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end)
|
||||
return (last);
|
||||
}
|
||||
|
||||
/* Parse a SIXEL colour register sequence. */
|
||||
static const char *
|
||||
sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end)
|
||||
{
|
||||
@@ -249,6 +312,7 @@ sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end)
|
||||
return (last);
|
||||
}
|
||||
|
||||
/* Parse a SIXEL repeat sequence. */
|
||||
static const char *
|
||||
sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end)
|
||||
{
|
||||
@@ -290,6 +354,7 @@ sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end)
|
||||
return (last);
|
||||
}
|
||||
|
||||
/* Parse SIXEL data into an indexed image. */
|
||||
struct sixel_image *
|
||||
sixel_parse(const char *buf, size_t len, u_int p2, u_int xpixel, u_int ypixel)
|
||||
{
|
||||
@@ -355,6 +420,7 @@ bad:
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Free an indexed SIXEL image. */
|
||||
void
|
||||
sixel_free(struct sixel_image *si)
|
||||
{
|
||||
@@ -368,6 +434,7 @@ sixel_free(struct sixel_image *si)
|
||||
free(si);
|
||||
}
|
||||
|
||||
/* Write a SIXEL image to the debug log. */
|
||||
void
|
||||
sixel_log(struct sixel_image *si)
|
||||
{
|
||||
@@ -394,24 +461,121 @@ sixel_log(struct sixel_image *si)
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the cell dimensions occupied by a SIXEL image. */
|
||||
void
|
||||
sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y)
|
||||
{
|
||||
if ((si->x % si->xpixel) == 0)
|
||||
*x = (si->x / si->xpixel);
|
||||
else
|
||||
*x = 1 + (si->x / si->xpixel);
|
||||
if ((si->y % si->ypixel) == 0)
|
||||
*y = (si->y / si->ypixel);
|
||||
else
|
||||
*y = 1 + (si->y / si->ypixel);
|
||||
if (si->xpixel == 0)
|
||||
si->xpixel = 8;
|
||||
if (si->ypixel == 0)
|
||||
si->ypixel = 16;
|
||||
image_size_in_cells(si->x, si->y, si->xpixel, si->ypixel, x, y);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Convert one HLS component to RGB. */
|
||||
static double
|
||||
sixel_hue(double p, double q, double t)
|
||||
{
|
||||
if (t < 0)
|
||||
t += 1;
|
||||
if (t > 1)
|
||||
t -= 1;
|
||||
if (t < 1.0 / 6)
|
||||
return (p + (q - p) * 6 * t);
|
||||
if (t < 1.0 / 2)
|
||||
return (q);
|
||||
if (t < 2.0 / 3)
|
||||
return (p + (q - p) * (2.0 / 3 - t) * 6);
|
||||
return (p);
|
||||
}
|
||||
|
||||
/* Convert a SIXEL colour register to RGB. */
|
||||
static void
|
||||
sixel_colour_to_rgb(u_int colour, u_char *r, u_char *g, u_char *b)
|
||||
{
|
||||
u_int type = colour >> 25;
|
||||
double h, l, s, p, q;
|
||||
|
||||
if (type == 2) {
|
||||
*r = (((colour >> 16) & 0xff) * 255 + 50) / 100;
|
||||
*g = (((colour >> 8) & 0xff) * 255 + 50) / 100;
|
||||
*b = ((colour & 0xff) * 255 + 50) / 100;
|
||||
return;
|
||||
}
|
||||
if (type != 1) {
|
||||
*r = *g = *b = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
h = ((colour >> 16) & 0x1ff) / 360.0;
|
||||
l = ((colour >> 8) & 0xff) / 100.0;
|
||||
s = (colour & 0xff) / 100.0;
|
||||
if (s == 0) {
|
||||
*r = *g = *b = l * 255 + 0.5;
|
||||
return;
|
||||
}
|
||||
q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
p = 2 * l - q;
|
||||
/* SIXEL HLS has blue at 0, red at 120 and green at 240 degrees. */
|
||||
*r = sixel_hue(p, q, h) * 255 + 0.5;
|
||||
*g = sixel_hue(p, q, h - 1.0 / 3) * 255 + 0.5;
|
||||
*b = sixel_hue(p, q, h + 1.0 / 3) * 255 + 0.5;
|
||||
}
|
||||
|
||||
/* Convert decoded SIXEL data into the immutable image. */
|
||||
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;
|
||||
|
||||
if ((uint64_t)si->x * si->y * 4 > SIZE_MAX)
|
||||
return (NULL);
|
||||
pixels = xcalloc(si->x * si->y, 4);
|
||||
for (y = 0; y < si->y; y++) {
|
||||
for (x = 0; x < si->x; x++) {
|
||||
c = sixel_get_pixel(si, x, y);
|
||||
pixel = pixels + ((size_t)y * si->x + x) * 4;
|
||||
if (c == 0) {
|
||||
pixel[3] = si->p2 == 1 ? 0 : 255;
|
||||
continue;
|
||||
}
|
||||
c--;
|
||||
if (c < si->ncolours)
|
||||
sixel_colour_to_rgb(si->colours[c], &r, &g, &b);
|
||||
else
|
||||
r = g = b = 0;
|
||||
pixel[0] = r;
|
||||
pixel[1] = g;
|
||||
pixel[2] = b;
|
||||
pixel[3] = 255;
|
||||
}
|
||||
}
|
||||
sixel_size_in_cells(si, &sx, &sy);
|
||||
if ((uint64_t)sx * si->xpixel > UINT_MAX ||
|
||||
(uint64_t)sy * si->ypixel > UINT_MAX) {
|
||||
free(pixels);
|
||||
return (NULL);
|
||||
}
|
||||
im = image_create(si->x, si->y, sx * si->xpixel, sy * si->ypixel,
|
||||
sx, sy, pixels);
|
||||
if (im == NULL)
|
||||
free(pixels);
|
||||
else
|
||||
image_set_sixel(im, si);
|
||||
return (im);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Scale or crop an indexed SIXEL image. */
|
||||
struct sixel_image *
|
||||
sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox,
|
||||
u_int oy, u_int sx, u_int sy, int colours)
|
||||
{
|
||||
struct sixel_image *new;
|
||||
uint64_t x0, x1, y0, y1;
|
||||
u_int cx, cy, pox, poy, psx, psy, tsx, tsy, px, py;
|
||||
u_int x, y, i;
|
||||
|
||||
@@ -435,10 +599,19 @@ sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox,
|
||||
if (ypixel == 0)
|
||||
ypixel = si->ypixel;
|
||||
|
||||
pox = ox * si->xpixel;
|
||||
poy = oy * si->ypixel;
|
||||
psx = sx * si->xpixel;
|
||||
psy = sy * si->ypixel;
|
||||
/*
|
||||
* Map cell boundaries over the actual raster, not the rounded-up cell
|
||||
* canvas. Otherwise a raster shorter than its last cell row produces an
|
||||
* empty strip when it is scaled for output.
|
||||
*/
|
||||
x0 = (uint64_t)ox * si->x / cx;
|
||||
x1 = (uint64_t)(ox + sx) * si->x / cx;
|
||||
y0 = (uint64_t)oy * si->y / cy;
|
||||
y1 = (uint64_t)(oy + sy) * si->y / cy;
|
||||
pox = x0;
|
||||
poy = y0;
|
||||
psx = x1 - x0;
|
||||
psy = y1 - y0;
|
||||
|
||||
tsx = sx * xpixel;
|
||||
tsy = sy * ypixel;
|
||||
@@ -477,6 +650,7 @@ sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox,
|
||||
return (new);
|
||||
}
|
||||
|
||||
/* Append data to a growing SIXEL output buffer. */
|
||||
static void
|
||||
sixel_print_add(char **buf, size_t *len, size_t *used, const char *s,
|
||||
size_t slen)
|
||||
@@ -489,6 +663,7 @@ sixel_print_add(char **buf, size_t *len, size_t *used, const char *s,
|
||||
(*used) += slen;
|
||||
}
|
||||
|
||||
/* Append a SIXEL character repetition to an output buffer. */
|
||||
static void
|
||||
sixel_print_repeat(char **buf, size_t *len, size_t *used, u_int count, char ch)
|
||||
{
|
||||
@@ -510,6 +685,7 @@ sixel_print_repeat(char **buf, size_t *len, size_t *used, u_int count, char ch)
|
||||
}
|
||||
}
|
||||
|
||||
/* Build compressed SIXEL output chunks for a sixel row. */
|
||||
static void
|
||||
sixel_print_compress_colors(struct sixel_image *si, struct sixel_chunk *chunks,
|
||||
u_int y, u_int *active, u_int *nactive)
|
||||
@@ -562,6 +738,7 @@ sixel_print_compress_colors(struct sixel_image *si, struct sixel_chunk *chunks,
|
||||
}
|
||||
}
|
||||
|
||||
/* Encode an indexed SIXEL image for terminal output. */
|
||||
char *
|
||||
sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size)
|
||||
{
|
||||
@@ -649,6 +826,605 @@ 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. */
|
||||
static void
|
||||
sixel_box_update(struct sixel_box *box, struct sixel_hgram *hg)
|
||||
{
|
||||
struct sixel_hgram *entry;
|
||||
u_int red, green, blue, index;
|
||||
u_int red_min = SIXEL_HISTOGRAM_LEVELS;
|
||||
u_int green_min = SIXEL_HISTOGRAM_LEVELS;
|
||||
u_int blue_min = SIXEL_HISTOGRAM_LEVELS;
|
||||
u_int red_max = 0, green_max = 0, blue_max = 0;
|
||||
u_int count = 0;
|
||||
|
||||
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++) {
|
||||
index = (red << 10)|(green << 5)|blue;
|
||||
entry = &hg[index];
|
||||
if (entry->count == 0)
|
||||
continue;
|
||||
if (red < red_min)
|
||||
red_min = red;
|
||||
if (red > red_max)
|
||||
red_max = red;
|
||||
if (green < green_min)
|
||||
green_min = green;
|
||||
if (green > green_max)
|
||||
green_max = green;
|
||||
if (blue < blue_min)
|
||||
blue_min = blue;
|
||||
if (blue > blue_max)
|
||||
blue_max = blue;
|
||||
count += entry->count;
|
||||
}
|
||||
}
|
||||
}
|
||||
box->count = count;
|
||||
if (count == 0)
|
||||
return;
|
||||
box->red_min = red_min;
|
||||
box->red_max = red_max;
|
||||
box->green_min = green_min;
|
||||
box->green_max = green_max;
|
||||
box->blue_min = blue_min;
|
||||
box->blue_max = blue_max;
|
||||
}
|
||||
|
||||
/* Split a histogram box at its weighted median. */
|
||||
static int
|
||||
sixel_box_split(struct sixel_box *box, struct sixel_box *new,
|
||||
struct sixel_hgram *hg)
|
||||
{
|
||||
u_int levels[SIXEL_HISTOGRAM_LEVELS] = { 0 };
|
||||
u_int red, green, blue, index, channel, first, last, level;
|
||||
u_int red_range, green_range, blue_range, count = 0;
|
||||
|
||||
red_range = box->red_max - box->red_min;
|
||||
green_range = box->green_max - box->green_min;
|
||||
blue_range = box->blue_max - box->blue_min;
|
||||
if (red_range == 0 && green_range == 0 && blue_range == 0)
|
||||
return (0);
|
||||
if (green_range >= red_range && green_range >= blue_range)
|
||||
channel = 1;
|
||||
else if (red_range >= blue_range)
|
||||
channel = 0;
|
||||
else
|
||||
channel = 2;
|
||||
|
||||
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++) {
|
||||
index = (red << 10)|(green << 5)|blue;
|
||||
if (channel == 0)
|
||||
levels[red] += hg[index].count;
|
||||
else if (channel == 1)
|
||||
levels[green] += hg[index].count;
|
||||
else
|
||||
levels[blue] += hg[index].count;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (channel == 0) {
|
||||
first = box->red_min;
|
||||
last = box->red_max;
|
||||
} else if (channel == 1) {
|
||||
first = box->green_min;
|
||||
last = box->green_max;
|
||||
} else {
|
||||
first = box->blue_min;
|
||||
last = box->blue_max;
|
||||
}
|
||||
for (level = first; level < last; level++) {
|
||||
count += levels[level];
|
||||
if (count >= box->count / 2)
|
||||
break;
|
||||
}
|
||||
/* Keep the maximum occupied level in the new box. */
|
||||
if (level == last)
|
||||
level--;
|
||||
memcpy(new, box, sizeof *new);
|
||||
if (channel == 0) {
|
||||
box->red_max = level;
|
||||
new->red_min = level + 1;
|
||||
} else if (channel == 1) {
|
||||
box->green_max = level;
|
||||
new->green_min = level + 1;
|
||||
} else {
|
||||
box->blue_max = level;
|
||||
new->blue_min = level + 1;
|
||||
}
|
||||
sixel_box_update(box, hg);
|
||||
sixel_box_update(new, hg);
|
||||
return (box->count != 0 && new->count != 0);
|
||||
}
|
||||
|
||||
/* Build an adaptive palette from an RGB histogram. */
|
||||
static u_int
|
||||
sixel_make_palette(struct sixel_hgram *hg,
|
||||
struct sixel_rgb *palette)
|
||||
{
|
||||
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;
|
||||
|
||||
memset(&boxes[0], 0, sizeof boxes[0]);
|
||||
boxes[0].red_max = boxes[0].green_max = boxes[0].blue_max =
|
||||
SIXEL_HISTOGRAM_LEVELS - 1;
|
||||
sixel_box_update(&boxes[0], hg);
|
||||
if (boxes[0].count == 0)
|
||||
return (0);
|
||||
|
||||
while (nboxes < SIXEL_PALETTE_SIZE) {
|
||||
best = nboxes;
|
||||
best_score = 0;
|
||||
for (i = 0; i < nboxes; i++) {
|
||||
box = &boxes[i];
|
||||
red_range = box->red_max - box->red_min;
|
||||
green_range = box->green_max - box->green_min;
|
||||
blue_range = box->blue_max - box->blue_min;
|
||||
score = (uint64_t)box->count *
|
||||
(red_range * red_range + green_range * green_range +
|
||||
blue_range * blue_range);
|
||||
if (score > best_score) {
|
||||
best = i;
|
||||
best_score = score;
|
||||
}
|
||||
}
|
||||
if (best == nboxes ||
|
||||
!sixel_box_split(&boxes[best], &new, hg))
|
||||
break;
|
||||
memcpy(&boxes[nboxes++], &new, sizeof new);
|
||||
}
|
||||
|
||||
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++) {
|
||||
index = (r << 10)|(g << 5)|b;
|
||||
red += hg[index].red;
|
||||
green += hg[index].green;
|
||||
blue += hg[index].blue;
|
||||
count += hg[index].count;
|
||||
}
|
||||
}
|
||||
}
|
||||
palette[i].red = (red + count / 2) / count;
|
||||
palette[i].green = (green + count / 2) / count;
|
||||
palette[i].blue = (blue + count / 2) / count;
|
||||
}
|
||||
return (nboxes);
|
||||
}
|
||||
|
||||
/* Find the closest adaptive palette entry for an RGB colour. */
|
||||
static u_int
|
||||
sixel_nearest_colour(struct sixel_rgb *palette, u_int ncolours,
|
||||
uint16_t *cache, u_int red, u_int green, u_int blue)
|
||||
{
|
||||
uint64_t distance, best_distance = UINT64_MAX;
|
||||
int dr, dg, db;
|
||||
u_int i, best = 0, index;
|
||||
|
||||
index = ((red >> 3) << 10)|((green >> 3) << 5)|(blue >> 3);
|
||||
if (cache[index] != UINT16_MAX)
|
||||
return (cache[index]);
|
||||
for (i = 0; i < ncolours; i++) {
|
||||
dr = (int)red - palette[i].red;
|
||||
dg = (int)green - palette[i].green;
|
||||
db = (int)blue - palette[i].blue;
|
||||
distance = 3ULL * dr * dr + 6ULL * dg * dg + db * db;
|
||||
if (distance < best_distance) {
|
||||
best = i;
|
||||
best_distance = distance;
|
||||
}
|
||||
}
|
||||
cache[index] = best;
|
||||
return (best);
|
||||
}
|
||||
|
||||
/* Clamp an RGB component to the valid range. */
|
||||
static u_int
|
||||
sixel_clamp_colour(int colour)
|
||||
{
|
||||
if (colour < 0)
|
||||
return (0);
|
||||
if (colour > 255)
|
||||
return (255);
|
||||
return (colour);
|
||||
}
|
||||
|
||||
/* Return a source pixel mapped to an output SIXEL pixel. */
|
||||
static const u_char *
|
||||
sixel_from_image_pixel(const struct sixel_source *source, u_int sourcex0,
|
||||
u_int sourcey0,
|
||||
u_int sourcewidth, u_int sourceheight, u_int sx, u_int sy, u_int x,
|
||||
u_int y)
|
||||
{
|
||||
u_int sourcex, sourcey;
|
||||
|
||||
sourcex = sourcex0 + (uint64_t)x * sourcewidth / sx;
|
||||
sourcey = sourcey0 + (uint64_t)y * sourceheight / sy;
|
||||
if (sourcex >= source->width)
|
||||
sourcex = source->width - 1;
|
||||
if (sourcey >= source->height)
|
||||
sourcey = source->height - 1;
|
||||
return (source->pixels + sourcey * source->stride + sourcex * 4);
|
||||
}
|
||||
|
||||
/* Render an image rectangle as an indexed SIXEL image. */
|
||||
static struct sixel_image *
|
||||
sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells_x,
|
||||
u_int cells_y, u_int xpixel, u_int ypixel)
|
||||
{
|
||||
struct sixel_image *si;
|
||||
struct sixel_source source;
|
||||
struct sixel_hgram *hg, *entry;
|
||||
struct sixel_rgb palette[SIXEL_PALETTE_SIZE];
|
||||
const u_char *pixel;
|
||||
uint16_t *cache;
|
||||
int *current, *next, *tmp;
|
||||
int red_error, green_error, blue_error, alpha_error;
|
||||
u_int x, y, sx, sy, index, error_index;
|
||||
u_int sourcex0, sourcey0, sourcewidth, sourceheight;
|
||||
u_int red, green, blue, alpha, colour, i, ncolours;
|
||||
uint64_t destination_width, destination_height;
|
||||
uint64_t content_width, content_height, x0, x1, y0, y1;
|
||||
|
||||
/* Work out the requested cell crop in destination pixel coordinates. */
|
||||
source.pixels = image_get_pixels(im, &source.stride, NULL);
|
||||
image_get_size(im, &source.width, &source.height);
|
||||
image_get_canvas_size(im, &source.canvas_width,
|
||||
&source.canvas_height);
|
||||
image_get_size_in_cells(im, &source.sx, &source.sy);
|
||||
destination_width = (uint64_t)source.sx * xpixel;
|
||||
destination_height = (uint64_t)source.sy * ypixel;
|
||||
if (destination_width > UINT_MAX || destination_height > UINT_MAX)
|
||||
return (NULL);
|
||||
content_width = ((uint64_t)source.width * destination_width +
|
||||
source.canvas_width - 1) / source.canvas_width;
|
||||
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. */
|
||||
x0 = (uint64_t)ox * xpixel;
|
||||
y0 = (uint64_t)oy * ypixel;
|
||||
x1 = ((uint64_t)ox + cells_x) * xpixel;
|
||||
y1 = ((uint64_t)oy + cells_y) * ypixel;
|
||||
if (x1 > content_width)
|
||||
x1 = content_width;
|
||||
if (y1 > content_height)
|
||||
y1 = content_height;
|
||||
if (x1 <= x0 || y1 <= y0)
|
||||
return (NULL);
|
||||
|
||||
/* The clipped output bounds determine the SIXEL image dimensions. */
|
||||
sx = x1 - x0;
|
||||
sy = y1 - y0;
|
||||
if (sx == 0 || sy == 0 || sx > SIXEL_WIDTH_LIMIT ||
|
||||
sy > SIXEL_HEIGHT_LIMIT)
|
||||
return (NULL);
|
||||
|
||||
/* Map the requested cell crop to the source image's pixel rectangle. */
|
||||
image_get_pixel_rect(im, ox, oy, cells_x, cells_y, &sourcex0,
|
||||
&sourcey0, &sourcewidth, &sourceheight);
|
||||
if (sourcewidth == 0 || sourceheight == 0)
|
||||
return (NULL);
|
||||
|
||||
/* Build an adaptive palette from the visible nontransparent pixels. */
|
||||
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, sourcex0, sourcey0,
|
||||
sourcewidth, sourceheight, sx, sy, x, y);
|
||||
if (pixel[3] == 0)
|
||||
continue;
|
||||
|
||||
/* Add this opaque pixel to its 5-bit RGB histogram bucket. */
|
||||
index = ((pixel[0] >> 3) << 10)|
|
||||
((pixel[1] >> 3) << 5)|(pixel[2] >> 3);
|
||||
entry = &hg[index];
|
||||
entry->count++;
|
||||
entry->red += pixel[0];
|
||||
entry->green += pixel[1];
|
||||
entry->blue += pixel[2];
|
||||
}
|
||||
}
|
||||
ncolours = sixel_make_palette(hg, palette);
|
||||
free(hg);
|
||||
if (ncolours == 0)
|
||||
return (NULL);
|
||||
|
||||
/* Create the indexed SIXEL image and convert its palette to SIXEL RGB. */
|
||||
si = xcalloc(1, sizeof *si);
|
||||
si->xpixel = xpixel;
|
||||
si->ypixel = ypixel;
|
||||
si->p2 = 1;
|
||||
si->set_ra = 1;
|
||||
si->ra_x = sx;
|
||||
si->ra_y = sy;
|
||||
si->ncolours = si->used_colours = ncolours;
|
||||
si->colours = xcalloc(si->ncolours, sizeof *si->colours);
|
||||
for (i = 0; i < si->ncolours; i++) {
|
||||
red = (palette[i].red * 100 + 127) / 255;
|
||||
green = (palette[i].green * 100 + 127) / 255;
|
||||
blue = (palette[i].blue * 100 + 127) / 255;
|
||||
si->colours[i] = (2U << 25)|(red << 16)|(green << 8)|blue;
|
||||
}
|
||||
|
||||
/* Floyd-Steinberg dither colour and alpha into the indexed image. */
|
||||
cache = xmalloc(SIXEL_HISTOGRAM_SIZE * sizeof *cache);
|
||||
memset(cache, 0xff, SIXEL_HISTOGRAM_SIZE * sizeof *cache);
|
||||
current = xcalloc(((size_t)sx + 2) * 4, sizeof *current);
|
||||
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, sourcex0, sourcey0,
|
||||
sourcewidth, sourceheight, sx, sy, x, y);
|
||||
error_index = (x + 1) * 4;
|
||||
/* SIXEL pixels are binary, so dither alpha separately. */
|
||||
alpha = sixel_clamp_colour((int)pixel[3] +
|
||||
current[error_index + 3] / 16);
|
||||
alpha_error = (int)alpha;
|
||||
if (alpha >= 128) {
|
||||
alpha_error -= 255;
|
||||
red = sixel_clamp_colour((int)pixel[0] +
|
||||
current[error_index] / 16);
|
||||
green = sixel_clamp_colour((int)pixel[1] +
|
||||
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);
|
||||
if (sixel_set_pixel(si, x, y, colour + 1) != 0)
|
||||
goto fail;
|
||||
|
||||
/* Calculate the RGB error introduced by palette quantization. */
|
||||
red_error = (int)red - palette[colour].red;
|
||||
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.
|
||||
*/
|
||||
current[error_index + 4] += red_error * 7;
|
||||
current[error_index + 5] += green_error * 7;
|
||||
current[error_index + 6] += blue_error * 7;
|
||||
next[error_index - 4] += red_error * 3;
|
||||
next[error_index - 3] += green_error * 3;
|
||||
next[error_index - 2] += blue_error * 3;
|
||||
next[error_index] += red_error * 5;
|
||||
next[error_index + 1] += green_error * 5;
|
||||
next[error_index + 2] += blue_error * 5;
|
||||
next[error_index + 4] += red_error;
|
||||
next[error_index + 5] += green_error;
|
||||
next[error_index + 6] += blue_error;
|
||||
}
|
||||
/* Diffuse alpha independently using the same kernel. */
|
||||
current[error_index + 7] += alpha_error * 7;
|
||||
next[error_index - 1] += alpha_error * 3;
|
||||
next[error_index + 3] += alpha_error * 5;
|
||||
next[error_index + 7] += alpha_error;
|
||||
}
|
||||
/* Advance to the next output row's accumulated error. */
|
||||
tmp = current;
|
||||
current = next;
|
||||
next = tmp;
|
||||
|
||||
/* Reuse the old row buffer to accumulate the row after that. */
|
||||
memset(next, 0, ((size_t)sx + 2) * 4 * sizeof *next);
|
||||
}
|
||||
|
||||
free(current);
|
||||
free(next);
|
||||
free(cache);
|
||||
return (si);
|
||||
|
||||
fail:
|
||||
/* Discard a partially built image after an allocation or size failure. */
|
||||
free(current);
|
||||
free(next);
|
||||
free(cache);
|
||||
sixel_free(si);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Return the SIXEL output cache for a terminal. */
|
||||
static struct sixel_output *
|
||||
sixel_get_output(struct tty *tty)
|
||||
{
|
||||
struct sixel_output *so = tty->image_data;
|
||||
|
||||
if (so == NULL) {
|
||||
so = xcalloc(1, sizeof *so);
|
||||
tty->image_data = so;
|
||||
}
|
||||
return (so);
|
||||
}
|
||||
|
||||
/* Return the memory used by an indexed SIXEL image. */
|
||||
static size_t
|
||||
sixel_image_size(struct sixel_image *si)
|
||||
{
|
||||
uint64_t size;
|
||||
|
||||
if ((uint64_t)si->x * si->y > SIZE_MAX / sizeof(uint16_t))
|
||||
return (0);
|
||||
size = (uint64_t)si->x * si->y * sizeof(uint16_t);
|
||||
if ((uint64_t)si->ncolours * sizeof *si->colours > SIZE_MAX - size)
|
||||
return (0);
|
||||
size += (uint64_t)si->ncolours * sizeof *si->colours;
|
||||
if (size > SIZE_MAX)
|
||||
return (0);
|
||||
return (size);
|
||||
}
|
||||
|
||||
/* Remove an image from the SIXEL output cache. */
|
||||
static void
|
||||
sixel_remove_cache(struct sixel_output *so, struct sixel_image_cache **pp)
|
||||
{
|
||||
struct sixel_image_cache *cache = *pp;
|
||||
|
||||
*pp = cache->next;
|
||||
so->size -= cache->size;
|
||||
sixel_free(cache->si);
|
||||
free(cache);
|
||||
}
|
||||
|
||||
/* Drop SIXEL cache entries whose source images have gone away. */
|
||||
static void
|
||||
sixel_collect_images(struct sixel_output *so)
|
||||
{
|
||||
struct sixel_image_cache **pp, *cache;
|
||||
|
||||
for (pp = &so->images; (cache = *pp) != NULL; ) {
|
||||
if (image_find(cache->server_id) == NULL)
|
||||
sixel_remove_cache(so, pp);
|
||||
else
|
||||
pp = &cache->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Free SIXEL output state for a terminal. */
|
||||
void
|
||||
sixel_free_output(struct tty *tty, __unused int send)
|
||||
{
|
||||
struct sixel_output *so = tty->image_data;
|
||||
struct sixel_image_cache *cache, *next;
|
||||
|
||||
if (so == NULL)
|
||||
return;
|
||||
for (cache = so->images; cache != NULL; cache = next) {
|
||||
next = cache->next;
|
||||
sixel_free(cache->si);
|
||||
free(cache);
|
||||
}
|
||||
free(so);
|
||||
tty->image_data = NULL;
|
||||
}
|
||||
|
||||
/* Render an image at a terminal's current pixel geometry. */
|
||||
static struct sixel_image *
|
||||
sixel_render_image(struct image *im, u_int xpixel, u_int ypixel)
|
||||
{
|
||||
struct sixel_image *original;
|
||||
u_int sx, sy;
|
||||
|
||||
image_get_size_in_cells(im, &sx, &sy);
|
||||
/* Preserve SIXEL's original palette and indexed pixels when possible. */
|
||||
original = image_get_sixel(im);
|
||||
if (original != NULL)
|
||||
return (sixel_scale(original, xpixel, ypixel, 0, 0, sx, sy, 1));
|
||||
return (sixel_from_image(im, 0, 0, sx, sy, xpixel, ypixel));
|
||||
}
|
||||
|
||||
/* Return a rendered image from the SIXEL output cache. */
|
||||
static struct sixel_image *
|
||||
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;
|
||||
|
||||
sixel_collect_images(so);
|
||||
for (cache = so->images; cache != NULL; cache = cache->next) {
|
||||
if (cache->server_id != image_get_id(im) ||
|
||||
cache->xpixel != tty->xpixel ||
|
||||
cache->ypixel != tty->ypixel)
|
||||
continue;
|
||||
cache->age = ++so->age;
|
||||
return (cache->si);
|
||||
}
|
||||
|
||||
si = sixel_render_image(im, tty->xpixel, tty->ypixel);
|
||||
if (si == NULL)
|
||||
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. */
|
||||
return (si);
|
||||
}
|
||||
while (so->size > IMAGE_SIZE_LIMIT - size) {
|
||||
oldest = NULL;
|
||||
for (pp = &so->images; (cache = *pp) != NULL;
|
||||
pp = &cache->next) {
|
||||
if (oldest == NULL || cache->age < (*oldest)->age)
|
||||
oldest = pp;
|
||||
}
|
||||
if (oldest == NULL)
|
||||
break;
|
||||
sixel_remove_cache(so, oldest);
|
||||
}
|
||||
cache = xcalloc(1, sizeof *cache);
|
||||
cache->server_id = image_get_id(im);
|
||||
cache->xpixel = tty->xpixel;
|
||||
cache->ypixel = tty->ypixel;
|
||||
cache->size = size;
|
||||
cache->age = ++so->age;
|
||||
cache->si = si;
|
||||
cache->next = so->images;
|
||||
so->images = cache;
|
||||
so->size += size;
|
||||
return (si);
|
||||
}
|
||||
|
||||
/* Return if a SIXEL image is held by the output cache. */
|
||||
static int
|
||||
sixel_image_is_cached(struct tty *tty, struct sixel_image *si)
|
||||
{
|
||||
struct sixel_output *so = tty->image_data;
|
||||
struct sixel_image_cache *cache;
|
||||
|
||||
if (so == NULL)
|
||||
return (0);
|
||||
for (cache = so->images; cache != NULL; cache = cache->next) {
|
||||
if (cache->si == si)
|
||||
return (1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Draw an image rectangle with SIXEL output. */
|
||||
void
|
||||
sixel_draw_rect(struct tty *tty, const struct image_rect *rectangle,
|
||||
__unused const struct tty_style_ctx *style_ctx)
|
||||
{
|
||||
struct sixel_image *si, *crop;
|
||||
char *data;
|
||||
size_t size;
|
||||
u_int source_x, source_y, width, height;
|
||||
u_int destination_x, destination_y;
|
||||
|
||||
si = sixel_get_image(tty, image_rect_get_image(rectangle));
|
||||
if (si == NULL)
|
||||
return;
|
||||
image_rect_get_coords(rectangle, &source_x, &source_y, &width,
|
||||
&height, &destination_x, &destination_y);
|
||||
crop = sixel_scale(si, tty->xpixel, tty->ypixel,
|
||||
source_x, source_y, width, height, 1);
|
||||
if (!sixel_image_is_cached(tty, si))
|
||||
sixel_free(si);
|
||||
if (crop == NULL)
|
||||
return;
|
||||
data = sixel_print(crop, NULL, &size);
|
||||
sixel_free(crop);
|
||||
if (data == NULL)
|
||||
return;
|
||||
tty_region_off(tty);
|
||||
tty_margin_off(tty);
|
||||
tty_cursor(tty, destination_x, destination_y);
|
||||
tty->flags |= TTY_NOBLOCK;
|
||||
tty_putn(tty, data, size, 0);
|
||||
tty_invalidate(tty);
|
||||
free(data);
|
||||
}
|
||||
|
||||
/* Convert a SIXEL image to a fallback screen. */
|
||||
struct screen *
|
||||
sixel_to_screen(struct sixel_image *si)
|
||||
{
|
||||
|
||||
@@ -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,205 +19,740 @@
|
||||
|
||||
#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
|
||||
/* 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;
|
||||
};
|
||||
|
||||
static void printflike(3, 4)
|
||||
image_log(struct image *im, const char* from, const char* fmt, ...)
|
||||
/* Half blocks, quadrants and sextants all divide evenly into a 2 by 6 grid. */
|
||||
#define IMAGE_SAMPLE_COLUMNS 2
|
||||
#define IMAGE_SAMPLE_ROWS 6
|
||||
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_short grid_id;
|
||||
u_int references;
|
||||
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; /* lazily generated text samples */
|
||||
struct image_fallback_data *fallback_data;
|
||||
|
||||
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;
|
||||
u_int source_x;
|
||||
u_int source_y;
|
||||
u_int width;
|
||||
u_int height;
|
||||
u_int destination_x;
|
||||
u_int destination_y;
|
||||
};
|
||||
|
||||
static struct images images = RB_INITIALIZER(&images);
|
||||
static u_int image_next_id;
|
||||
static u_short image_next_grid_id;
|
||||
static struct image *image_grid_ids[USHRT_MAX + 1];
|
||||
|
||||
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_sixel = {
|
||||
"sixel", IMAGE_BACKEND_GRAPHICAL|IMAGE_BACKEND_TEMPORAL,
|
||||
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)
|
||||
{
|
||||
va_list ap;
|
||||
char s[128];
|
||||
|
||||
if (log_get_level() == 0)
|
||||
return;
|
||||
|
||||
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);
|
||||
if (tty->term != NULL && tty->term->flags & TERM_SIXEL &&
|
||||
tty->xpixel != 0 && tty->ypixel != 0)
|
||||
return (&image_backend_sixel);
|
||||
return (&image_backend_fallback);
|
||||
}
|
||||
|
||||
static void
|
||||
image_free(struct image *im)
|
||||
/* Update a terminal's image backend after its capabilities change. */
|
||||
void
|
||||
image_tty_update(struct tty *tty)
|
||||
{
|
||||
image_log(im, __func__, NULL);
|
||||
const struct image_backend *backend;
|
||||
|
||||
TAILQ_REMOVE(&all_images, im, all_entry);
|
||||
all_images_count--;
|
||||
backend = image_tty_find_backend(tty);
|
||||
if (tty->image_backend == backend)
|
||||
return;
|
||||
|
||||
TAILQ_REMOVE(im->list, im, entry);
|
||||
sixel_free(im->data);
|
||||
free(im->fallback);
|
||||
free(im);
|
||||
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);
|
||||
}
|
||||
|
||||
/* Return the flags for a terminal's image backend. */
|
||||
int
|
||||
image_free_all(struct screen *s)
|
||||
image_backend_flags(struct tty *tty)
|
||||
{
|
||||
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);
|
||||
image_tty_update(tty);
|
||||
return (tty->image_backend->flags);
|
||||
}
|
||||
|
||||
/* Create text placeholder for an image. */
|
||||
/* 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);
|
||||
|
||||
/* Average a rectangle of image pixels into one fallback sample. */
|
||||
static void
|
||||
image_fallback(char **ret, u_int sx, u_int sy)
|
||||
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)
|
||||
{
|
||||
char *buf, *label;
|
||||
u_int py, size, lsize;
|
||||
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;
|
||||
|
||||
/* 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;
|
||||
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;
|
||||
|
||||
/* 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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remaining lines. */
|
||||
for (py = 1; py < sy; py++) {
|
||||
memset(buf, '+', sx);
|
||||
buf += sx;
|
||||
snprintf(buf, 3, "\r\n");
|
||||
buf += 2;
|
||||
}
|
||||
|
||||
free(label);
|
||||
if (count == 0)
|
||||
return;
|
||||
sample->red = red / count;
|
||||
sample->green = green / count;
|
||||
sample->blue = blue / count;
|
||||
sample->alpha = alpha / count;
|
||||
sample->brightness = brightness / count;
|
||||
}
|
||||
|
||||
struct image*
|
||||
image_store(struct screen *s, struct sixel_image *si)
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* 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 an image's cached fallback rendering data. */
|
||||
struct image_fallback_data *
|
||||
image_get_fallback_data(const struct image *im)
|
||||
{
|
||||
return (im->fallback_data);
|
||||
}
|
||||
|
||||
/* Associate cached fallback rendering data with an image. */
|
||||
void
|
||||
image_set_fallback_data(struct image *im, struct image_fallback_data *data)
|
||||
{
|
||||
im->fallback_data = data;
|
||||
}
|
||||
|
||||
/* Return the image for a drawing rectangle. */
|
||||
struct image *
|
||||
image_rect_get_image(const struct image_rect *rectangle)
|
||||
{
|
||||
return (rectangle->image);
|
||||
}
|
||||
|
||||
/* 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->width;
|
||||
*height = rectangle->height;
|
||||
*destination_x = rectangle->destination_x;
|
||||
*destination_y = rectangle->destination_y;
|
||||
}
|
||||
|
||||
/* Create and register an immutable image. */
|
||||
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;
|
||||
u_int i;
|
||||
|
||||
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)
|
||||
return (NULL);
|
||||
if (sx > USHRT_MAX || sy > USHRT_MAX)
|
||||
return (NULL);
|
||||
for (i = 0; i < USHRT_MAX; i++) {
|
||||
if (++image_next_grid_id == 0)
|
||||
image_next_grid_id++;
|
||||
if (image_grid_ids[image_next_grid_id] == NULL)
|
||||
break;
|
||||
}
|
||||
if (i == USHRT_MAX)
|
||||
return (NULL);
|
||||
|
||||
im = xcalloc(1, sizeof *im);
|
||||
im->s = s;
|
||||
im->data = si;
|
||||
do {
|
||||
if (++image_next_id == 0)
|
||||
image_next_id++;
|
||||
im->id = image_next_id;
|
||||
} while (image_find(im->id) != NULL);
|
||||
|
||||
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->references = 1;
|
||||
im->grid_id = image_next_grid_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 = (size_t)width * 4;
|
||||
im->size = im->stride * height;
|
||||
im->pixels = pixels;
|
||||
|
||||
RB_INSERT(images, &images, im);
|
||||
image_grid_ids[im->grid_id] = 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);
|
||||
}
|
||||
|
||||
int
|
||||
image_check_line(struct screen *s, u_int py, u_int ny)
|
||||
/* Return the compact grid ID for an image. */
|
||||
u_short
|
||||
image_get_grid_id(u_int id)
|
||||
{
|
||||
struct image *im, *im1;
|
||||
int redraw = 0, in;
|
||||
struct image *im = image_find(id);
|
||||
|
||||
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 (im == NULL)
|
||||
return (0);
|
||||
return (im->grid_id);
|
||||
}
|
||||
|
||||
/* Return the server image ID for a compact grid ID. */
|
||||
u_int
|
||||
image_get_id_by_grid_id(u_short grid_id)
|
||||
{
|
||||
struct image *im;
|
||||
|
||||
if (grid_id == 0 || (im = image_grid_ids[grid_id]) == NULL)
|
||||
return (0);
|
||||
return (im->id);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
image_grid_ids[im->grid_id] = NULL;
|
||||
free(im->pixels);
|
||||
if (im->sixel != NULL)
|
||||
sixel_free(im->sixel);
|
||||
free(im->cells);
|
||||
image_free_fallback(im);
|
||||
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]);
|
||||
}
|
||||
|
||||
/* Return the brightness of an image cell. */
|
||||
u_char
|
||||
image_get_brightness(struct image *im, u_int x, u_int y)
|
||||
{
|
||||
const struct image_cell *cell;
|
||||
|
||||
cell = image_get_cell(im, x, y);
|
||||
if (cell == NULL)
|
||||
return (0);
|
||||
return (cell->whole.brightness);
|
||||
}
|
||||
|
||||
/* Average part of an image cell for fallback rendering. */
|
||||
void
|
||||
image_get_cell_average(struct image *im, u_int x, u_int y, u_int part_x,
|
||||
u_int part_y, u_int parts_x, u_int parts_y, u_char *red, u_char *green,
|
||||
u_char *blue)
|
||||
{
|
||||
const struct image_cell *cell;
|
||||
u_int ix, iy, x0, x1, y0, y1, count;
|
||||
u_int value_red, value_green, value_blue;
|
||||
|
||||
*red = *green = *blue = 0;
|
||||
if (parts_x == 0 || parts_y == 0 || part_x >= parts_x ||
|
||||
part_y >= parts_y)
|
||||
return;
|
||||
cell = image_get_cell(im, x, y);
|
||||
if (cell == NULL)
|
||||
return;
|
||||
x0 = part_x * IMAGE_SAMPLE_COLUMNS / parts_x;
|
||||
x1 = (part_x + 1) * IMAGE_SAMPLE_COLUMNS / parts_x;
|
||||
y0 = part_y * IMAGE_SAMPLE_ROWS / parts_y;
|
||||
y1 = (part_y + 1) * IMAGE_SAMPLE_ROWS / parts_y;
|
||||
value_red = value_green = value_blue = count = 0;
|
||||
for (iy = y0; iy < y1; iy++) {
|
||||
for (ix = x0; ix < x1; ix++) {
|
||||
value_red += cell->samples[iy][ix].red;
|
||||
value_green += cell->samples[iy][ix].green;
|
||||
value_blue += cell->samples[iy][ix].blue;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count == 0)
|
||||
return;
|
||||
*red = value_red / count;
|
||||
*green = value_green / count;
|
||||
*blue = value_blue / count;
|
||||
}
|
||||
|
||||
/* Get the terminal cell used to draw an image marker. */
|
||||
int
|
||||
image_get_draw_cell(struct tty *tty, const struct grid_cell *gc,
|
||||
struct grid_cell *out, const struct tty_style_ctx *style_ctx)
|
||||
{
|
||||
struct image *im = image_find(gc->image_id);
|
||||
|
||||
if (image_backend_flags(tty) & IMAGE_BACKEND_GRAPHICAL) {
|
||||
memcpy(out, gc, sizeof *out);
|
||||
out->flags &= ~(GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED|
|
||||
GRID_FLAG_SELECTED);
|
||||
return (1);
|
||||
}
|
||||
if (gc->flags & GRID_FLAG_IMAGE_DAMAGED) {
|
||||
memcpy(out, gc, sizeof *out);
|
||||
out->flags &= ~(GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED);
|
||||
return (0);
|
||||
}
|
||||
image_get_fallback_cell(tty, im, gc->image_x, gc->image_y, gc, out,
|
||||
style_ctx);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Store an image marker in a grid cell. */
|
||||
void
|
||||
image_set_cell(struct grid_cell *gc, struct image *im, u_int x, u_int y)
|
||||
{
|
||||
/* Keep the cell contents as the underlay for transparent pixels. */
|
||||
gc->flags &= ~GRID_FLAG_IMAGE_DAMAGED;
|
||||
gc->flags |= GRID_FLAG_IMAGE;
|
||||
gc->image_id = im->id;
|
||||
gc->image_x = x;
|
||||
gc->image_y = y;
|
||||
}
|
||||
|
||||
/* Convert a cell-aligned image rectangle into source 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);
|
||||
}
|
||||
|
||||
/* Return if a screen area contains image markers. */
|
||||
static int
|
||||
image_check_area(struct screen *s, u_int px, u_int py, u_int nx, u_int ny)
|
||||
{
|
||||
struct image *im, *im1;
|
||||
int redraw = 0, in;
|
||||
struct grid_cell gc;
|
||||
u_int x, y, ex, ey;
|
||||
u_int sx = screen_size_x(s), sy = screen_size_y(s);
|
||||
|
||||
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;
|
||||
if (px >= sx || py >= sy || nx == 0 || ny == 0)
|
||||
return (0);
|
||||
if (nx > sx - px)
|
||||
ex = sx;
|
||||
else
|
||||
ex = px + nx;
|
||||
if (ny > sy - py)
|
||||
ey = sy;
|
||||
else
|
||||
ey = py + ny;
|
||||
for (y = py; y < ey; y++) {
|
||||
for (x = px; x < ex; x++) {
|
||||
grid_view_get_cell(s->grid, x, y, &gc);
|
||||
if (gc.flags & GRID_FLAG_IMAGE)
|
||||
return (1);
|
||||
}
|
||||
}
|
||||
return (redraw);
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
image_scroll_up(struct screen *s, u_int lines)
|
||||
/* Redraw image markers 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)
|
||||
{
|
||||
struct image *im, *im1;
|
||||
int redraw = 0;
|
||||
u_int sx, sy;
|
||||
struct sixel_image *new;
|
||||
|
||||
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;
|
||||
if (ctx->wp != NULL && image_check_area(ctx->s, px, py, nx, ny))
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
}
|
||||
|
||||
/* Redraw all image markers 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 the graphical image marker runs in one visible scene span. */
|
||||
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, const struct tty_style_ctx *style_ctx)
|
||||
{
|
||||
const struct image_backend *backend;
|
||||
struct image_rect rectangle;
|
||||
struct grid_cell gc, next;
|
||||
struct image *im;
|
||||
u_int i, run;
|
||||
|
||||
image_tty_update(tty);
|
||||
backend = tty->image_backend;
|
||||
if (~backend->flags & IMAGE_BACKEND_GRAPHICAL)
|
||||
return;
|
||||
|
||||
for (i = 0; i < nx; i += run) {
|
||||
grid_view_get_cell(s->grid, px + i, py, &gc);
|
||||
if (~gc.flags & GRID_FLAG_IMAGE) {
|
||||
run = 1;
|
||||
continue;
|
||||
}
|
||||
if ((backend->flags & IMAGE_BACKEND_TEMPORAL) &&
|
||||
(gc.flags & GRID_FLAG_IMAGE_DAMAGED)) {
|
||||
run = 1;
|
||||
continue;
|
||||
}
|
||||
if (im->py + im->sy <= lines) {
|
||||
image_log(im, __func__, "2, lines=%u", lines);
|
||||
image_free(im);
|
||||
redraw = 1;
|
||||
im = image_find(gc.image_id);
|
||||
if (im == NULL) {
|
||||
run = 1;
|
||||
continue;
|
||||
}
|
||||
for (run = 1; i + run < nx; run++) {
|
||||
grid_view_get_cell(s->grid, px + i + run, py, &next);
|
||||
if (~next.flags & GRID_FLAG_IMAGE ||
|
||||
((backend->flags & IMAGE_BACKEND_TEMPORAL) &&
|
||||
(next.flags & GRID_FLAG_IMAGE_DAMAGED)) ||
|
||||
next.image_id != gc.image_id ||
|
||||
next.image_y != gc.image_y ||
|
||||
next.image_x != gc.image_x + run)
|
||||
break;
|
||||
}
|
||||
sx = im->sx;
|
||||
sy = (im->py + im->sy) - lines;
|
||||
image_log(im, __func__, "3, lines=%u, sy=%u", lines, sy);
|
||||
rectangle.image = im;
|
||||
memcpy(&rectangle.cell, &gc, sizeof rectangle.cell);
|
||||
rectangle.source_x = gc.image_x;
|
||||
rectangle.source_y = gc.image_y;
|
||||
rectangle.width = run;
|
||||
rectangle.height = 1;
|
||||
rectangle.destination_x = atx + i;
|
||||
rectangle.destination_y = aty;
|
||||
backend->draw_rect(tty, &rectangle, style_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
new = sixel_scale(im->data, 0, 0, 0, im->sy - sy, sx, sy, 1);
|
||||
sixel_free(im->data);
|
||||
im->data = new;
|
||||
/*
|
||||
* Put image marker cells at the cursor. The pixel object is immutable; only
|
||||
* the marker rectangle is clipped to the available pane cells.
|
||||
*/
|
||||
void
|
||||
image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg)
|
||||
{
|
||||
struct screen *s = ctx->s;
|
||||
struct grid *gd = s->grid;
|
||||
struct grid_cell gc;
|
||||
u_int cx = s->cx, cy = s->cy;
|
||||
u_int x, y, sx, sy, lines;
|
||||
|
||||
im->py = 0;
|
||||
sixel_size_in_cells(im->data, &im->sx, &im->sy);
|
||||
image_get_size_in_cells(im, &sx, &sy);
|
||||
if (sx > screen_size_x(s) - cx)
|
||||
sx = screen_size_x(s) - cx;
|
||||
if (sy > screen_size_y(s) - 1)
|
||||
sy = screen_size_y(s) - 1;
|
||||
if (sx == 0 || sy == 0)
|
||||
return;
|
||||
|
||||
free(im->fallback);
|
||||
image_fallback(&im->fallback, im->sx, im->sy);
|
||||
redraw = 1;
|
||||
if (screen_size_y(s) - cy <= sy) {
|
||||
lines = sy - (screen_size_y(s) - cy) + 1;
|
||||
screen_write_scrollup(ctx, lines, bg);
|
||||
if (lines > cy)
|
||||
screen_write_cursormove(ctx, -1, 0, 0);
|
||||
else
|
||||
screen_write_cursormove(ctx, -1, cy - lines, 0);
|
||||
cy = s->cy;
|
||||
}
|
||||
|
||||
for (y = 0; y < sy; y++) {
|
||||
for (x = 0; x < sx; x++) {
|
||||
grid_view_get_cell(gd, cx + x, cy + y, &gc);
|
||||
image_set_cell(&gc, im, x, y);
|
||||
grid_view_set_cell(gd, cx + x, cy + y, &gc);
|
||||
}
|
||||
}
|
||||
return (redraw);
|
||||
image_redraw_area(ctx, cx, cy, sx, sy);
|
||||
screen_write_cursormove(ctx, 0, cy + sy, 0);
|
||||
}
|
||||
|
||||
@@ -1583,7 +1583,7 @@ input_csi_dispatch(struct input_ctx *ictx)
|
||||
case -1:
|
||||
break;
|
||||
case 0:
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
input_reply(ictx, 1, "\033[?1;2;4c");
|
||||
#else
|
||||
input_reply(ictx, 1, "\033[?1;2c");
|
||||
@@ -2101,7 +2101,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx)
|
||||
static void
|
||||
input_csi_dispatch_sm_graphics(__unused struct input_ctx *ictx)
|
||||
{
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
int n, m, o;
|
||||
|
||||
if (ictx->param_list_len > 3)
|
||||
@@ -2626,7 +2626,7 @@ input_dcs_dispatch(struct input_ctx *ictx)
|
||||
const char prefix[] = "tmux;";
|
||||
const u_int prefixlen = (sizeof prefix) - 1;
|
||||
long long allow_passthrough = 0;
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct window *w;
|
||||
struct sixel_image *si;
|
||||
int p2;
|
||||
@@ -2642,7 +2642,7 @@ input_dcs_dispatch(struct input_ctx *ictx)
|
||||
return (0);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (wp != NULL && buf[0] == 'q' && ictx->interm_len == 0) {
|
||||
w = wp->window;
|
||||
if (input_split(ictx) != 0)
|
||||
|
||||
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Grid-resident SIXEL image markers: history, copy, overwrite and fallback.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Limage$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Limage-client$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -x 20 -y 8 "
|
||||
printf '\033Pq\"1;1;8;16#0;2;100;100;100#0~~~~~~~~\044-~~~~~~~~\033\\'
|
||||
i=0
|
||||
while [ \$i -lt 10 ]; do printf '\nline-%d' \$i; i=\$((i + 1)); done
|
||||
sleep 10"
|
||||
|
||||
sleep 1
|
||||
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
|
||||
|
||||
# Images scroll as grid cells, while capture output contains ordinary spaces.
|
||||
[ "$($TMUX display-message -p '#{history_size}')" -gt 0 ] || exit 1
|
||||
$TMUX capture-pane -pS- >$TMP || exit 1
|
||||
grep 'Pq' $TMP >/dev/null && exit 1
|
||||
|
||||
# Copy mode duplicates the backing grid, including image marker references.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX capture-pane -p >$TMP || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
# Ordinary text overwrites marker cells through the normal grid write path.
|
||||
$TMUX new-window -d "
|
||||
printf '\033Pq\"1;1;8;16#0;2;100;100;100#0~~~~~~~~\044-~~~~~~~~\033\\'
|
||||
printf '\033[HXY'
|
||||
sleep 10"
|
||||
sleep 1
|
||||
[ "$($TMUX capture-pane -pt:1 -S0 -E0)" = "XY" ] || exit 1
|
||||
|
||||
# A 26-pixel raster occupies two default 16-pixel cells, but the final cell
|
||||
# remains only partially filled instead of stretching the raster to 32 pixels.
|
||||
# A client without an image feature exposes this in the sampled text backend.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 new-session -d -x 10 -y 4 "
|
||||
printf '\033Pq\"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -as terminal-features \
|
||||
',*:image-quadrants:image-sextants' || exit 1
|
||||
$TMUX new-session -d -x 10 -y 4 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX send-keys -l "$TMUX2 attach-session" || exit 1
|
||||
$TMUX send-keys Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX2 list-clients -F '#{client_termfeatures}' | \
|
||||
grep -q 'image-quadrants.*image-sextants' || exit 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
[ -n "$(sed -n 1p $TMP)" ] || exit 1
|
||||
|
||||
# Selection redraws must leave fallback image cells visible.
|
||||
$TMUX2 copy-mode || exit 1
|
||||
$TMUX2 send-keys -X history-top || exit 1
|
||||
$TMUX2 send-keys -X start-of-line || exit 1
|
||||
$TMUX2 send-keys -X begin-selection || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
|
||||
[ -n "$(sed -n 1p $TMP)" ] || exit 1
|
||||
|
||||
# Image marker rows remain cell-aligned when a narrower terminal causes text
|
||||
# reflow. The second image cell is clipped, not moved to the following row.
|
||||
$TMUX2 send-keys -X cancel || exit 1
|
||||
$TMUX2 new-window -d "
|
||||
printf '\033Pq\"1;1;26;26#0;2;100;100;100#0!26~-!26~-!26~-!26~-!26B\033\\'
|
||||
sleep 10" || exit 1
|
||||
$TMUX2 select-window -t:1 || exit 1
|
||||
sleep 1
|
||||
$TMUX resize-window -x 1 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ -n "$(sed -n 1p $TMP)" ] || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
$TMUX resize-window -x 10 -y 4 || exit 1
|
||||
sleep 1
|
||||
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
|
||||
[ "$(sed -n 1p $TMP | wc -c)" = 61 ] || exit 1
|
||||
[ -z "$(sed -n 2p $TMP)" ] || exit 1
|
||||
|
||||
exit 0
|
||||
+3
-9
@@ -1125,6 +1125,9 @@ redraw_draw_pane_span(struct redraw_draw_ctx *dctx,
|
||||
px = span->data.p.px + (x - span->x);
|
||||
py = span->data.p.py;
|
||||
tty_draw_line(tty, s, px, py, n, x, y, &style_ctx);
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_draw_line(tty, s, px, py, n, x, y, &style_ctx);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Get default border style for spans without a pane. */
|
||||
@@ -1817,15 +1820,6 @@ redraw_draw(struct client *c, struct window_pane *wp, int flags)
|
||||
tty_reset(tty);
|
||||
tty_sync_end(tty);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (wp != NULL)
|
||||
tty_draw_images(c, wp);
|
||||
else {
|
||||
TAILQ_FOREACH(loop, &scene->w->panes, entry)
|
||||
tty_draw_images(c, loop);
|
||||
}
|
||||
#endif
|
||||
|
||||
log_debug("%s: finished @%u redraw", c->name, scene->w->id);
|
||||
}
|
||||
|
||||
|
||||
+60
-105
@@ -1308,9 +1308,8 @@ screen_write_alignmenttest(struct screen_write_ctx *ctx)
|
||||
memcpy(&gc, &grid_default_cell, sizeof gc);
|
||||
utf8_set(&gc.data, 'E');
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_all(ctx);
|
||||
#endif
|
||||
|
||||
for (yy = 0; yy < screen_size_y(s); yy++) {
|
||||
@@ -1355,9 +1354,8 @@ screen_write_insertcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 1);
|
||||
@@ -1396,9 +1394,8 @@ screen_write_deletecharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 1);
|
||||
@@ -1437,9 +1434,8 @@ screen_write_clearcharacter(struct screen_write_ctx *ctx, u_int nx, u_int bg)
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 1);
|
||||
@@ -1472,9 +1468,8 @@ screen_write_insertline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
if (ny == 0)
|
||||
ny = 1;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
|
||||
#endif
|
||||
|
||||
if (s->cy < s->rupper || s->cy > s->rlower) {
|
||||
@@ -1540,9 +1535,8 @@ screen_write_deleteline(struct screen_write_ctx *ctx, u_int ny, u_int bg)
|
||||
if (ny == 0)
|
||||
ny = 1;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
|
||||
#endif
|
||||
|
||||
if (s->cy < s->rupper || s->cy > s->rlower) {
|
||||
@@ -1613,9 +1607,8 @@ screen_write_clearline(struct screen_write_ctx *ctx, u_int bg)
|
||||
if (gl->cellsize == 0 && COLOUR_DEFAULT(bg))
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#endif
|
||||
|
||||
flags = gl->flags & GRID_LINE_OSC133_FLAGS;
|
||||
@@ -1652,9 +1645,8 @@ screen_write_clearendofline(struct screen_write_ctx *ctx, u_int bg)
|
||||
if (s->cx > sx - 1 || (s->cx >= gl->cellsize && COLOUR_DEFAULT(bg)))
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#endif
|
||||
|
||||
grid_view_clear(s->grid, s->cx, s->cy, sx - s->cx, 1, bg);
|
||||
@@ -1679,9 +1671,8 @@ screen_write_clearstartofline(struct screen_write_ctx *ctx, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), 1);
|
||||
#endif
|
||||
|
||||
if (s->cx > sx - 1)
|
||||
@@ -1733,9 +1724,8 @@ screen_write_reverseindex(struct screen_write_ctx *ctx, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_all(ctx);
|
||||
#endif
|
||||
|
||||
grid_view_scroll_region_down(s->grid, s->rupper, s->rlower, bg);
|
||||
@@ -1785,9 +1775,6 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
|
||||
struct screen *s = ctx->s;
|
||||
struct grid *gd = s->grid;
|
||||
struct grid_line *gl;
|
||||
#ifdef ENABLE_SIXEL
|
||||
int redraw = 0;
|
||||
#endif
|
||||
u_int rupper = s->rupper, rlower = s->rlower;
|
||||
|
||||
gl = grid_get_line(gd, gd->hsize + s->cy);
|
||||
@@ -1808,13 +1795,12 @@ screen_write_linefeed(struct screen_write_ctx *ctx, int wrapped, u_int bg)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (rlower == screen_size_y(s) - 1)
|
||||
redraw = image_scroll_up(s, 1);
|
||||
image_redraw_scroll(ctx, 1);
|
||||
else
|
||||
redraw = image_check_line(s, rupper, rlower - rupper);
|
||||
if (redraw && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
image_redraw_area(ctx, 0, rupper, screen_size_x(s),
|
||||
rlower - rupper);
|
||||
#endif
|
||||
|
||||
grid_view_scroll_region_up(gd, s->rupper, s->rlower, bg);
|
||||
@@ -1840,9 +1826,8 @@ screen_write_scrollup(struct screen_write_ctx *ctx, u_int lines, u_int bg)
|
||||
ctx->bg = bg;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_scroll_up(s, lines) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_scroll(ctx, lines);
|
||||
#endif
|
||||
|
||||
for (i = 0; i < lines; i++) {
|
||||
@@ -1869,9 +1854,8 @@ screen_write_scrolldown(struct screen_write_ctx *ctx, u_int lines, u_int bg)
|
||||
else if (lines > s->rlower - s->rupper + 1)
|
||||
lines = s->rlower - s->rupper + 1;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_all(ctx);
|
||||
#endif
|
||||
|
||||
for (i = 0; i < lines; i++)
|
||||
@@ -1910,9 +1894,8 @@ screen_write_clearendofscreen(struct screen_write_ctx *ctx, u_int bg)
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *ri;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, s->cy, sy - s->cy) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, s->cy, screen_size_x(s), sy - s->cy);
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
@@ -1991,9 +1974,8 @@ screen_write_clearstartofscreen(struct screen_write_ctx *ctx, u_int bg)
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *ri;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_line(s, 0, s->cy - 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, 0, 0, screen_size_x(s), s->cy + 1);
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
@@ -2064,9 +2046,8 @@ screen_write_clearscreen(struct screen_write_ctx *ctx, u_int bg)
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *ri;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_free_all(s) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_all(ctx);
|
||||
#endif
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 1, 1);
|
||||
@@ -2531,9 +2512,8 @@ screen_write_collect_end(struct screen_write_ctx *ctx)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
if (image_check_area(s, s->cx, s->cy, ci->used, 1) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_redraw_area(ctx, s->cx, s->cy, ci->used, 1);
|
||||
#endif
|
||||
|
||||
grid_view_set_cells(s->grid, s->cx, s->cy, &ci->gc, cl->data + ci->x,
|
||||
@@ -2627,6 +2607,9 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
struct grid_line *gl;
|
||||
struct grid_cell_entry *gce;
|
||||
struct grid_cell tmp_gc, now_gc;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct grid_cell image_gc;
|
||||
#endif
|
||||
struct tty_ctx ttyctx;
|
||||
u_int sx = screen_size_x(s), sy = screen_size_y(s);
|
||||
u_int width = ud->width, xx, not_wrap, i, n, vis;
|
||||
@@ -2671,6 +2654,20 @@ screen_write_cell(struct screen_write_ctx *ctx, const struct grid_cell *gc)
|
||||
return;
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 0);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Update the text underlay without removing an image placement. */
|
||||
grid_view_get_cell(gd, s->cx, s->cy, &now_gc);
|
||||
if ((now_gc.flags & GRID_FLAG_IMAGE) &&
|
||||
(~gc->flags & GRID_FLAG_IMAGE)) {
|
||||
image_redraw_area(ctx, s->cx, s->cy, width, 1);
|
||||
memcpy(&image_gc, gc, sizeof image_gc);
|
||||
image_gc.flags |= GRID_FLAG_IMAGE|GRID_FLAG_IMAGE_DAMAGED;
|
||||
image_gc.image_id = now_gc.image_id;
|
||||
image_gc.image_x = now_gc.image_x;
|
||||
image_gc.image_y = now_gc.image_y;
|
||||
gc = &image_gc;
|
||||
}
|
||||
#endif
|
||||
/* Handle overwriting of UTF-8 characters. */
|
||||
gl = grid_get_line(s->grid, s->grid->hsize + s->cy);
|
||||
if (gl->flags & GRID_LINE_EXTENDED) {
|
||||
@@ -3040,63 +3037,21 @@ screen_write_rawstring(struct screen_write_ctx *ctx, u_char *str, u_int len,
|
||||
tty_write(tty_cmd_rawstring, &ttyctx);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Write a SIXEL image. */
|
||||
void
|
||||
screen_write_sixelimage(struct screen_write_ctx *ctx, struct sixel_image *si,
|
||||
u_int bg)
|
||||
{
|
||||
struct screen *s = ctx->s;
|
||||
struct grid *gd = s->grid;
|
||||
struct tty_ctx ttyctx;
|
||||
u_int x, y, sx, sy, cx = s->cx, cy = s->cy, i, lines;
|
||||
struct sixel_image *new;
|
||||
struct image *im;
|
||||
|
||||
if (screen_size_y(s) == 1)
|
||||
return;
|
||||
|
||||
sixel_size_in_cells(si, &x, &y);
|
||||
if (x > screen_size_x(s) || y > screen_size_y(s) - 1) {
|
||||
if (x > screen_size_x(s) - cx)
|
||||
sx = screen_size_x(s) - cx;
|
||||
else
|
||||
sx = x;
|
||||
if (y > screen_size_y(s) - 1)
|
||||
sy = screen_size_y(s) - 1;
|
||||
else
|
||||
sy = y;
|
||||
new = sixel_scale(si, 0, 0, 0, y - sy, sx, sy, 1);
|
||||
im = sixel_to_image(si);
|
||||
if (im == NULL) {
|
||||
sixel_free(si);
|
||||
if (new == NULL)
|
||||
return;
|
||||
sixel_size_in_cells(new, &x, &y);
|
||||
si = new;
|
||||
return;
|
||||
}
|
||||
|
||||
sy = screen_size_y(s) - cy;
|
||||
if (sy <= y) {
|
||||
lines = y - sy + 1;
|
||||
if (image_scroll_up(s, lines) && ctx->wp != NULL)
|
||||
ctx->wp->flags |= PANE_REDRAW;
|
||||
for (i = 0; i < lines; i++) {
|
||||
grid_view_scroll_region_up(gd, 0, screen_size_y(s) - 1,
|
||||
bg);
|
||||
screen_write_collect_scroll(ctx, bg);
|
||||
}
|
||||
ctx->scrolled += lines;
|
||||
if (lines > cy)
|
||||
screen_write_cursormove(ctx, -1, 0, 0);
|
||||
else
|
||||
screen_write_cursormove(ctx, -1, cy - lines, 0);
|
||||
}
|
||||
screen_write_collect_flush(ctx, 0, __func__);
|
||||
|
||||
screen_write_initctx(ctx, &ttyctx, 0, 0);
|
||||
ttyctx.image = image_store(s, si);
|
||||
|
||||
tty_write(tty_cmd_sixelimage, &ttyctx);
|
||||
|
||||
screen_write_cursormove(ctx, 0, cy + y, 0);
|
||||
image_write(ctx, im, bg);
|
||||
image_free(image_get_id(im));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -93,11 +93,6 @@ screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit)
|
||||
s->tabs = NULL;
|
||||
s->sel = NULL;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
TAILQ_INIT(&s->images);
|
||||
TAILQ_INIT(&s->saved_images);
|
||||
#endif
|
||||
|
||||
s->write_list = NULL;
|
||||
s->hyperlinks = NULL;
|
||||
|
||||
@@ -131,11 +126,6 @@ screen_reinit(struct screen *s, int check)
|
||||
|
||||
screen_clear_selection(s);
|
||||
screen_free_titles(s);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
image_free_all(s);
|
||||
#endif
|
||||
|
||||
screen_set_progress_bar(s, PROGRESS_BAR_HIDDEN, 0);
|
||||
screen_reset_hyperlinks(s);
|
||||
}
|
||||
@@ -154,10 +144,6 @@ screen_reset_hyperlinks(struct screen *s)
|
||||
void
|
||||
screen_free(struct screen *s)
|
||||
{
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct image *im;
|
||||
#endif
|
||||
|
||||
free(s->sel);
|
||||
free(s->tabs);
|
||||
free(s->path);
|
||||
@@ -174,17 +160,6 @@ screen_free(struct screen *s)
|
||||
hyperlinks_free(s->hyperlinks);
|
||||
screen_free_titles(s);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
/*
|
||||
* Images saved when entering the alternate screen stay linked in the
|
||||
* global list; move them back so they are freed and unlinked here, or
|
||||
* a later eviction would write through this freed screen.
|
||||
*/
|
||||
TAILQ_CONCAT(&s->images, &s->saved_images, entry);
|
||||
TAILQ_FOREACH(im, &s->images, entry)
|
||||
im->list = &s->images;
|
||||
image_free_all(s);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Reset tabs to default, eight spaces apart. */
|
||||
@@ -377,10 +352,6 @@ screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow,
|
||||
if (sy != screen_size_y(s))
|
||||
screen_resize_y(s, sy, eat_empty, &cy);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
image_free_all(s);
|
||||
#endif
|
||||
|
||||
if (reflow)
|
||||
screen_reflow(s, sx, &cx, &cy, cursor);
|
||||
|
||||
@@ -692,10 +663,6 @@ int
|
||||
screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
{
|
||||
u_int sx, sy;
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct image *im;
|
||||
#endif
|
||||
|
||||
if (SCREEN_IS_ALTERNATE(s))
|
||||
return 0;
|
||||
sx = screen_size_x(s);
|
||||
@@ -709,12 +676,6 @@ screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
}
|
||||
memcpy(&s->saved_cell, gc, sizeof s->saved_cell);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
TAILQ_CONCAT(&s->saved_images, &s->images, entry);
|
||||
TAILQ_FOREACH(im, &s->saved_images, entry)
|
||||
im->list = &s->saved_images;
|
||||
#endif
|
||||
|
||||
grid_view_clear(s->grid, 0, 0, sx, sy, 8);
|
||||
|
||||
s->saved_flags = s->grid->flags;
|
||||
@@ -728,10 +689,6 @@ int
|
||||
screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
{
|
||||
u_int sx = screen_size_x(s), sy = screen_size_y(s);
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct image *im;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* If the current size is different, temporarily resize to the old size
|
||||
* before copying back.
|
||||
@@ -774,13 +731,6 @@ screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
|
||||
grid_destroy(s->saved_grid);
|
||||
s->saved_grid = NULL;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
image_free_all(s);
|
||||
TAILQ_CONCAT(&s->images, &s->saved_images, entry);
|
||||
TAILQ_FOREACH(im, &s->images, entry)
|
||||
im->list = &s->images;
|
||||
#endif
|
||||
|
||||
if (s->cx > screen_size_x(s) - 1)
|
||||
s->cx = screen_size_x(s) - 1;
|
||||
if (s->cy > screen_size_y(s) - 1)
|
||||
|
||||
@@ -30,7 +30,11 @@
|
||||
|
||||
/* Default style. */
|
||||
static struct style style_default = {
|
||||
{ { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0 },
|
||||
{ { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0, 0
|
||||
#ifdef ENABLE_IMAGES
|
||||
, 0, 0, 0
|
||||
#endif
|
||||
},
|
||||
0,
|
||||
0,
|
||||
|
||||
|
||||
@@ -5057,6 +5057,10 @@ Supports extended keys.
|
||||
Supports focus reporting.
|
||||
.It hyperlinks
|
||||
Supports OSC 8 hyperlinks.
|
||||
.It image-quadrants
|
||||
Allows image fallbacks to use Unicode quadrant block characters.
|
||||
.It image-sextants
|
||||
Allows image fallbacks to use Unicode sextant block characters.
|
||||
.It ignorefkeys
|
||||
Ignore function keys from
|
||||
.Xr terminfo 5
|
||||
|
||||
@@ -75,7 +75,13 @@ struct screen_write_cline;
|
||||
struct screen_write_ctx;
|
||||
struct session;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct image;
|
||||
struct image_backend;
|
||||
struct image_fallback_data;
|
||||
struct image_rect;
|
||||
#endif
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct sixel_image;
|
||||
#endif
|
||||
|
||||
@@ -803,6 +809,8 @@ struct colour_palette {
|
||||
#define GRID_FLAG_NOPALETTE 0x20
|
||||
#define GRID_FLAG_CLEARED 0x40
|
||||
#define GRID_FLAG_TAB 0x80
|
||||
#define GRID_FLAG_IMAGE 0x100
|
||||
#define GRID_FLAG_IMAGE_DAMAGED 0x200
|
||||
|
||||
/* Grid line flags. */
|
||||
#define GRID_LINE_WRAPPED 0x1
|
||||
@@ -858,22 +866,32 @@ struct colour_palette {
|
||||
struct grid_cell {
|
||||
struct utf8_data data;
|
||||
u_short attr;
|
||||
u_char flags;
|
||||
u_short flags;
|
||||
int fg;
|
||||
int bg;
|
||||
int us;
|
||||
u_int link;
|
||||
#ifdef ENABLE_IMAGES
|
||||
u_int image_id;
|
||||
u_int image_x;
|
||||
u_int image_y;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Grid extended cell entry. */
|
||||
struct grid_extd_entry {
|
||||
utf8_char data;
|
||||
u_short attr;
|
||||
u_char flags;
|
||||
u_short flags;
|
||||
int fg;
|
||||
int bg;
|
||||
int us;
|
||||
u_int link;
|
||||
#ifdef ENABLE_IMAGES
|
||||
u_short image_id;
|
||||
u_short image_x;
|
||||
u_short image_y;
|
||||
#endif
|
||||
} __packed;
|
||||
|
||||
/* Grid cell entry. */
|
||||
@@ -887,7 +905,7 @@ struct grid_cell_entry {
|
||||
u_char data;
|
||||
} data;
|
||||
};
|
||||
u_char flags;
|
||||
u_short flags;
|
||||
} __packed;
|
||||
|
||||
/* OSC 133 data for a grid line. */
|
||||
@@ -1021,24 +1039,30 @@ struct style {
|
||||
u_int link;
|
||||
};
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
/* Image. */
|
||||
struct image {
|
||||
struct screen *s;
|
||||
struct sixel_image *data;
|
||||
char *fallback;
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* Private ACS keys used by the text image backend. */
|
||||
#define TTY_ACS_IMAGE_SHADE_LIGHT 0x80
|
||||
#define TTY_ACS_IMAGE_SHADE_MEDIUM 0x81
|
||||
#define TTY_ACS_IMAGE_SHADE_DARK 0x82
|
||||
#define TTY_ACS_IMAGE_BLOCK 0x83
|
||||
#define TTY_ACS_IMAGE_HALF_UPPER 0x84
|
||||
#define TTY_ACS_IMAGE_HALF_LOWER 0x85
|
||||
#define TTY_ACS_IMAGE_HALF_LEFT 0x86
|
||||
#define TTY_ACS_IMAGE_HALF_RIGHT 0x87
|
||||
#define TTY_ACS_IMAGE_QUADRANT_LOWER_LEFT 0x88
|
||||
#define TTY_ACS_IMAGE_QUADRANT_LOWER_RIGHT 0x89
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT 0x8a
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_LEFT_LOWER_RIGHT 0x8b
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_RIGHT 0x8c
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_LEFT 0x8d
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_RIGHT 0x8e
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT 0x8f
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT 0x90
|
||||
#define TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT_LOWER_RIGHT 0x91
|
||||
#define TTY_ACS_IMAGE_SEXTANT_FIRST 0x92
|
||||
#define TTY_ACS_IMAGE_SEXTANT_LAST 0xcd
|
||||
|
||||
u_int px;
|
||||
u_int py;
|
||||
u_int sx;
|
||||
u_int sy;
|
||||
|
||||
struct images *list;
|
||||
TAILQ_ENTRY (image) entry;
|
||||
|
||||
TAILQ_ENTRY (image) all_entry;
|
||||
};
|
||||
TAILQ_HEAD(images, image);
|
||||
#define IMAGE_SIZE_LIMIT (64 * 1024 * 1024)
|
||||
#endif
|
||||
|
||||
/* Cursor style. */
|
||||
@@ -1097,11 +1121,6 @@ struct screen {
|
||||
bitstr_t *tabs;
|
||||
struct screen_sel *sel;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
struct images images;
|
||||
struct images saved_images;
|
||||
#endif
|
||||
|
||||
struct screen_write_cline *write_list;
|
||||
|
||||
struct hyperlinks *hyperlinks;
|
||||
@@ -1731,6 +1750,10 @@ struct tty_term {
|
||||
#define TERM_RGBCOLOURS 0x10
|
||||
#define TERM_VT100LIKE 0x20
|
||||
#define TERM_SIXEL 0x40
|
||||
#ifdef ENABLE_IMAGES
|
||||
#define TERM_IMAGE_QUADRANTS 0x80
|
||||
#define TERM_IMAGE_SEXTANTS 0x100
|
||||
#endif
|
||||
int flags;
|
||||
|
||||
LIST_ENTRY(tty_term) entry;
|
||||
@@ -1832,6 +1855,10 @@ struct tty {
|
||||
|
||||
struct event key_timer;
|
||||
struct tty_key *key_tree;
|
||||
#ifdef ENABLE_IMAGES
|
||||
const struct image_backend *image_backend;
|
||||
void *image_data;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Terminal command context. */
|
||||
@@ -1868,7 +1895,7 @@ struct tty_ctx {
|
||||
size_t size;
|
||||
} sel;
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct image *image;
|
||||
#endif
|
||||
};
|
||||
@@ -2984,10 +3011,6 @@ void tty_cmd_scrolldown(struct tty *, const struct tty_ctx *);
|
||||
void tty_cmd_reverseindex(struct tty *, const struct tty_ctx *);
|
||||
void tty_cmd_setselection(struct tty *, const struct tty_ctx *);
|
||||
void tty_cmd_rawstring(struct tty *, const struct tty_ctx *);
|
||||
#ifdef ENABLE_SIXEL
|
||||
void tty_cmd_sixelimage(struct tty *, const struct tty_ctx *);
|
||||
void tty_draw_images(struct client *, struct window_pane *);
|
||||
#endif
|
||||
void tty_cmd_syncstart(struct tty *, const struct tty_ctx *);
|
||||
void tty_default_colours(struct grid_cell *, struct window_pane *, u_int *);
|
||||
|
||||
@@ -3029,6 +3052,9 @@ void tty_default_features(int *, const char *, u_int);
|
||||
int tty_acs_needed(struct tty *);
|
||||
const char *tty_acs_get(struct tty *, u_char);
|
||||
int tty_acs_reverse_get(struct tty *, const char *, size_t);
|
||||
#ifdef ENABLE_IMAGES
|
||||
u_char tty_acs_image_sextant(u_int);
|
||||
#endif
|
||||
const struct utf8_data *tty_acs_double_borders(int);
|
||||
const struct utf8_data *tty_acs_heavy_borders(int);
|
||||
const struct utf8_data *tty_acs_rounded_borders(int);
|
||||
@@ -3628,7 +3654,7 @@ void screen_write_setselection(struct screen_write_ctx *, const char *,
|
||||
u_char *, u_int);
|
||||
void screen_write_rawstring(struct screen_write_ctx *, u_char *, u_int,
|
||||
int);
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
void screen_write_sixelimage(struct screen_write_ctx *,
|
||||
struct sixel_image *, u_int);
|
||||
#endif
|
||||
@@ -4211,16 +4237,66 @@ void spawn_editor_finish(struct window_pane *);
|
||||
/* regsub.c */
|
||||
char *regsub(const char *, const char *, const char *, int);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* image.c */
|
||||
int image_free_all(struct screen *);
|
||||
struct image *image_store(struct screen *, struct sixel_image *);
|
||||
int image_check_line(struct screen *, u_int, u_int);
|
||||
int image_check_area(struct screen *, u_int, u_int, u_int, u_int);
|
||||
int image_scroll_up(struct screen *, u_int);
|
||||
#define IMAGE_BACKEND_GRAPHICAL 0x1
|
||||
#define IMAGE_BACKEND_SCROLLS 0x2
|
||||
#define IMAGE_BACKEND_TEMPORAL 0x4
|
||||
|
||||
struct image *image_create(u_int, u_int, u_int, u_int, u_int, u_int,
|
||||
u_char *);
|
||||
struct image *image_find(u_int);
|
||||
u_int image_get_id(const struct image *);
|
||||
u_short image_get_grid_id(u_int);
|
||||
u_int image_get_id_by_grid_id(u_short);
|
||||
void image_get_size(const struct image *, u_int *, u_int *);
|
||||
void image_get_canvas_size(const struct image *, u_int *, u_int *);
|
||||
void image_get_size_in_cells(const struct image *, u_int *, u_int *);
|
||||
const u_char *image_get_pixels(const struct image *, size_t *, size_t *);
|
||||
struct sixel_image *image_get_sixel(const struct image *);
|
||||
void image_set_sixel(struct image *, struct sixel_image *);
|
||||
u_char image_get_brightness(struct image *, u_int, u_int);
|
||||
void image_get_cell_average(struct image *, u_int, u_int, u_int, u_int,
|
||||
u_int, u_int, u_char *, u_char *, u_char *);
|
||||
struct image_fallback_data *image_get_fallback_data(const struct image *);
|
||||
void image_set_fallback_data(struct image *,
|
||||
struct image_fallback_data *);
|
||||
void image_ref(u_int);
|
||||
void image_free(u_int);
|
||||
void image_set_cell(struct grid_cell *, struct image *, u_int,
|
||||
u_int);
|
||||
void image_write(struct screen_write_ctx *, struct image *, u_int);
|
||||
void image_get_pixel_rect(const struct image *, u_int, u_int,
|
||||
u_int, u_int, u_int *, u_int *, u_int *, u_int *);
|
||||
void image_size_in_cells(u_int, u_int, u_int, u_int, u_int *,
|
||||
u_int *);
|
||||
void image_redraw_area(struct screen_write_ctx *, u_int, u_int,
|
||||
u_int, u_int);
|
||||
void image_redraw_all(struct screen_write_ctx *);
|
||||
void image_redraw_scroll(struct screen_write_ctx *, u_int);
|
||||
int image_backend_flags(struct tty *);
|
||||
int image_get_draw_cell(struct tty *, const struct grid_cell *,
|
||||
struct grid_cell *, const struct tty_style_ctx *);
|
||||
void image_tty_update(struct tty *);
|
||||
void image_tty_geometry_changed(struct tty *);
|
||||
void image_tty_free(struct tty *, int);
|
||||
void image_draw_line(struct tty *, struct screen *, u_int, u_int,
|
||||
u_int, u_int, u_int, const struct tty_style_ctx *);
|
||||
void image_get_fallback_cell(struct tty *, struct image *, u_int,
|
||||
u_int, const struct grid_cell *, struct grid_cell *,
|
||||
const struct tty_style_ctx *);
|
||||
void image_free_fallback(struct image *);
|
||||
struct image *image_rect_get_image(const struct image_rect *);
|
||||
void image_rect_get_coords(const struct image_rect *,
|
||||
u_int *, u_int *, u_int *, u_int *, u_int *, u_int *);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
/* image-sixel.c */
|
||||
#define SIXEL_COLOUR_REGISTERS 1024
|
||||
void sixel_draw_rect(struct tty *,
|
||||
const struct image_rect *, const struct tty_style_ctx *);
|
||||
void sixel_free_output(struct tty *, int);
|
||||
struct sixel_image *sixel_parse(const char *, size_t, u_int, u_int, u_int);
|
||||
void sixel_free(struct sixel_image *);
|
||||
void sixel_log(struct sixel_image *);
|
||||
@@ -4230,6 +4306,7 @@ struct sixel_image *sixel_scale(struct sixel_image *, u_int, u_int, u_int,
|
||||
char *sixel_print(struct sixel_image *, struct sixel_image *,
|
||||
size_t *);
|
||||
struct screen *sixel_to_screen(struct sixel_image *);
|
||||
struct image *sixel_to_image(struct sixel_image *);
|
||||
#endif
|
||||
|
||||
/* server-acl.c */
|
||||
|
||||
@@ -64,9 +64,65 @@ static const struct tty_acs_entry tty_acs_table[] = {
|
||||
{ '{', "\317\200" }, /* greek pi */
|
||||
{ '|', "\342\211\240" }, /* not-equal */
|
||||
{ '}', "\302\243" }, /* UK pound sign */
|
||||
{ '~', "\302\267" } /* bullet */
|
||||
{ '~', "\302\267" }, /* bullet */
|
||||
#ifdef ENABLE_IMAGES
|
||||
{ TTY_ACS_IMAGE_SHADE_LIGHT, "\342\226\221" },
|
||||
{ TTY_ACS_IMAGE_SHADE_MEDIUM, "\342\226\222" },
|
||||
{ TTY_ACS_IMAGE_SHADE_DARK, "\342\226\223" },
|
||||
{ TTY_ACS_IMAGE_BLOCK, "\342\226\210" },
|
||||
{ TTY_ACS_IMAGE_HALF_UPPER, "\342\226\200" },
|
||||
{ TTY_ACS_IMAGE_HALF_LOWER, "\342\226\204" },
|
||||
{ TTY_ACS_IMAGE_HALF_LEFT, "\342\226\214" },
|
||||
{ TTY_ACS_IMAGE_HALF_RIGHT, "\342\226\220" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_LOWER_LEFT, "\342\226\226" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_LOWER_RIGHT, "\342\226\227" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT, "\342\226\230" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_LEFT_LOWER_RIGHT,
|
||||
"\342\226\231" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_LOWER_RIGHT, "\342\226\232" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_LEFT,
|
||||
"\342\226\233" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_LEFT_UPPER_RIGHT_LOWER_RIGHT,
|
||||
"\342\226\234" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT, "\342\226\235" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT, "\342\226\236" },
|
||||
{ TTY_ACS_IMAGE_QUADRANT_UPPER_RIGHT_LOWER_LEFT_LOWER_RIGHT,
|
||||
"\342\226\237" }
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
static char tty_acs_image_sextants[60][5];
|
||||
|
||||
static void
|
||||
tty_acs_image_sextants_init(void)
|
||||
{
|
||||
u_int cp, i;
|
||||
|
||||
if (tty_acs_image_sextants[0][0] != '\0')
|
||||
return;
|
||||
for (i = 0; i < nitems(tty_acs_image_sextants); i++) {
|
||||
cp = 0x1fb00 + i;
|
||||
tty_acs_image_sextants[i][0] = 0xf0 | (cp >> 18);
|
||||
tty_acs_image_sextants[i][1] = 0x80 | ((cp >> 12) & 0x3f);
|
||||
tty_acs_image_sextants[i][2] = 0x80 | ((cp >> 6) & 0x3f);
|
||||
tty_acs_image_sextants[i][3] = 0x80 | (cp & 0x3f);
|
||||
}
|
||||
}
|
||||
|
||||
u_char
|
||||
tty_acs_image_sextant(u_int mask)
|
||||
{
|
||||
if (mask == 0 || mask == 21 || mask == 42 || mask >= 63)
|
||||
return (0);
|
||||
if (mask < 21)
|
||||
return (TTY_ACS_IMAGE_SEXTANT_FIRST + mask - 1);
|
||||
if (mask < 42)
|
||||
return (TTY_ACS_IMAGE_SEXTANT_FIRST + mask - 2);
|
||||
return (TTY_ACS_IMAGE_SEXTANT_FIRST + mask - 3);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Table mapping UTF-8 to ACS entries. */
|
||||
struct tty_acs_reverse_entry {
|
||||
const char *string;
|
||||
@@ -239,6 +295,15 @@ tty_acs_get(struct tty *tty, u_char ch)
|
||||
return (&tty->term->acs[ch][0]);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (ch >= TTY_ACS_IMAGE_SEXTANT_FIRST &&
|
||||
ch <= TTY_ACS_IMAGE_SEXTANT_LAST) {
|
||||
tty_acs_image_sextants_init();
|
||||
return (tty_acs_image_sextants[
|
||||
ch - TTY_ACS_IMAGE_SEXTANT_FIRST]);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Otherwise look up the UTF-8 translation. */
|
||||
entry = bsearch(&ch, tty_acs_table, nitems(tty_acs_table),
|
||||
sizeof tty_acs_table[0], tty_acs_cmp);
|
||||
|
||||
+18
-5
@@ -123,6 +123,9 @@ tty_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py, u_int nx,
|
||||
struct grid *gd = s->grid;
|
||||
const struct grid_cell *gcp;
|
||||
struct grid_cell gc, ngc, last;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct grid_cell image_gc;
|
||||
#endif
|
||||
struct grid_line *gl;
|
||||
u_int i, j, last_i, cx, ex, width;
|
||||
u_int cellsize, bg;
|
||||
@@ -253,14 +256,24 @@ 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);
|
||||
|
||||
/* Work out empty cells. */
|
||||
empty = tty_draw_line_get_empty(&gc, &last,
|
||||
nx - i);
|
||||
if (empty != 0)
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (gc.flags & GRID_FLAG_IMAGE) {
|
||||
(void)image_get_draw_cell(tty, &gc, &image_gc,
|
||||
style_ctx);
|
||||
gcp = &image_gc;
|
||||
} else
|
||||
gcp = &gc;
|
||||
#else
|
||||
gcp = &gc;
|
||||
#endif
|
||||
|
||||
/* Work out empty cells. */
|
||||
empty = tty_draw_line_get_empty(gcp, &last, nx - i);
|
||||
if (empty != 0)
|
||||
;
|
||||
else {
|
||||
/* Update for codeset if needed. */
|
||||
gcp = tty_check_codeset(tty, &gc);
|
||||
gcp = tty_check_codeset(tty, gcp);
|
||||
|
||||
/* And for selection. */
|
||||
if (gcp->flags & GRID_FLAG_SELECTED) {
|
||||
|
||||
+24
-1
@@ -224,9 +224,11 @@ static const struct tty_feature tty_feature_strikethrough = {
|
||||
0
|
||||
};
|
||||
|
||||
#define TTY_FEATURE_SYNC "Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;"
|
||||
|
||||
/* Terminal supports synchronized updates. */
|
||||
static const char *const tty_feature_sync_capabilities[] = {
|
||||
"Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;",
|
||||
TTY_FEATURE_SYNC,
|
||||
NULL
|
||||
};
|
||||
static const struct tty_feature tty_feature_sync = {
|
||||
@@ -357,6 +359,22 @@ static const struct tty_feature tty_feature_sixel = {
|
||||
TERM_SIXEL
|
||||
};
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
static const char *const tty_feature_image_capabilities[] = {
|
||||
NULL
|
||||
};
|
||||
static const struct tty_feature tty_feature_image_quadrants = {
|
||||
"image-quadrants",
|
||||
tty_feature_image_capabilities,
|
||||
TERM_IMAGE_QUADRANTS
|
||||
};
|
||||
static const struct tty_feature tty_feature_image_sextants = {
|
||||
"image-sextants",
|
||||
tty_feature_image_capabilities,
|
||||
TERM_IMAGE_SEXTANTS
|
||||
};
|
||||
#endif
|
||||
|
||||
/* Terminal supports the OSC 9;4 progress bar. */
|
||||
static const char *const tty_feature_progressbar_capabilities[] = {
|
||||
"Spb=\\E]9;4;%p1%d;%p2%d\\E\\\\",
|
||||
@@ -379,6 +397,10 @@ static const struct tty_feature *const tty_features[] = {
|
||||
&tty_feature_extkeys,
|
||||
&tty_feature_focus,
|
||||
&tty_feature_ignorefkeys,
|
||||
#ifdef ENABLE_IMAGES
|
||||
&tty_feature_image_quadrants,
|
||||
&tty_feature_image_sextants,
|
||||
#endif
|
||||
&tty_feature_margins,
|
||||
&tty_feature_mouse,
|
||||
&tty_feature_osc7,
|
||||
@@ -581,6 +603,7 @@ tty_default_features(int *feat, const char *name, u_int version)
|
||||
"extkeys,"
|
||||
"focus,"
|
||||
"hyperlinks,"
|
||||
"sixel,"
|
||||
"usstyle"
|
||||
},
|
||||
{ .name = "ghostty",
|
||||
|
||||
@@ -68,11 +68,6 @@ static void tty_emulate_repeat(struct tty *, enum tty_code_code,
|
||||
static void tty_draw_pane(struct tty *, const struct tty_ctx *, u_int);
|
||||
static int tty_check_overlay(struct tty *, u_int, u_int);
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
static void tty_write_one(void (*)(struct tty *, const struct tty_ctx *),
|
||||
struct client *, struct tty_ctx *);
|
||||
#endif
|
||||
|
||||
#define tty_use_margin(tty) \
|
||||
(tty->term->flags & TERM_DECSLRM)
|
||||
#define tty_full_width(tty, ctx) \
|
||||
@@ -163,10 +158,21 @@ tty_resize(struct tty *tty)
|
||||
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;
|
||||
|
||||
geometry_changed = (tty->xpixel != xpixel || tty->ypixel != ypixel);
|
||||
#endif
|
||||
tty->sx = sx;
|
||||
tty->sy = sy;
|
||||
tty->xpixel = xpixel;
|
||||
tty->ypixel = ypixel;
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (geometry_changed) {
|
||||
image_tty_geometry_changed(tty);
|
||||
server_redraw_client(tty->client);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -511,6 +517,9 @@ tty_close(struct tty *tty)
|
||||
tty_stop_tty(tty);
|
||||
|
||||
if (tty->flags & TTY_OPENED) {
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_tty_free(tty, 1);
|
||||
#endif
|
||||
evbuffer_free(tty->in);
|
||||
event_del(&tty->event_in);
|
||||
evbuffer_free(tty->out);
|
||||
@@ -527,7 +536,9 @@ void
|
||||
tty_free(struct tty *tty)
|
||||
{
|
||||
tty_close(tty);
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_tty_free(tty, 0);
|
||||
#endif
|
||||
free(tty->r.ranges);
|
||||
}
|
||||
|
||||
@@ -538,6 +549,9 @@ tty_update_features(struct tty *tty)
|
||||
|
||||
if (tty_apply_features(tty->term, c->term_features))
|
||||
tty_term_apply_overrides(tty->term);
|
||||
#ifdef ENABLE_IMAGES
|
||||
image_tty_update(tty);
|
||||
#endif
|
||||
|
||||
if (tty_use_margin(tty))
|
||||
tty_putcode(tty, TTYC_ENMG);
|
||||
@@ -1522,60 +1536,6 @@ tty_check_overlay_range(struct tty *tty, u_int px, u_int py, u_int nx)
|
||||
return (c->overlay_check(c, c->overlay_data, px, py, nx));
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
/* Update context for client. */
|
||||
static int
|
||||
tty_set_client_cb(struct tty_ctx *ttyctx, struct client *c)
|
||||
{
|
||||
struct window_pane *wp = ttyctx->arg;
|
||||
|
||||
if (c->session->curw->window != wp->window)
|
||||
return (0);
|
||||
if (wp->layout_cell == NULL)
|
||||
return (0);
|
||||
|
||||
if (tty_window_offset(&c->tty, &ttyctx->wox, &ttyctx->woy, &ttyctx->wsx,
|
||||
&ttyctx->wsy))
|
||||
ttyctx->flags |= TTY_CTX_WINDOW_BIGGER;
|
||||
else
|
||||
ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER;
|
||||
|
||||
ttyctx->yoff = ttyctx->ryoff = wp->yoff;
|
||||
if (status_at_line(c) == 0)
|
||||
ttyctx->yoff += status_line_size(c);
|
||||
|
||||
return (1);
|
||||
}
|
||||
|
||||
void
|
||||
tty_draw_images(struct client *c, struct window_pane *wp)
|
||||
{
|
||||
struct image *im;
|
||||
struct tty_ctx ttyctx;
|
||||
|
||||
TAILQ_FOREACH(im, &wp->screen->images, entry) {
|
||||
memset(&ttyctx, 0, sizeof ttyctx);
|
||||
|
||||
/* Set the client independent properties. */
|
||||
ttyctx.ocx = im->px;
|
||||
ttyctx.ocy = im->py;
|
||||
|
||||
ttyctx.orlower = wp->screen->rlower;
|
||||
ttyctx.orupper = wp->screen->rupper;
|
||||
|
||||
ttyctx.xoff = ttyctx.rxoff = wp->xoff;
|
||||
ttyctx.sx = wp->sx;
|
||||
ttyctx.sy = wp->sy;
|
||||
|
||||
ttyctx.image = im;
|
||||
ttyctx.arg = wp;
|
||||
ttyctx.set_client_cb = tty_set_client_cb;
|
||||
ttyctx.flags |= TTY_CTX_INVISIBLE_PANES;
|
||||
tty_write_one(tty_cmd_sixelimage, c, &ttyctx);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
tty_sync_start(struct tty *tty)
|
||||
{
|
||||
@@ -1649,19 +1609,6 @@ tty_write(void (*cmdfn)(struct tty *, const struct tty_ctx *),
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
/* Only write to the incoming tty instead of every client. */
|
||||
static void
|
||||
tty_write_one(void (*cmdfn)(struct tty *, const struct tty_ctx *),
|
||||
struct client *c, struct tty_ctx *ctx)
|
||||
{
|
||||
if (ctx->set_client_cb == NULL)
|
||||
return;
|
||||
if ((ctx->set_client_cb(ctx, c)) == 1)
|
||||
cmdfn(&c->tty, ctx);
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
tty_cmd_insertcharacter(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
@@ -2155,58 +2102,6 @@ tty_cmd_rawstring(struct tty *tty, const struct tty_ctx *ctx)
|
||||
tty_invalidate(tty);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SIXEL
|
||||
void
|
||||
tty_cmd_sixelimage(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
struct image *im = ctx->image;
|
||||
struct sixel_image *si = im->data;
|
||||
struct sixel_image *new;
|
||||
char *data;
|
||||
size_t size;
|
||||
u_int cx = ctx->ocx, cy = ctx->ocy, sx, sy;
|
||||
u_int i, j, x, y, rx, ry;
|
||||
int fallback = 0;
|
||||
|
||||
if ((~tty->term->flags & TERM_SIXEL) &&
|
||||
!tty_term_has(tty->term, TTYC_SXL))
|
||||
fallback = 1;
|
||||
if (tty->xpixel == 0 || tty->ypixel == 0)
|
||||
fallback = 1;
|
||||
|
||||
sixel_size_in_cells(si, &sx, &sy);
|
||||
log_debug("%s: image is %ux%u", __func__, sx, sy);
|
||||
if (!tty_clamp_area(tty, ctx, cx, cy, sx, sy, &i, &j, &x, &y, &rx, &ry))
|
||||
return;
|
||||
log_debug("%s: clamping to %u,%u-%u,%u", __func__, i, j, rx, ry);
|
||||
|
||||
if (fallback == 1) {
|
||||
data = xstrdup(im->fallback);
|
||||
size = strlen(data);
|
||||
} else {
|
||||
new = sixel_scale(si, tty->xpixel, tty->ypixel, i, j, rx, ry, 0);
|
||||
if (new == NULL)
|
||||
return;
|
||||
|
||||
data = sixel_print(new, si, &size);
|
||||
}
|
||||
if (data != NULL) {
|
||||
log_debug("%s: %zu bytes: %s", __func__, size, data);
|
||||
tty_region_off(tty);
|
||||
tty_margin_off(tty);
|
||||
tty_cursor(tty, x, y);
|
||||
|
||||
tty->flags |= TTY_NOBLOCK;
|
||||
tty_add(tty, data, size);
|
||||
tty_invalidate(tty);
|
||||
free(data);
|
||||
}
|
||||
|
||||
if (fallback == 0)
|
||||
sixel_free(new);
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
tty_cmd_syncstart(struct tty *tty, const struct tty_ctx *ctx)
|
||||
{
|
||||
@@ -2234,6 +2129,9 @@ tty_cell(struct tty *tty, const struct grid_cell *gc,
|
||||
const struct tty_style_ctx *style_ctx)
|
||||
{
|
||||
const struct grid_cell *gcp;
|
||||
#ifdef ENABLE_IMAGES
|
||||
struct grid_cell image_gc;
|
||||
#endif
|
||||
|
||||
/* Skip last character if terminal is stupid. */
|
||||
if ((tty->term->flags & TERM_NOAM) &&
|
||||
@@ -2249,6 +2147,14 @@ tty_cell(struct tty *tty, const struct grid_cell *gc,
|
||||
if (!tty_check_overlay(tty, tty->cx, tty->cy))
|
||||
return;
|
||||
|
||||
#ifdef ENABLE_IMAGES
|
||||
if (gc->flags & GRID_FLAG_IMAGE) {
|
||||
if (image_get_draw_cell(tty, gc, &image_gc, style_ctx))
|
||||
return;
|
||||
gc = &image_gc;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Check the output codeset and apply attributes. */
|
||||
gcp = tty_check_codeset(tty, gc);
|
||||
tty_attributes(tty, gcp, style_ctx);
|
||||
|
||||
+3
-1
@@ -6151,7 +6151,9 @@ window_copy_copy_line(struct window_mode_entry *wme, char **buf, size_t *off,
|
||||
grid_get_cell(gd, i, sy, &gc);
|
||||
if (gc.flags & GRID_FLAG_PADDING)
|
||||
continue;
|
||||
if (gc.flags & GRID_FLAG_TAB)
|
||||
if (gc.flags & GRID_FLAG_IMAGE)
|
||||
utf8_set(&ud, ' ');
|
||||
else if (gc.flags & GRID_FLAG_TAB)
|
||||
utf8_set(&ud, '\t');
|
||||
else
|
||||
utf8_copy(&ud, &gc.data);
|
||||
|
||||
Reference in New Issue
Block a user