mirror of
https://github.com/tmux/tmux.git
synced 2026-10-14 23:25:50 +00:00
1402 lines
33 KiB
C
1402 lines
33 KiB
C
/* $OpenBSD$ */
|
|
|
|
/*
|
|
* 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
|
|
* 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 <errno.h>
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "tmux.h"
|
|
|
|
/* Maximum SIXEL width and height. */
|
|
#define SIXEL_WIDTH_LIMIT 10000
|
|
#define SIXEL_HEIGHT_LIMIT 10000
|
|
|
|
/* Number of colours in a generated SIXEL palette. */
|
|
#define SIXEL_PALETTE_SIZE 256
|
|
|
|
/* Number of histogram levels for each RGB component. */
|
|
#define SIXEL_HGRAM_LEVELS 32
|
|
|
|
/* Number of bins in the RGB histogram. */
|
|
#define SIXEL_HGRAM_SIZE \
|
|
(SIXEL_HGRAM_LEVELS * SIXEL_HGRAM_LEVELS * SIXEL_HGRAM_LEVELS)
|
|
|
|
/* Line of a SIXEL image. */
|
|
struct sixel_line {
|
|
/* Number of pixels in this row. */
|
|
u_int x;
|
|
|
|
/* Palette index for each pixel in the row. */
|
|
uint16_t *data;
|
|
};
|
|
|
|
/* SIXEL image. */
|
|
struct sixel_image {
|
|
/* Decoded image dimensions in pixels. */
|
|
u_int x;
|
|
u_int y;
|
|
|
|
/* Terminal cell pixel dimensions used for scaling. */
|
|
u_int xpixel;
|
|
u_int ypixel;
|
|
|
|
/* SIXEL palette. */
|
|
u_int *colours;
|
|
u_int ncolours;
|
|
u_int used_colours;
|
|
|
|
/* SIXEL raster attributes. */
|
|
u_int set_ra;
|
|
u_int ra_x;
|
|
u_int ra_y;
|
|
|
|
/* DCS parameters. */
|
|
u_int p1;
|
|
u_int p2;
|
|
|
|
/* Current parser position and colour register. */
|
|
u_int dx;
|
|
u_int dy;
|
|
u_int dc;
|
|
|
|
/* Lines of palette indexes and the bytes allocated for them. */
|
|
struct sixel_line *lines;
|
|
size_t allocated;
|
|
};
|
|
|
|
/* One colour's encoded columns in the current six-row band. */
|
|
struct sixel_plane {
|
|
/* Position of the next encoded colour plane. */
|
|
u_int next_x;
|
|
u_int next_y;
|
|
|
|
/* State used while encoding SIXEL patterns. */
|
|
u_int count;
|
|
char pattern;
|
|
char next_pattern;
|
|
|
|
/* Output buffer and its allocation and used lengths. */
|
|
size_t stream_len;
|
|
size_t stream_used;
|
|
char *stream;
|
|
};
|
|
|
|
/* Adjacent placement rows buffered for one SIXEL output sequence. */
|
|
struct sixel_pending {
|
|
struct image *image;
|
|
|
|
/* Source in image in cells. */
|
|
u_int src_x;
|
|
u_int src_y;
|
|
|
|
/* Size in cells. */
|
|
u_int w;
|
|
u_int h;
|
|
|
|
/* Destination on terminal in cells. */
|
|
u_int dst_x;
|
|
u_int dst_y;
|
|
};
|
|
|
|
/* RGB histogram entry. */
|
|
struct sixel_hgram {
|
|
u_int count;
|
|
uint64_t r;
|
|
uint64_t g;
|
|
uint64_t b;
|
|
};
|
|
|
|
/* Bounds and population of one palette region. */
|
|
struct sixel_box {
|
|
u_int rmin;
|
|
u_int rmax;
|
|
u_int gmin;
|
|
u_int gmax;
|
|
u_int bmin;
|
|
u_int bmax;
|
|
u_int count;
|
|
};
|
|
|
|
/* One generated palette colour. */
|
|
struct sixel_rgb {
|
|
u_char r;
|
|
u_char g;
|
|
u_char b;
|
|
};
|
|
|
|
/* Pixels and dimensions of the image being encoded. */
|
|
struct sixel_source {
|
|
/* Source pixels and number of bytes between rows. */
|
|
const u_char *pixels;
|
|
size_t stride;
|
|
|
|
/* Source image and canvas sizes in pixels and size in cells. */
|
|
u_int width;
|
|
u_int height;
|
|
u_int canvas_width;
|
|
u_int canvas_height;
|
|
u_int sx;
|
|
u_int sy;
|
|
|
|
/* Destination canvas size in pixels. */
|
|
u_int dst_w;
|
|
u_int dst_h;
|
|
|
|
/* Output position and size in the destination canvas in pixels. */
|
|
u_int dst_x;
|
|
u_int dst_y;
|
|
u_int out_w;
|
|
u_int out_h;
|
|
};
|
|
|
|
/* Grow a SIXEL image to contain a line. */
|
|
static int
|
|
sixel_parse_expand_lines(struct sixel_image *si, u_int y)
|
|
{
|
|
u_int height = si->y;
|
|
|
|
if (y > height)
|
|
height = y;
|
|
if (height > SIXEL_HEIGHT_LIMIT)
|
|
return (1);
|
|
if (si->x * height > IMAGE_SIZE_LIMIT / 4)
|
|
return (1);
|
|
if (y <= si->y)
|
|
return (0);
|
|
si->lines = xrecallocarray(si->lines, si->y, y, sizeof *si->lines);
|
|
si->y = 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)
|
|
{
|
|
u_int width = si->x;
|
|
size_t added;
|
|
|
|
if (x > width)
|
|
width = x;
|
|
if (width > SIXEL_WIDTH_LIMIT)
|
|
return (1);
|
|
if (width * si->y > IMAGE_SIZE_LIMIT / 4)
|
|
return (1);
|
|
if (x <= sl->x)
|
|
return (0);
|
|
added = (width - sl->x) * sizeof *sl->data;
|
|
if (added > IMAGE_SIZE_LIMIT / 2 - si->allocated)
|
|
return (1);
|
|
si->x = width;
|
|
sl->data = xrecallocarray(sl->data, sl->x, si->x, sizeof *sl->data);
|
|
sl->x = si->x;
|
|
si->allocated += added;
|
|
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)
|
|
{
|
|
struct sixel_line *sl;
|
|
|
|
if (y >= si->y)
|
|
return (0);
|
|
sl = &si->lines[y];
|
|
if (x >= sl->x)
|
|
return (0);
|
|
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)
|
|
{
|
|
struct sixel_line *sl;
|
|
|
|
if (sixel_parse_expand_lines(si, y + 1) != 0)
|
|
return (1);
|
|
sl = &si->lines[y];
|
|
if (sixel_parse_expand_line(si, sl, x + 1) != 0)
|
|
return (1);
|
|
sl->data[x] = c;
|
|
return (0);
|
|
}
|
|
|
|
/* Write a SIXEL column. */
|
|
static int
|
|
sixel_parse_write(struct sixel_image *si, u_int ch)
|
|
{
|
|
u_int i;
|
|
|
|
for (i = 0; i < 6; i++) {
|
|
if (ch & (1 << i)) {
|
|
if (sixel_set_pixel(si, si->dx, si->dy + i, si->dc))
|
|
return (1);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/* Parse a number. */
|
|
static int
|
|
sixel_parse_number(const char *cp, char **endptr, u_int *value)
|
|
{
|
|
unsigned long long number;
|
|
|
|
if (*cp < '0' || *cp > '9')
|
|
return (1);
|
|
errno = 0;
|
|
number = strtoull(cp, endptr, 10);
|
|
if (errno == ERANGE || number > UINT_MAX)
|
|
return (1);
|
|
*value = number;
|
|
return (0);
|
|
}
|
|
|
|
/* Parse SIXEL attributes. */
|
|
static const char *
|
|
sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end)
|
|
{
|
|
const char *last;
|
|
char *endptr;
|
|
u_int x, y;
|
|
|
|
last = cp;
|
|
while (last != end) {
|
|
if (*last != ';' && (*last < '0' || *last > '9'))
|
|
break;
|
|
last++;
|
|
}
|
|
strtoul(cp, &endptr, 10);
|
|
if (endptr == last || *endptr != ';')
|
|
return (last);
|
|
strtoul(endptr + 1, &endptr, 10);
|
|
if (endptr == last)
|
|
return (last);
|
|
if (*endptr != ';') {
|
|
log_debug("%s: missing ;", __func__);
|
|
return (NULL);
|
|
}
|
|
|
|
if (sixel_parse_number(endptr + 1, &endptr, &x) != 0) {
|
|
log_debug("%s: invalid number", __func__);
|
|
return (NULL);
|
|
}
|
|
if (endptr == last || *endptr != ';') {
|
|
log_debug("%s: missing ;", __func__);
|
|
return (NULL);
|
|
}
|
|
if (x > SIXEL_WIDTH_LIMIT) {
|
|
log_debug("%s: image is too wide", __func__);
|
|
return (NULL);
|
|
}
|
|
if (sixel_parse_number(endptr + 1, &endptr, &y) != 0) {
|
|
log_debug("%s: invalid number", __func__);
|
|
return (NULL);
|
|
}
|
|
if (endptr != last) {
|
|
log_debug("%s: extra ;", __func__);
|
|
return (NULL);
|
|
}
|
|
if (y > SIXEL_HEIGHT_LIMIT) {
|
|
log_debug("%s: image is too tall", __func__);
|
|
return (NULL);
|
|
}
|
|
|
|
si->x = x;
|
|
if (sixel_parse_expand_lines(si, y) != 0) {
|
|
log_debug("%s: size limit reached", __func__);
|
|
return (NULL);
|
|
}
|
|
|
|
return (last);
|
|
}
|
|
|
|
/* Parse a SIXEL colour register sequence. */
|
|
static const char *
|
|
sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end)
|
|
{
|
|
const char *last;
|
|
char *endptr;
|
|
u_int c, type, c1, c2, c3;
|
|
|
|
last = cp;
|
|
while (last != end) {
|
|
if (*last != ';' && (*last < '0' || *last > '9'))
|
|
break;
|
|
last++;
|
|
}
|
|
|
|
if (sixel_parse_number(cp, &endptr, &c) != 0) {
|
|
log_debug("%s: invalid number", __func__);
|
|
return (NULL);
|
|
}
|
|
if (c > SIXEL_COLOUR_REGISTERS) {
|
|
log_debug("%s: too many colours", __func__);
|
|
return (NULL);
|
|
}
|
|
si->dc = c + 1;
|
|
if (endptr == last || *endptr != ';')
|
|
return (last);
|
|
|
|
if (sixel_parse_number(endptr + 1, &endptr, &type) != 0) {
|
|
log_debug("%s: invalid number", __func__);
|
|
return (NULL);
|
|
}
|
|
if (endptr == last || *endptr != ';') {
|
|
log_debug("%s: missing ;", __func__);
|
|
return (NULL);
|
|
}
|
|
if (sixel_parse_number(endptr + 1, &endptr, &c1) != 0) {
|
|
log_debug("%s: invalid number", __func__);
|
|
return (NULL);
|
|
}
|
|
if (endptr == last || *endptr != ';') {
|
|
log_debug("%s: missing ;", __func__);
|
|
return (NULL);
|
|
}
|
|
if (sixel_parse_number(endptr + 1, &endptr, &c2) != 0) {
|
|
log_debug("%s: invalid number", __func__);
|
|
return (NULL);
|
|
}
|
|
if (endptr == last || *endptr != ';') {
|
|
log_debug("%s: missing ;", __func__);
|
|
return (NULL);
|
|
}
|
|
if (sixel_parse_number(endptr + 1, &endptr, &c3) != 0) {
|
|
log_debug("%s: invalid number", __func__);
|
|
return (NULL);
|
|
}
|
|
if (endptr != last) {
|
|
log_debug("%s: missing ;", __func__);
|
|
return (NULL);
|
|
}
|
|
|
|
if ((type != 1 && type != 2) ||
|
|
(type == 1 && (c1 > 360 || c2 > 100 || c3 > 100)) ||
|
|
(type == 2 && (c1 > 100 || c2 > 100 || c3 > 100))) {
|
|
log_debug("%s: invalid color %u;%u;%u;%u", __func__, type,
|
|
c1, c2, c3);
|
|
return (NULL);
|
|
}
|
|
|
|
if (c + 1 > si->ncolours) {
|
|
si->colours = xrecallocarray(si->colours, si->ncolours, c + 1,
|
|
sizeof *si->colours);
|
|
si->ncolours = c + 1;
|
|
}
|
|
si->colours[c] = (type << 25) | (c1 << 16) | (c2 << 8) | c3;
|
|
return (last);
|
|
}
|
|
|
|
/* Parse a SIXEL repeat sequence. */
|
|
static const char *
|
|
sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end)
|
|
{
|
|
const char *last;
|
|
char tmp[32], ch;
|
|
u_int n = 0, i;
|
|
const char *errstr = NULL;
|
|
|
|
last = cp;
|
|
while (last != end) {
|
|
if (*last < '0' || *last > '9')
|
|
break;
|
|
tmp[n++] = *last++;
|
|
if (n == (sizeof tmp) - 1) {
|
|
log_debug("%s: repeat not terminated", __func__);
|
|
return (NULL);
|
|
}
|
|
}
|
|
if (n == 0 || last == end) {
|
|
log_debug("%s: repeat not terminated", __func__);
|
|
return (NULL);
|
|
}
|
|
tmp[n] = '\0';
|
|
|
|
n = strtonum(tmp, 1, SIXEL_WIDTH_LIMIT, &errstr);
|
|
if (n == 0 || errstr != NULL) {
|
|
log_debug("%s: repeat too wide", __func__);
|
|
return (NULL);
|
|
}
|
|
|
|
ch = (*last++) - 0x3f;
|
|
for (i = 0; i < n; i++) {
|
|
if (sixel_parse_write(si, ch) != 0) {
|
|
log_debug("%s: size limit reached", __func__);
|
|
return (NULL);
|
|
}
|
|
si->dx++;
|
|
}
|
|
return (last);
|
|
}
|
|
|
|
/* Parse SIXEL data into an indexed image. */
|
|
struct sixel_image *
|
|
sixel_parse(const char *buf, size_t len, u_int p1, u_int p2, u_int xpixel,
|
|
u_int ypixel)
|
|
{
|
|
struct sixel_image *si;
|
|
const char *cp = buf, *end = buf + len;
|
|
char ch;
|
|
|
|
if (len == 0 || len == 1 || *cp++ != 'q') {
|
|
log_debug("%s: empty image", __func__);
|
|
return (NULL);
|
|
}
|
|
|
|
si = xcalloc(1, sizeof *si);
|
|
si->xpixel = xpixel;
|
|
si->ypixel = ypixel;
|
|
si->p1 = p1;
|
|
si->p2 = p2;
|
|
|
|
while (cp != end) {
|
|
ch = *cp++;
|
|
switch (ch) {
|
|
case '"':
|
|
cp = sixel_parse_attributes(si, cp, end);
|
|
if (cp == NULL)
|
|
goto bad;
|
|
break;
|
|
case '#':
|
|
cp = sixel_parse_colour(si, cp, end);
|
|
if (cp == NULL)
|
|
goto bad;
|
|
break;
|
|
case '!':
|
|
cp = sixel_parse_repeat(si, cp, end);
|
|
if (cp == NULL)
|
|
goto bad;
|
|
break;
|
|
case '-':
|
|
si->dx = 0;
|
|
si->dy += 6;
|
|
break;
|
|
case '$':
|
|
si->dx = 0;
|
|
break;
|
|
default:
|
|
if (ch < 0x3f || ch > 0x7e)
|
|
break;
|
|
if (sixel_parse_write(si, ch - 0x3f) != 0) {
|
|
log_debug("%s: size limit reached", __func__);
|
|
goto bad;
|
|
}
|
|
si->dx++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (si->x == 0 || si->y == 0)
|
|
goto bad;
|
|
return (si);
|
|
|
|
bad:
|
|
sixel_free(si);
|
|
return (NULL);
|
|
}
|
|
|
|
/* Free a SIXEL image. */
|
|
void
|
|
sixel_free(struct sixel_image *si)
|
|
{
|
|
u_int y;
|
|
|
|
for (y = 0; y < si->y; y++)
|
|
free(si->lines[y].data);
|
|
free(si->lines);
|
|
|
|
free(si->colours);
|
|
free(si);
|
|
}
|
|
|
|
/* Get the size of a SIXEL image in cells. */
|
|
static void
|
|
sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y)
|
|
{
|
|
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);
|
|
}
|
|
|
|
/* Log a SIXEL image. */
|
|
void
|
|
sixel_log(struct sixel_image *si)
|
|
{
|
|
struct sixel_line *sl;
|
|
char s[SIXEL_WIDTH_LIMIT + 1];
|
|
u_int i, x, y, cx, cy;
|
|
|
|
sixel_size_in_cells(si, &cx, &cy);
|
|
log_debug("%s: image %ux%u (%ux%u)", __func__, si->x, si->y, cx, cy);
|
|
for (i = 0; i < si->ncolours; i++)
|
|
log_debug("%s: colour %u is %07x", __func__, i, si->colours[i]);
|
|
for (y = 0; y < si->y; y++) {
|
|
sl = &si->lines[y];
|
|
for (x = 0; x < si->x; x++) {
|
|
if (x >= sl->x)
|
|
s[x] = '_';
|
|
else if (sl->data[x] != 0)
|
|
s[x] = '0' + (sl->data[x] - 1) % 10;
|
|
else
|
|
s[x] = '.';
|
|
}
|
|
s[x] = '\0';
|
|
log_debug("%s: %4u: %s", __func__, y, s);
|
|
}
|
|
}
|
|
|
|
/* 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. / 6)
|
|
return (p + (q - p) * 6 * t);
|
|
if (t < 1. / 2)
|
|
return (q);
|
|
if (t < 2. / 3)
|
|
return (p + (q - p) * (2. / 3 - t) * 6);
|
|
return (p);
|
|
}
|
|
|
|
/* Convert a SIXEL colour register to RGB. */
|
|
static void
|
|
sixel_colour_to_rgb(u_int c, u_char *r, u_char *g, u_char *b)
|
|
{
|
|
u_int type = (c >> 25);
|
|
double h, l, s, p, q;
|
|
|
|
if (type == 2) {
|
|
*r = (((c >> 16) & 0xff) * 255 + 50) / 100;
|
|
*g = (((c >> 8) & 0xff) * 255 + 50) / 100;
|
|
*b = ((c & 0xff) * 255 + 50) / 100;
|
|
return;
|
|
}
|
|
if (type != 1) {
|
|
*r = *g = *b = 0;
|
|
return;
|
|
}
|
|
|
|
h = ((c >> 16) & 0x1ff) / 360.;
|
|
l = ((c >> 8) & 0xff) / 100.;
|
|
s = (c & 0xff) / 100.;
|
|
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. / 3) * 255 + 0.5;
|
|
*b = sixel_hue(p, q, h + 1. / 3) * 255 + 0.5;
|
|
}
|
|
|
|
/* Convert a SIXEL image into the common RGBA format. */
|
|
struct image *
|
|
sixel_to_image(struct sixel_image *si)
|
|
{
|
|
struct image *im;
|
|
u_char *pixels, *pixel, r, g, b;
|
|
u_int x, y, c, sx, sy;
|
|
|
|
if ((uint64_t)si->x * si->y > IMAGE_SIZE_LIMIT / 4)
|
|
return (NULL);
|
|
|
|
sixel_size_in_cells(si, &sx, &sy);
|
|
if ((uint64_t)sx * si->xpixel > UINT_MAX ||
|
|
(uint64_t)sy * si->ypixel > UINT_MAX)
|
|
return (NULL);
|
|
|
|
pixels = xcalloc(si->x * si->y, 4);
|
|
for (y = 0; y < si->y; y++) {
|
|
for (x = 0; x < si->x; x++) {
|
|
pixel = pixels + (y * si->x + x) * 4;
|
|
|
|
c = sixel_get_pixel(si, x, y);
|
|
if (c == 0) {
|
|
pixel[3] = (si->p2 == 1 ? 0 : 255);
|
|
continue;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
im = image_create(si->x, si->y, sx * si->xpixel, sy * si->ypixel, sx,
|
|
sy, pixels);
|
|
if (im == NULL)
|
|
free(pixels);
|
|
return (im);
|
|
}
|
|
|
|
/* Write a SIXEL image. */
|
|
void
|
|
sixel_write(struct screen_write_ctx *ctx, struct sixel_image *si, u_int bg)
|
|
{
|
|
struct image *im;
|
|
|
|
im = sixel_to_image(si);
|
|
sixel_free(si);
|
|
if (im == NULL)
|
|
return;
|
|
image_write(ctx, im, bg);
|
|
image_free(im);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
while (*used + slen + 1 > *len) {
|
|
*buf = xreallocarray(*buf, 2, *len);
|
|
(*len) *= 2;
|
|
}
|
|
memcpy(*buf + *used, s, slen);
|
|
(*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)
|
|
{
|
|
char tmp[16];
|
|
size_t tmplen;
|
|
|
|
if (count == 1)
|
|
sixel_print_add(buf, len, used, &ch, 1);
|
|
else if (count == 2) {
|
|
sixel_print_add(buf, len, used, &ch, 1);
|
|
sixel_print_add(buf, len, used, &ch, 1);
|
|
} else if (count == 3) {
|
|
sixel_print_add(buf, len, used, &ch, 1);
|
|
sixel_print_add(buf, len, used, &ch, 1);
|
|
sixel_print_add(buf, len, used, &ch, 1);
|
|
} else if (count != 0) {
|
|
tmplen = xsnprintf(tmp, sizeof tmp, "!%u%c", count, ch);
|
|
sixel_print_add(buf, len, used, tmp, tmplen);
|
|
}
|
|
}
|
|
|
|
/* Build compressed SIXEL output planes for a sixel row. */
|
|
static void
|
|
sixel_print_compress_colors(struct sixel_image *si, struct sixel_plane *planes,
|
|
u_int y, u_int *active, u_int *nactive)
|
|
{
|
|
u_int i, x, c, dx, pixels[6];
|
|
struct sixel_plane *plane = NULL;
|
|
struct sixel_line *sl;
|
|
|
|
for (x = 0; x < si->x; x++) {
|
|
/* Collect this column's six-bit pattern for each colour. */
|
|
for (i = 0; i < 6; i++) {
|
|
pixels[i] = 0;
|
|
if (y + i < si->y) {
|
|
sl = &si->lines[y + i];
|
|
if (x < sl->x && sl->data[x] != 0) {
|
|
pixels[i] = sl->data[x];
|
|
c = sl->data[x] - 1;
|
|
planes[c].next_pattern |= 1 << i;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Compress patterns and gaps in active colour planes. */
|
|
for (i = 0; i < 6; i++) {
|
|
if (pixels[i] == 0)
|
|
continue;
|
|
|
|
c = pixels[i] - 1;
|
|
plane = &planes[c];
|
|
if (plane->next_x == x + 1)
|
|
continue;
|
|
|
|
if (plane->next_y < y + 1) {
|
|
plane->next_y = y + 1;
|
|
active[(*nactive)++] = c;
|
|
}
|
|
|
|
dx = x - plane->next_x;
|
|
if (plane->pattern != plane->next_pattern || dx != 0) {
|
|
sixel_print_repeat(&plane->stream,
|
|
&plane->stream_len, &plane->stream_used,
|
|
plane->count, plane->pattern + 0x3f);
|
|
sixel_print_repeat(&plane->stream,
|
|
&plane->stream_len, &plane->stream_used,
|
|
dx, '?');
|
|
plane->pattern = plane->next_pattern;
|
|
plane->count = 0;
|
|
}
|
|
plane->count++;
|
|
plane->next_pattern = 0;
|
|
plane->next_x = x + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Encode an indexed SIXEL image for terminal output. */
|
|
static char *
|
|
sixel_print(struct sixel_image *si, size_t *size)
|
|
{
|
|
char *buf, tmp[64];
|
|
size_t len, used = 0, tmplen;
|
|
u_int used_colours, i, c, y;
|
|
u_int *active, nactive;
|
|
struct sixel_plane *planes, *plane;
|
|
|
|
used_colours = si->used_colours;
|
|
if (used_colours == 0)
|
|
return (NULL);
|
|
|
|
len = 8192;
|
|
buf = xmalloc(len);
|
|
|
|
tmplen = xsnprintf(tmp, sizeof tmp, "\033P%u;%uq", si->p1, si->p2);
|
|
sixel_print_add(&buf, &len, &used, tmp, tmplen);
|
|
|
|
if (si->set_ra) {
|
|
tmplen = xsnprintf(tmp, sizeof tmp, "\"1;1;%u;%u", si->ra_x,
|
|
si->ra_y);
|
|
sixel_print_add(&buf, &len, &used, tmp, tmplen);
|
|
}
|
|
|
|
/* Track colour planes containing pixels in the current six-row band. */
|
|
planes = xcalloc(used_colours, sizeof *planes);
|
|
active = xcalloc(used_colours, sizeof *active);
|
|
|
|
/* Emit the palette before the encoded colour planes. */
|
|
for (i = 0; i < si->ncolours; i++) {
|
|
c = si->colours[i];
|
|
tmplen = xsnprintf(tmp, sizeof tmp, "#%u;%u;%u;%u;%u",
|
|
i, c >> 25, (c >> 16) & 0x1ff, (c >> 8) & 0xff, c & 0xff);
|
|
sixel_print_add(&buf, &len, &used, tmp, tmplen);
|
|
}
|
|
|
|
for (i = 0; i < used_colours; i++) {
|
|
plane = &planes[i];
|
|
plane->stream_len = 8;
|
|
plane->stream = xmalloc(plane->stream_len);
|
|
}
|
|
|
|
/* Encode each six-row band using only its active colours. */
|
|
for (y = 0; y < si->y; y += 6) {
|
|
nactive = 0;
|
|
sixel_print_compress_colors(si, planes, y, active, &nactive);
|
|
|
|
for (i = 0; i < nactive; i++) {
|
|
c = active[i];
|
|
plane = &planes[c];
|
|
tmplen = xsnprintf(tmp, sizeof tmp, "#%u", c);
|
|
sixel_print_add(&buf, &len, &used, tmp, tmplen);
|
|
sixel_print_add(&buf, &len, &used, plane->stream,
|
|
plane->stream_used);
|
|
sixel_print_repeat(&buf, &len, &used, plane->count,
|
|
plane->pattern + 0x3f);
|
|
sixel_print_add(&buf, &len, &used, "$", 1);
|
|
plane->stream_used = plane->next_x = plane->count = 0;
|
|
}
|
|
|
|
if (buf[used - 1] == '$')
|
|
used--;
|
|
sixel_print_add(&buf, &len, &used, "-", 1);
|
|
}
|
|
if (buf[used - 1] == '-')
|
|
used--;
|
|
|
|
sixel_print_add(&buf, &len, &used, "\033\\", 2);
|
|
|
|
buf[used] = '\0';
|
|
if (size != NULL)
|
|
*size = used;
|
|
|
|
for (i = 0; i < used_colours; i++)
|
|
free(planes[i].stream);
|
|
free(active);
|
|
free(planes);
|
|
|
|
return (buf);
|
|
}
|
|
|
|
/* Update the occupied bounds and pixel count of a colour region. */
|
|
static void
|
|
sixel_box_update(struct sixel_box *box, struct sixel_hgram *hg)
|
|
{
|
|
struct sixel_hgram *entry;
|
|
u_int r, g, b, rmax = 0, gmax = 0, bmax = 0;
|
|
u_int rmin = SIXEL_HGRAM_LEVELS;
|
|
u_int gmin = SIXEL_HGRAM_LEVELS;
|
|
u_int bmin = SIXEL_HGRAM_LEVELS;
|
|
u_int count = 0;
|
|
|
|
for (r = box->rmin; r <= box->rmax; r++) {
|
|
for (g = box->gmin; g <= box->gmax; g++) {
|
|
for (b = box->bmin; b <= box->bmax; b++) {
|
|
entry = &hg[(r << 10)|(g << 5)|b];
|
|
if (entry->count != 0) {
|
|
if (r < rmin)
|
|
rmin = r;
|
|
if (r > rmax)
|
|
rmax = r;
|
|
if (g < gmin)
|
|
gmin = g;
|
|
if (g > gmax)
|
|
gmax = g;
|
|
if (b < bmin)
|
|
bmin = b;
|
|
if (b > bmax)
|
|
bmax = b;
|
|
count += entry->count;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
box->count = count;
|
|
if (count != 0) {
|
|
box->rmin = rmin;
|
|
box->rmax = rmax;
|
|
box->gmin = gmin;
|
|
box->gmax = gmax;
|
|
box->bmin = bmin;
|
|
box->bmax = bmax;
|
|
}
|
|
}
|
|
|
|
/* 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_HGRAM_LEVELS] = { 0 };
|
|
u_int r, g, b, index, channel, first, last, level;
|
|
u_int rrange, grange, brange, count = 0;
|
|
|
|
/* Split along the channel with the widest occupied range. */
|
|
rrange = box->rmax - box->rmin;
|
|
grange = box->gmax - box->gmin;
|
|
brange = box->bmax - box->bmin;
|
|
if (rrange == 0 && grange == 0 && brange == 0)
|
|
return (0);
|
|
if (grange >= rrange && grange >= brange)
|
|
channel = 1;
|
|
else if (rrange >= brange)
|
|
channel = 0;
|
|
else
|
|
channel = 2;
|
|
for (r = box->rmin; r <= box->rmax; r++) {
|
|
for (g = box->gmin; g <= box->gmax; g++) {
|
|
for (b = box->bmin; b <= box->bmax; b++) {
|
|
index = (r << 10)|(g << 5)|b;
|
|
if (channel == 0)
|
|
levels[r] += hg[index].count;
|
|
else if (channel == 1)
|
|
levels[g] += hg[index].count;
|
|
else
|
|
levels[b] += hg[index].count;
|
|
}
|
|
}
|
|
}
|
|
if (channel == 0) {
|
|
first = box->rmin;
|
|
last = box->rmax;
|
|
} else if (channel == 1) {
|
|
first = box->gmin;
|
|
last = box->gmax;
|
|
} else {
|
|
first = box->bmin;
|
|
last = box->bmax;
|
|
}
|
|
|
|
/* Find the weighted median without emptying either resulting box. */
|
|
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->rmax = level;
|
|
new->rmin = level + 1;
|
|
} else if (channel == 1) {
|
|
box->gmax = level;
|
|
new->gmin = level + 1;
|
|
} else {
|
|
box->bmax = level;
|
|
new->bmin = 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, rsum, gsum, bsum, count;
|
|
u_int i, nbox = 1, best, r, g, b, index;
|
|
u_int rrange, grange, brange;
|
|
|
|
/* Initialize the first box. */
|
|
memset(&boxes[0], 0, sizeof boxes[0]);
|
|
boxes[0].rmax = boxes[0].gmax = boxes[0].bmax = SIXEL_HGRAM_LEVELS - 1;
|
|
sixel_box_update(&boxes[0], hg);
|
|
if (boxes[0].count == 0)
|
|
return (0);
|
|
|
|
/* Repeatedly split the most populated and varied colour region. */
|
|
while (nbox < SIXEL_PALETTE_SIZE) {
|
|
best = nbox;
|
|
best_score = 0;
|
|
for (i = 0; i < nbox; i++) {
|
|
box = &boxes[i];
|
|
rrange = box->rmax - box->rmin;
|
|
grange = box->gmax - box->gmin;
|
|
brange = box->bmax - box->bmin;
|
|
score = (uint64_t)box->count * (rrange * rrange +
|
|
grange * grange + brange * brange);
|
|
if (score > best_score) {
|
|
best = i;
|
|
best_score = score;
|
|
}
|
|
}
|
|
if (best == nbox || !sixel_box_split(&boxes[best], &new, hg))
|
|
break;
|
|
memcpy(&boxes[nbox++], &new, sizeof new);
|
|
}
|
|
|
|
/* Average each region's colours into one palette entry. */
|
|
for (i = 0; i < nbox; i++) {
|
|
box = &boxes[i];
|
|
|
|
rsum = gsum = bsum = count = 0;
|
|
for (r = box->rmin; r <= box->rmax; r++) {
|
|
for (g = box->gmin; g <= box->gmax; g++) {
|
|
for (b = box->bmin; b <= box->bmax; b++) {
|
|
index = (r << 10)|(g << 5)|b;
|
|
rsum += hg[index].r;
|
|
gsum += hg[index].g;
|
|
bsum += hg[index].b;
|
|
count += hg[index].count;
|
|
}
|
|
}
|
|
}
|
|
palette[i].r = (rsum + count / 2) / count;
|
|
palette[i].g = (gsum + count / 2) / count;
|
|
palette[i].b = (bsum + count / 2) / count;
|
|
}
|
|
|
|
return (nbox);
|
|
}
|
|
|
|
/* 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 r, u_int g, u_int b)
|
|
{
|
|
uint64_t distance, best_distance = UINT64_MAX;
|
|
int dr, dg, db;
|
|
u_int i, best = 0, index;
|
|
|
|
index = ((r >> 3) << 10)|((g >> 3) << 5)|(b >> 3);
|
|
if (cache[index] != UINT16_MAX)
|
|
return (cache[index]);
|
|
for (i = 0; i < ncolours; i++) {
|
|
dr = (int)r - palette[i].r;
|
|
dg = (int)g - palette[i].g;
|
|
db = (int)b - palette[i].b;
|
|
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 *src, u_int x, u_int y)
|
|
{
|
|
u_int px, py;
|
|
|
|
px = ((uint64_t)src->dst_x + x) * src->canvas_width / src->dst_w;
|
|
py = ((uint64_t)src->dst_y + y) * src->canvas_height / src->dst_h;
|
|
return (src->pixels + py * src->stride + px * 4);
|
|
}
|
|
|
|
/* Make a palette from the visible pixels in the output area. */
|
|
static u_int
|
|
sixel_make_image_palette(struct sixel_source *src, struct sixel_rgb *palette)
|
|
{
|
|
struct sixel_hgram *hg, *entry;
|
|
const u_char *pixel;
|
|
u_int x, y, index, ncolours;
|
|
|
|
hg = xcalloc(SIXEL_HGRAM_SIZE, sizeof *hg);
|
|
for (y = 0; y < src->out_h; y++) {
|
|
for (x = 0; x < src->out_w; x++) {
|
|
pixel = sixel_from_image_pixel(src, x, y);
|
|
if (pixel[3] == 0)
|
|
continue;
|
|
|
|
index = ((pixel[0] >> 3) << 10)| ((pixel[1] >> 3) << 5)|
|
|
(pixel[2] >> 3);
|
|
entry = &hg[index];
|
|
entry->count++;
|
|
entry->r += pixel[0];
|
|
entry->g += pixel[1];
|
|
entry->b += pixel[2];
|
|
}
|
|
}
|
|
ncolours = sixel_make_palette(hg, palette);
|
|
free(hg);
|
|
return (ncolours);
|
|
}
|
|
|
|
/* Choose an indexed colour for a pixel and diffuse it. */
|
|
static u_int
|
|
sixel_dither_pixel(struct sixel_rgb *palette, u_int ncolours, uint16_t *cache,
|
|
const u_char *pixel, int *current, int *next)
|
|
{
|
|
int alphaerr, rerr, gerr, berr;
|
|
u_int alpha, r, g, b, c;
|
|
|
|
/* Dither alpha to SIXEL's binary transparency. */
|
|
alpha = sixel_clamp_colour((int)pixel[3] + current[3] / 16);
|
|
alphaerr = alpha;
|
|
if (alpha >= 128)
|
|
alphaerr -= 255;
|
|
current[7] += alphaerr * 7;
|
|
next[-1] += alphaerr * 3;
|
|
next[3] += alphaerr * 5;
|
|
next[7] += alphaerr;
|
|
if (alpha < 128)
|
|
return (0);
|
|
|
|
/* Get the RGB colours. */
|
|
r = sixel_clamp_colour((int)pixel[0] + current[0] / 16);
|
|
g = sixel_clamp_colour((int)pixel[1] + current[1] / 16);
|
|
b = sixel_clamp_colour((int)pixel[2] + current[2] / 16);
|
|
c = sixel_nearest_colour(palette, ncolours, cache, r, g, b);
|
|
|
|
/* Find the colour error. */
|
|
rerr = (int)r - palette[c].r;
|
|
gerr = (int)g - palette[c].g;
|
|
berr = (int)b - palette[c].b;
|
|
|
|
/* Diffuse error with the 7:3:5:1 kernel. */
|
|
current[4] += rerr * 7;
|
|
current[5] += gerr * 7;
|
|
current[6] += berr * 7;
|
|
next[-4] += rerr * 3;
|
|
next[-3] += gerr * 3;
|
|
next[-2] += berr * 3;
|
|
next[0] += rerr * 5;
|
|
next[1] += gerr * 5;
|
|
next[2] += berr * 5;
|
|
next[4] += rerr;
|
|
next[5] += gerr;
|
|
next[6] += berr;
|
|
return (c + 1);
|
|
}
|
|
|
|
/* Make an indexed SIXEL image from part of an image. */
|
|
static struct sixel_image *
|
|
sixel_make_image(struct image *im, u_int cx, u_int cy, u_int csx, u_int csy,
|
|
u_int cw, u_int ch)
|
|
{
|
|
struct sixel_image *si;
|
|
struct sixel_rgb palette[SIXEL_PALETTE_SIZE];
|
|
struct sixel_source src;
|
|
const u_char *pixel;
|
|
uint16_t *cache;
|
|
int *current, *next, *tmp;
|
|
u_int x, y, sx, sy, error_index;
|
|
u_int r, g, b, c, i, nc;
|
|
uint64_t dst_w, dst_h, content_width, content_height;
|
|
uint64_t left, right, top, bottom;
|
|
|
|
/* Work out the requested cell crop in destination pixel coordinates. */
|
|
src.pixels = image_get_pixels(im, &src.stride, NULL);
|
|
image_get_size(im, &src.width, &src.height);
|
|
image_get_canvas_size(im, &src.canvas_width, &src.canvas_height);
|
|
image_get_size_in_cells(im, &src.sx, &src.sy);
|
|
dst_w = (uint64_t)src.sx * cw;
|
|
dst_h = (uint64_t)src.sy * ch;
|
|
if (dst_w > UINT_MAX || dst_h > UINT_MAX)
|
|
return (NULL);
|
|
content_width = ((uint64_t)src.width * dst_w +
|
|
src.canvas_width - 1) / src.canvas_width;
|
|
content_height = ((uint64_t)src.height * dst_h +
|
|
src.canvas_height - 1) / src.canvas_height;
|
|
|
|
/* Clip the requested cell rectangle to output pixel bounds. */
|
|
left = (uint64_t)cx * cw;
|
|
top = (uint64_t)cy * ch;
|
|
right = ((uint64_t)cx + csx) * cw;
|
|
bottom = ((uint64_t)cy + csy) * ch;
|
|
if (right > content_width)
|
|
right = content_width;
|
|
if (bottom > content_height)
|
|
bottom = content_height;
|
|
if (right <= left || bottom <= top)
|
|
return (NULL);
|
|
|
|
/* The clipped output bounds determine the SIXEL image dimensions. */
|
|
sx = right - left;
|
|
sy = bottom - top;
|
|
if (sx == 0 ||
|
|
sy == 0 ||
|
|
sx > SIXEL_WIDTH_LIMIT ||
|
|
sy > SIXEL_HEIGHT_LIMIT) {
|
|
return (NULL);
|
|
}
|
|
if (sx * sy > IMAGE_SIZE_LIMIT / 4)
|
|
return (NULL);
|
|
|
|
/* Map output pixels using their position in the full canvas. */
|
|
src.dst_w = dst_w;
|
|
src.dst_h = dst_h;
|
|
src.dst_x = left;
|
|
src.dst_y = top;
|
|
src.out_w = sx;
|
|
src.out_h = sy;
|
|
|
|
/* Build an adaptive palette from the visible nontransparent pixels. */
|
|
nc = sixel_make_image_palette(&src, palette);
|
|
if (nc == 0)
|
|
return (NULL);
|
|
|
|
/* Create the indexed image with a SIXEL RGB palette. */
|
|
si = xcalloc(1, sizeof *si);
|
|
si->xpixel = cw;
|
|
si->ypixel = ch;
|
|
si->p1 = 9;
|
|
si->p2 = 1;
|
|
si->set_ra = 1;
|
|
si->ra_x = sx;
|
|
si->ra_y = sy;
|
|
si->ncolours = si->used_colours = nc;
|
|
si->colours = xcalloc(si->ncolours, sizeof *si->colours);
|
|
for (i = 0; i < si->ncolours; i++) {
|
|
r = (palette[i].r * 100 + 127) / 255;
|
|
g = (palette[i].g * 100 + 127) / 255;
|
|
b = (palette[i].b * 100 + 127) / 255;
|
|
si->colours[i] = (2U << 25)|(r << 16)|(g << 8)|b;
|
|
}
|
|
|
|
/* Dither colour and alpha into the indexed image. */
|
|
cache = xmalloc(SIXEL_HGRAM_SIZE * sizeof *cache);
|
|
memset(cache, 0xff, SIXEL_HGRAM_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(&src, x, y);
|
|
error_index = (x + 1) * 4;
|
|
c = sixel_dither_pixel(palette, nc, cache, pixel,
|
|
current + error_index, next + error_index);
|
|
if (c != 0 && sixel_set_pixel(si, x, y, c) != 0)
|
|
goto fail;
|
|
}
|
|
|
|
/* Advance to the next output row's accumulated error. */
|
|
tmp = current;
|
|
current = next;
|
|
next = tmp;
|
|
memset(next, 0, ((size_t)sx + 2) * 4 * sizeof *next);
|
|
}
|
|
|
|
free(current);
|
|
free(next);
|
|
free(cache);
|
|
return (si);
|
|
|
|
fail:
|
|
free(current);
|
|
free(next);
|
|
free(cache);
|
|
sixel_free(si);
|
|
return (NULL);
|
|
}
|
|
|
|
/* Encode part of an image as SIXEL for a terminal cell size. */
|
|
char *
|
|
sixel_from_image(struct image *im, u_int x, u_int y, u_int sx, u_int sy,
|
|
u_int cw, u_int ch, size_t *size)
|
|
{
|
|
struct sixel_image *si;
|
|
char *data;
|
|
|
|
si = sixel_make_image(im, x, y, sx, sy, cw, ch);
|
|
if (si == NULL)
|
|
return (NULL);
|
|
data = sixel_print(si, size);
|
|
sixel_free(si);
|
|
return (data);
|
|
}
|
|
|
|
/* Return whether the terminal supports SIXEL. */
|
|
int
|
|
sixel_select(struct tty *tty)
|
|
{
|
|
if (tty->term != NULL &&
|
|
(tty->term->flags & TERM_SIXEL) &&
|
|
tty->xpixel != 0 &&
|
|
tty->ypixel != 0)
|
|
return (1);
|
|
return (0);
|
|
}
|
|
|
|
/* Get pending SIXEL rows for a terminal. */
|
|
static struct sixel_pending *
|
|
sixel_get_pending(struct tty *tty)
|
|
{
|
|
struct sixel_pending *sp = tty->image_data;
|
|
|
|
if (sp == NULL) {
|
|
sp = xcalloc(1, sizeof *sp);
|
|
tty->image_data = sp;
|
|
}
|
|
return (sp);
|
|
}
|
|
|
|
/* Free pending SIXEL rows for a terminal. */
|
|
void
|
|
sixel_free_pending(struct tty *tty)
|
|
{
|
|
struct sixel_pending *sp = tty->image_data;
|
|
|
|
if (sp == NULL)
|
|
return;
|
|
if (sp->image != NULL)
|
|
image_free(sp->image);
|
|
free(sp);
|
|
tty->image_data = NULL;
|
|
}
|
|
|
|
/* Write the rows held in the pending run as a single SIXEL. */
|
|
void
|
|
sixel_flush_pending(struct tty *tty)
|
|
{
|
|
struct sixel_pending *sp = tty->image_data;
|
|
struct image *im;
|
|
char *data;
|
|
size_t size;
|
|
|
|
if (sp == NULL || sp->image == NULL)
|
|
return;
|
|
im = sp->image;
|
|
sp->image = NULL;
|
|
|
|
data = sixel_from_image(im, sp->src_x, sp->src_y, sp->w, sp->h,
|
|
tty->xpixel, tty->ypixel, &size);
|
|
if (data != NULL) {
|
|
tty_region_off(tty);
|
|
tty_margin_off(tty);
|
|
tty_cursor(tty, sp->dst_x, sp->dst_y);
|
|
|
|
tty->flags |= TTY_NOBLOCK;
|
|
tty_putn(tty, data, size, 0);
|
|
tty->cx = tty->cy = UINT_MAX;
|
|
|
|
free(data);
|
|
}
|
|
image_free(im);
|
|
}
|
|
|
|
/* Queue adjacent placement rows for one SIXEL output sequence. */
|
|
void
|
|
sixel_draw_rect(struct tty *tty, struct image_rect *rect)
|
|
{
|
|
struct sixel_pending *sp = sixel_get_pending(tty);
|
|
|
|
if (sp->image == rect->image &&
|
|
sp->src_x == rect->src_x &&
|
|
sp->w == rect->sx &&
|
|
sp->dst_x == rect->dst_x &&
|
|
sp->src_y + sp->h == rect->src_y &&
|
|
sp->dst_y + sp->h == rect->dst_y) {
|
|
sp->h += rect->sy;
|
|
return;
|
|
}
|
|
|
|
image_ref(rect->image);
|
|
sixel_flush_pending(tty);
|
|
|
|
sp->image = rect->image;
|
|
sp->src_x = rect->src_x;
|
|
sp->src_y = rect->src_y;
|
|
sp->w = rect->sx;
|
|
sp->h = rect->sy;
|
|
sp->dst_x = rect->dst_x;
|
|
sp->dst_y = rect->dst_y;
|
|
}
|
|
|
|
/* Remove old SIXEL pixels before drawing an image area. */
|
|
void
|
|
sixel_start_draw(struct tty *tty, u_int x, u_int y, u_int sx, u_int sy)
|
|
{
|
|
u_int yy;
|
|
|
|
for (yy = y; yy < y + sy; yy++) {
|
|
tty_cursor(tty, x, yy);
|
|
if (tty_term_has(tty->term, TTYC_ECH))
|
|
tty_putcode_i(tty, TTYC_ECH, sx);
|
|
else
|
|
tty_repeat_space(tty, sx);
|
|
}
|
|
}
|