st/sixel.c
veltza 118e965d0c
sixel: add support for fully transparent bg (P2=1) (#132)
P2 selects how the terminal draws the background color.

P2                  Meaning
0 or 2 (default)    Pixel positions specified as 0 are set to the
                    current background color.
1                   Pixel positions specified as 0 remain at their
                    current color.

Both modes are now supported.

Ref. https://www.vt100.net/docs/vt3xx-gp/chapter14.html
2024-04-17 18:04:27 +02:00

693 lines
16 KiB
C

// sixel.c (part of mintty)
// originally written by kmiya@cluti (https://github.com/saitoha/sixel/blob/master/fromsixel.c)
// Licensed under the terms of the GNU General Public License v3 or later.
#include <stdlib.h>
#include <string.h> /* memcpy */
#include "st.h"
#include "win.h"
#include "sixel.h"
#include "sixel_hls.h"
#define SIXEL_RGB(r, g, b) ((255 << 24) + ((r) << 16) + ((g) << 8) + (b))
#define SIXEL_PALVAL(n,a,m) (((n) * (a) + ((m) / 2)) / (m))
#define SIXEL_XRGB(r,g,b) SIXEL_RGB(SIXEL_PALVAL(r, 255, 100), SIXEL_PALVAL(g, 255, 100), SIXEL_PALVAL(b, 255, 100))
static sixel_color_t const sixel_default_color_table[] = {
SIXEL_XRGB( 0, 0, 0), /* 0 Black */
SIXEL_XRGB(20, 20, 80), /* 1 Blue */
SIXEL_XRGB(80, 13, 13), /* 2 Red */
SIXEL_XRGB(20, 80, 20), /* 3 Green */
SIXEL_XRGB(80, 20, 80), /* 4 Magenta */
SIXEL_XRGB(20, 80, 80), /* 5 Cyan */
SIXEL_XRGB(80, 80, 20), /* 6 Yellow */
SIXEL_XRGB(53, 53, 53), /* 7 Gray 50% */
SIXEL_XRGB(26, 26, 26), /* 8 Gray 25% */
SIXEL_XRGB(33, 33, 60), /* 9 Blue* */
SIXEL_XRGB(60, 26, 26), /* 10 Red* */
SIXEL_XRGB(33, 60, 33), /* 11 Green* */
SIXEL_XRGB(60, 33, 60), /* 12 Magenta* */
SIXEL_XRGB(33, 60, 60), /* 13 Cyan* */
SIXEL_XRGB(60, 60, 33), /* 14 Yellow* */
SIXEL_XRGB(80, 80, 80), /* 15 Gray 75% */
};
void
scroll_images(int n) {
ImageList *im, *next;
#if SCROLLBACK_PATCH || REFLOW_PATCH
int top = tisaltscr() ? 0 : term.scr - HISTSIZE;
#else
int top = 0;
#endif // SCROLLBACK_PATCH
for (im = term.images; im; im = next) {
next = im->next;
im->y += n;
/* check if the current sixel has exceeded the maximum
* draw distance, and should therefore be deleted */
if (im->y < top) {
//fprintf(stderr, "im@0x%08x exceeded maximum distance\n");
delete_image(im);
}
}
}
void
delete_image(ImageList *im)
{
if (im->prev)
im->prev->next = im->next;
else
term.images = im->next;
if (im->next)
im->next->prev = im->prev;
if (im->pixmap)
XFreePixmap(xw.dpy, (Drawable)im->pixmap);
if (im->clipmask)
XFreePixmap(xw.dpy, (Drawable)im->clipmask);
free(im->pixels);
free(im);
}
static int
set_default_color(sixel_image_t *image)
{
int i;
int n;
int r;
int g;
int b;
/* palette initialization */
for (n = 1; n < 17; n++) {
image->palette[n] = sixel_default_color_table[n - 1];
}
/* colors 17-232 are a 6x6x6 color cube */
for (r = 0; r < 6; r++) {
for (g = 0; g < 6; g++) {
for (b = 0; b < 6; b++) {
image->palette[n++] = SIXEL_RGB(r * 51, g * 51, b * 51);
}
}
}
/* colors 233-256 are a grayscale ramp, intentionally leaving out */
for (i = 0; i < 24; i++) {
image->palette[n++] = SIXEL_RGB(i * 11, i * 11, i * 11);
}
for (; n < DECSIXEL_PALETTE_MAX; n++) {
image->palette[n] = SIXEL_RGB(255, 255, 255);
}
return (0);
}
static int
sixel_image_init(
sixel_image_t *image,
int width,
int height,
int fgcolor,
int bgcolor,
int use_private_register)
{
int status = (-1);
size_t size;
size = (size_t)(width * height) * sizeof(sixel_color_no_t);
image->width = width;
image->height = height;
image->data = (sixel_color_no_t *)malloc(size);
image->ncolors = 2;
image->use_private_register = use_private_register;
if (image->data == NULL) {
status = (-1);
goto end;
}
memset(image->data, 0, size);
image->palette[0] = bgcolor;
if (image->use_private_register)
image->palette[1] = fgcolor;
image->palette_modified = 0;
status = (0);
end:
return status;
}
static int
image_buffer_resize(
sixel_image_t *image,
int width,
int height)
{
int status = (-1);
size_t size;
sixel_color_no_t *alt_buffer;
int n;
int min_height;
size = (size_t)(width * height) * sizeof(sixel_color_no_t);
alt_buffer = (sixel_color_no_t *)malloc(size);
if (alt_buffer == NULL) {
/* free source image */
free(image->data);
image->data = NULL;
status = (-1);
goto end;
}
min_height = height > image->height ? image->height: height;
if (width > image->width) { /* if width is extended */
for (n = 0; n < min_height; ++n) {
/* copy from source image */
memcpy(alt_buffer + width * n,
image->data + image->width * n,
(size_t)image->width * sizeof(sixel_color_no_t));
/* fill extended area with background color */
memset(alt_buffer + width * n + image->width,
0,
(size_t)(width - image->width) * sizeof(sixel_color_no_t));
}
} else {
for (n = 0; n < min_height; ++n) {
/* copy from source image */
memcpy(alt_buffer + width * n,
image->data + image->width * n,
(size_t)width * sizeof(sixel_color_no_t));
}
}
if (height > image->height) { /* if height is extended */
/* fill extended area with background color */
memset(alt_buffer + width * image->height,
0,
(size_t)(width * (height - image->height)) * sizeof(sixel_color_no_t));
}
/* free source image */
free(image->data);
image->data = alt_buffer;
image->width = width;
image->height = height;
status = (0);
end:
return status;
}
static void
sixel_image_deinit(sixel_image_t *image)
{
if (image->data)
free(image->data);
image->data = NULL;
}
int
sixel_parser_init(sixel_state_t *st,
int transparent,
sixel_color_t fgcolor, sixel_color_t bgcolor,
unsigned char use_private_register,
int cell_width, int cell_height)
{
int status = (-1);
st->state = PS_DECSIXEL;
st->pos_x = 0;
st->pos_y = 0;
st->max_x = 0;
st->max_y = 0;
st->attributed_pan = 2;
st->attributed_pad = 1;
st->attributed_ph = 0;
st->attributed_pv = 0;
st->transparent = transparent;
st->repeat_count = 1;
st->color_index = 16;
st->grid_width = cell_width;
st->grid_height = cell_height;
st->nparams = 0;
st->param = 0;
/* buffer initialization */
status = sixel_image_init(&st->image, 1, 1, fgcolor, transparent ? 0 : bgcolor, use_private_register);
return status;
}
int
sixel_parser_set_default_color(sixel_state_t *st)
{
return set_default_color(&st->image);
}
int
sixel_parser_finalize(sixel_state_t *st, ImageList **newimages, int cx, int cy, int cw, int ch)
{
sixel_image_t *image = &st->image;
int x, y;
sixel_color_no_t *src;
sixel_color_t *dst;
int color;
int w, h;
int i, j, cols, numimages;
ImageList *im, *next, *tail;
if (!image->data)
return -1;
if (++st->max_x < st->attributed_ph)
st->max_x = st->attributed_ph;
if (++st->max_y < st->attributed_pv)
st->max_y = st->attributed_pv;
if (image->use_private_register && image->ncolors > 2 && !image->palette_modified) {
if (set_default_color(image) < 0)
return -1;
}
w = MIN(st->max_x, image->width);
h = MIN(st->max_y, image->height);
if ((numimages = (h + ch-1) / ch) <= 0)
return -1;
cols = (w + cw-1) / cw;
*newimages = NULL, tail = NULL;
for (y = 0, i = 0; i < numimages; i++) {
if ((im = malloc(sizeof(ImageList)))) {
if (!tail) {
*newimages = tail = im;
im->prev = im->next = NULL;
} else {
tail->next = im;
im->prev = tail;
im->next = NULL;
tail = im;
}
im->x = cx;
im->y = cy + i;
im->cols = cols;
im->width = w;
im->height = MIN(h - ch * i, ch);
im->pixels = malloc(im->width * im->height * 4);
im->pixmap = NULL;
im->clipmask = NULL;
im->cw = cw;
im->ch = ch;
im->transparent = st->transparent;
}
if (!im || !im->pixels) {
for (im = *newimages; im; im = next) {
next = im->next;
if (im->pixels)
free(im->pixels);
free(im);
}
*newimages = NULL;
return -1;
}
dst = (sixel_color_t *)im->pixels;
for (j = 0; j < im->height && y < h; j++, y++) {
src = st->image.data + image->width * y;
for (x = 0; x < w; x++)
*dst++ = st->image.palette[*src++];
}
}
return numimages;
}
/* convert sixel data into indexed pixel bytes and palette data */
int
sixel_parser_parse(sixel_state_t *st, const unsigned char *p, size_t len)
{
int n = 0;
int i;
int x;
int y;
int bits;
int sx;
int sy;
int c;
int pos;
int width;
const unsigned char *p0 = p, *p2 = p + len;
sixel_image_t *image = &st->image;
sixel_color_no_t *data, color_index;
if (!image->data)
st->state = PS_ERROR;
while (p < p2) {
switch (st->state) {
case PS_ESC:
goto end;
case PS_DECSIXEL:
switch (*p) {
case '\x1b':
st->state = PS_ESC;
break;
case '"':
st->param = 0;
st->nparams = 0;
st->state = PS_DECGRA;
p++;
break;
case '!':
st->param = 0;
st->nparams = 0;
st->state = PS_DECGRI;
p++;
break;
case '#':
st->param = 0;
st->nparams = 0;
st->state = PS_DECGCI;
p++;
break;
case '$':
/* DECGCR Graphics Carriage Return */
st->pos_x = 0;
p++;
break;
case '-':
/* DECGNL Graphics Next Line */
st->pos_x = 0;
if (st->pos_y < DECSIXEL_HEIGHT_MAX - 5 - 6)
st->pos_y += 6;
else
st->pos_y = DECSIXEL_HEIGHT_MAX + 1;
p++;
break;
default:
if (*p >= '?' && *p <= '~') { /* sixel characters */
if ((image->width < (st->pos_x + st->repeat_count) || image->height < (st->pos_y + 6))
&& image->width < DECSIXEL_WIDTH_MAX && image->height < DECSIXEL_HEIGHT_MAX) {
sx = image->width * 2;
sy = image->height * 2;
while (sx < (st->pos_x + st->repeat_count) || sy < (st->pos_y + 6)) {
sx *= 2;
sy *= 2;
}
sx = MIN(sx, DECSIXEL_WIDTH_MAX);
sy = MIN(sy, DECSIXEL_HEIGHT_MAX);
if (image_buffer_resize(image, sx, sy) < 0) {
perror("sixel_parser_parse() failed");
st->state = PS_ERROR;
p++;
break;
}
}
if (st->color_index > image->ncolors)
image->ncolors = st->color_index;
if (st->pos_x + st->repeat_count > image->width)
st->repeat_count = image->width - st->pos_x;
if (st->repeat_count > 0 && st->pos_y + 5 < image->height) {
bits = *p - '?';
if (bits != 0) {
data = image->data + image->width * st->pos_y + st->pos_x;
width = image->width;
color_index = st->color_index;
if (st->repeat_count <= 1) {
if (bits & 0x01)
*data = color_index, n = 0;
data += width;
if (bits & 0x02)
*data = color_index, n = 1;
data += width;
if (bits & 0x04)
*data = color_index, n = 2;
data += width;
if (bits & 0x08)
*data = color_index, n = 3;
data += width;
if (bits & 0x10)
*data = color_index, n = 4;
if (bits & 0x20)
data[width] = color_index, n = 5;
if (st->max_x < st->pos_x)
st->max_x = st->pos_x;
} else {
/* st->repeat_count > 1 */
for (i = 0; bits; bits >>= 1, i++, data += width) {
if (bits & 1) {
data[0] = color_index;
data[1] = color_index;
for (x = 2; x < st->repeat_count; x++)
data[x] = color_index;
n = i;
}
}
if (st->max_x < (st->pos_x + st->repeat_count - 1))
st->max_x = st->pos_x + st->repeat_count - 1;
}
if (st->max_y < (st->pos_y + n))
st->max_y = st->pos_y + n;
}
}
if (st->repeat_count > 0)
st->pos_x += st->repeat_count;
st->repeat_count = 1;
}
p++;
break;
}
break;
case PS_DECGRA:
/* DECGRA Set Raster Attributes " Pan; Pad; Ph; Pv */
switch (*p) {
case '\x1b':
st->state = PS_ESC;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
st->param = st->param * 10 + *p - '0';
st->param = MIN(st->param, DECSIXEL_PARAMVALUE_MAX);
p++;
break;
case ';':
if (st->nparams < DECSIXEL_PARAMS_MAX)
st->params[st->nparams++] = st->param;
st->param = 0;
p++;
break;
default:
if (st->nparams < DECSIXEL_PARAMS_MAX)
st->params[st->nparams++] = st->param;
if (st->nparams > 0)
st->attributed_pad = st->params[0];
if (st->nparams > 1)
st->attributed_pan = st->params[1];
if (st->nparams > 2 && st->params[2] > 0)
st->attributed_ph = st->params[2];
if (st->nparams > 3 && st->params[3] > 0)
st->attributed_pv = st->params[3];
if (st->attributed_pan <= 0)
st->attributed_pan = 1;
if (st->attributed_pad <= 0)
st->attributed_pad = 1;
if (image->width < st->attributed_ph ||
image->height < st->attributed_pv) {
sx = MAX(image->width, st->attributed_ph);
sy = MAX(image->height, st->attributed_pv);
/* the height of the image buffer must be divisible by 6
* to avoid unnecessary resizing of the image buffer when
* parsing the last sixel line */
sy = (sy + 5) / 6 * 6;
sx = MIN(sx, DECSIXEL_WIDTH_MAX);
sy = MIN(sy, DECSIXEL_HEIGHT_MAX);
if (image_buffer_resize(image, sx, sy) < 0) {
perror("sixel_parser_parse() failed");
st->state = PS_ERROR;
break;
}
}
st->state = PS_DECSIXEL;
st->param = 0;
st->nparams = 0;
}
break;
case PS_DECGRI:
/* DECGRI Graphics Repeat Introducer ! Pn Ch */
switch (*p) {
case '\x1b':
st->state = PS_ESC;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
st->param = st->param * 10 + *p - '0';
st->param = MIN(st->param, DECSIXEL_PARAMVALUE_MAX);
p++;
break;
default:
st->repeat_count = MAX(st->param, 1);
st->state = PS_DECSIXEL;
st->param = 0;
st->nparams = 0;
break;
}
break;
case PS_DECGCI:
/* DECGCI Graphics Color Introducer # Pc; Pu; Px; Py; Pz */
switch (*p) {
case '\x1b':
st->state = PS_ESC;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
st->param = st->param * 10 + *p - '0';
st->param = MIN(st->param, DECSIXEL_PARAMVALUE_MAX);
p++;
break;
case ';':
if (st->nparams < DECSIXEL_PARAMS_MAX)
st->params[st->nparams++] = st->param;
st->param = 0;
p++;
break;
default:
st->state = PS_DECSIXEL;
if (st->nparams < DECSIXEL_PARAMS_MAX)
st->params[st->nparams++] = st->param;
st->param = 0;
if (st->nparams > 0) {
st->color_index = 1 + st->params[0]; /* offset 1(background color) added */
if (st->color_index < 0)
st->color_index = 0;
else if (st->color_index >= DECSIXEL_PALETTE_MAX)
st->color_index = DECSIXEL_PALETTE_MAX - 1;
}
if (st->nparams > 4) {
st->image.palette_modified = 1;
if (st->params[1] == 1) {
/* HLS */
st->params[2] = MIN(st->params[2], 360);
st->params[3] = MIN(st->params[3], 100);
st->params[4] = MIN(st->params[4], 100);
image->palette[st->color_index]
= hls_to_rgb(st->params[2], st->params[3], st->params[4]);
} else if (st->params[1] == 2) {
/* RGB */
st->params[2] = MIN(st->params[2], 100);
st->params[3] = MIN(st->params[3], 100);
st->params[4] = MIN(st->params[4], 100);
image->palette[st->color_index]
= SIXEL_XRGB(st->params[2], st->params[3], st->params[4]);
}
}
break;
}
break;
case PS_ERROR:
if (*p == '\x1b') {
st->state = PS_ESC;
goto end;
}
p++;
break;
default:
break;
}
}
end:
return p - p0;
}
void
sixel_parser_deinit(sixel_state_t *st)
{
if (st)
sixel_image_deinit(&st->image);
}
Pixmap
sixel_create_clipmask(char *pixels, int width, int height)
{
char c, *clipdata, *dst;
int b, i, n, y, w;
int msb = (XBitmapBitOrder(xw.dpy) == MSBFirst);
sixel_color_t *src = (sixel_color_t *)pixels;
Pixmap clipmask;
clipdata = dst = malloc((width+7)/8 * height);
if (!clipdata)
return (Pixmap)None;
for (y = 0; y < height; y++) {
for (w = width; w > 0; w -= n) {
n = MIN(w, 8);
if (msb) {
for (b = 0x80, c = 0, i = 0; i < n; i++, b >>= 1)
c |= (*src++) ? b : 0;
} else {
for (b = 0x01, c = 0, i = 0; i < n; i++, b <<= 1)
c |= (*src++) ? b : 0;
}
*dst++ = c;
}
}
clipmask = XCreateBitmapFromData(xw.dpy, xw.win, clipdata, width, height);
free(clipdata);
return clipmask;
}