mirror of
https://github.com/mintycube/st.git
synced 2024-10-22 14:05:49 +02:00
003ab067da
...support for transparency. Technically, the sixels do not have transparency, but empty pixels are now rendered with the current background color instead of black to make the them appear transparent. Same goes for the black bars. The current background color makes them disappear. There is one technical limitation with the alpha focus highlight patch. The alpha value and background color is taken from the current background color, so when the window is unfocused, images may have the wrong alpha and/or background color. This can't be fixed easily.
617 lines
15 KiB
C
617 lines
15 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 "sixel.h"
|
|
#include "sixel_hls.h"
|
|
|
|
#define SIXEL_RGB(r, g, b) ((r) + ((g) << 8) + ((b) << 16) + (255 << 24))
|
|
#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% */
|
|
};
|
|
|
|
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)
|
|
{
|
|
free(image->data);
|
|
image->data = NULL;
|
|
}
|
|
|
|
int
|
|
sixel_parser_init(sixel_state_t *st,
|
|
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->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, 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, unsigned char *pixels)
|
|
{
|
|
int status = (-1);
|
|
int sx;
|
|
int sy;
|
|
sixel_image_t *image = &st->image;
|
|
int x, y;
|
|
sixel_color_no_t *src;
|
|
unsigned char *dst;
|
|
int color;
|
|
|
|
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;
|
|
|
|
sx = (st->max_x + st->grid_width - 1) / st->grid_width * st->grid_width;
|
|
sy = (st->max_y + st->grid_height - 1) / st->grid_height * st->grid_height;
|
|
|
|
if (image->width > sx || image->height > sy) {
|
|
status = image_buffer_resize(image, sx, sy);
|
|
if (status < 0)
|
|
goto end;
|
|
}
|
|
|
|
if (image->use_private_register && image->ncolors > 2 && !image->palette_modified) {
|
|
status = set_default_color(image);
|
|
if (status < 0)
|
|
goto end;
|
|
}
|
|
|
|
src = st->image.data;
|
|
dst = pixels;
|
|
for (y = 0; y < st->image.height; ++y) {
|
|
for (x = 0; x < st->image.width; ++x) {
|
|
color = st->image.palette[*src++];
|
|
*dst++ = color >> 16 & 0xff; /* b */
|
|
*dst++ = color >> 8 & 0xff; /* g */
|
|
*dst++ = color >> 0 & 0xff; /* r */
|
|
*dst++ = color >> 24 & 0xff; /* a */
|
|
}
|
|
/* fill right padding with bgcolor */
|
|
for (; x < st->image.width; ++x) {
|
|
color = st->image.palette[0]; /* bgcolor */
|
|
*dst++ = color >> 16 & 0xff; /* b */
|
|
*dst++ = color >> 8 & 0xff; /* g */
|
|
*dst++ = color >> 0 & 0xff; /* r */
|
|
*dst++ = color >> 24 & 0xff; /* a */
|
|
}
|
|
}
|
|
/* fill bottom padding with bgcolor */
|
|
for (; y < st->image.height; ++y) {
|
|
for (x = 0; x < st->image.width; ++x) {
|
|
color = st->image.palette[0]; /* bgcolor */
|
|
*dst++ = color >> 16 & 0xff; /* b */
|
|
*dst++ = color >> 8 & 0xff; /* g */
|
|
*dst++ = color >> 0 & 0xff; /* r */
|
|
*dst++ = color >> 24 & 0xff; /* a */
|
|
}
|
|
}
|
|
|
|
status = (0);
|
|
|
|
end:
|
|
return status;
|
|
}
|
|
|
|
/* convert sixel data into indexed pixel bytes and palette data */
|
|
int
|
|
sixel_parser_parse(sixel_state_t *st, unsigned char *p, size_t len)
|
|
{
|
|
int status = (-1);
|
|
int n;
|
|
int i;
|
|
int x;
|
|
int y;
|
|
int bits;
|
|
int sixel_vertical_mask;
|
|
int sx;
|
|
int sy;
|
|
int c;
|
|
int pos;
|
|
unsigned char *p0 = p;
|
|
sixel_image_t *image = &st->image;
|
|
|
|
if (! image->data)
|
|
goto end;
|
|
|
|
while (p < p0 + len) {
|
|
switch (st->state) {
|
|
case PS_ESC:
|
|
goto end;
|
|
|
|
case PS_DECSIXEL:
|
|
switch (*p) {
|
|
case '\x1b':
|
|
st->state = PS_ESC;
|
|
p++;
|
|
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;
|
|
}
|
|
|
|
if (sx > DECSIXEL_WIDTH_MAX)
|
|
sx = DECSIXEL_WIDTH_MAX;
|
|
if (sy > DECSIXEL_HEIGHT_MAX)
|
|
sy = DECSIXEL_HEIGHT_MAX;
|
|
|
|
status = image_buffer_resize(image, sx, sy);
|
|
if (status < 0)
|
|
goto end;
|
|
}
|
|
|
|
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) {
|
|
sixel_vertical_mask = 0x01;
|
|
if (st->repeat_count <= 1) {
|
|
for (i = 0; i < 6; i++) {
|
|
if ((bits & sixel_vertical_mask) != 0) {
|
|
pos = image->width * (st->pos_y + i) + st->pos_x;
|
|
image->data[pos] = st->color_index;
|
|
if (st->max_x < st->pos_x)
|
|
st->max_x = st->pos_x;
|
|
if (st->max_y < (st->pos_y + i))
|
|
st->max_y = st->pos_y + i;
|
|
}
|
|
sixel_vertical_mask <<= 1;
|
|
}
|
|
} else {
|
|
/* st->repeat_count > 1 */
|
|
for (i = 0; i < 6; i++) {
|
|
if ((bits & sixel_vertical_mask) != 0) {
|
|
c = sixel_vertical_mask << 1;
|
|
for (n = 1; (i + n) < 6; n++) {
|
|
if ((bits & c) == 0)
|
|
break;
|
|
c <<= 1;
|
|
}
|
|
for (y = st->pos_y + i; y < st->pos_y + i + n; ++y) {
|
|
for (x = st->pos_x; x < st->pos_x + st->repeat_count; ++x)
|
|
image->data[image->width * y + x] = st->color_index;
|
|
}
|
|
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 + i + n - 1))
|
|
st->max_y = st->pos_y + i + n - 1;
|
|
i += (n - 1);
|
|
sixel_vertical_mask <<= (n - 1);
|
|
}
|
|
sixel_vertical_mask <<= 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
p++;
|
|
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';
|
|
if (st->param > DECSIXEL_PARAMVALUE_MAX)
|
|
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 = st->attributed_ph;
|
|
if (image->width > st->attributed_ph)
|
|
sx = image->width;
|
|
|
|
sy = st->attributed_pv;
|
|
if (image->height > st->attributed_pv)
|
|
sy = image->height;
|
|
|
|
sx = (sx + st->grid_width - 1) / st->grid_width * st->grid_width;
|
|
sy = (sy + st->grid_height - 1) / st->grid_height * st->grid_height;
|
|
|
|
if (sx > DECSIXEL_WIDTH_MAX)
|
|
sx = DECSIXEL_WIDTH_MAX;
|
|
if (sy > DECSIXEL_HEIGHT_MAX)
|
|
sy = DECSIXEL_HEIGHT_MAX;
|
|
|
|
status = image_buffer_resize(image, sx, sy);
|
|
if (status < 0)
|
|
goto end;
|
|
}
|
|
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;
|
|
p++;
|
|
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';
|
|
if (st->param > DECSIXEL_PARAMVALUE_MAX)
|
|
st->param = DECSIXEL_PARAMVALUE_MAX;
|
|
p++;
|
|
break;
|
|
default:
|
|
st->repeat_count = st->param;
|
|
if (st->repeat_count == 0)
|
|
st->repeat_count = 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;
|
|
p++;
|
|
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';
|
|
if (st->param > DECSIXEL_PARAMVALUE_MAX)
|
|
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 */
|
|
if (st->params[2] > 360)
|
|
st->params[2] = 360;
|
|
if (st->params[3] > 100)
|
|
st->params[3] = 100;
|
|
if (st->params[4] > 100)
|
|
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 */
|
|
if (st->params[2] > 100)
|
|
st->params[2] = 100;
|
|
if (st->params[3] > 100)
|
|
st->params[3] = 100;
|
|
if (st->params[4] > 100)
|
|
st->params[4] = 100;
|
|
image->palette[st->color_index]
|
|
= SIXEL_XRGB(st->params[2], st->params[3], st->params[4]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
status = (0);
|
|
|
|
end:
|
|
return status;
|
|
}
|
|
|
|
void
|
|
sixel_parser_deinit(sixel_state_t *st)
|
|
{
|
|
if (st)
|
|
sixel_image_deinit(&st->image);
|
|
}
|