-
-
Save Risto-Stevcev/0067fcd236fd321316049043bbf199bb to your computer and use it in GitHub Desktop.
Add SIXEL graphics support for suckless st. (sixel.c/sixel_hls.c come from mintty, licensed under GPL)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
commit ea830e03d4d4562b1ff225940f65bceddd9cad6c | |
Author: Hayaki Saito <[email protected]> | |
Date: Sun Jun 11 23:46:45 2017 +0900 | |
Add sixel graphics support | |
Signed-off-by: Hayaki Saito <[email protected]> | |
diff --git a/Makefile b/Makefile | |
index d8595fe..a25d040 100644 | |
--- a/Makefile | |
+++ b/Makefile | |
@@ -3,7 +3,7 @@ | |
include config.mk | |
-SRC = st.c x.c | |
+SRC = st.c x.c sixel.c sixel_hls.c | |
OBJ = ${SRC:.c=.o} | |
all: options st | |
diff --git a/sixel.c b/sixel.c | |
new file mode 100644 | |
index 0000000..7bfe598 | |
--- /dev/null | |
+++ b/sixel.c | |
@@ -0,0 +1,616 @@ | |
+// 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)) | |
+#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++; /* 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++; /* 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++; /* 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); | |
+} | |
diff --git a/sixel.h b/sixel.h | |
new file mode 100644 | |
index 0000000..8a05c44 | |
--- /dev/null | |
+++ b/sixel.h | |
@@ -0,0 +1,58 @@ | |
+#ifndef SIXEL_H | |
+#define SIXEL_H | |
+ | |
+#define DECSIXEL_PARAMS_MAX 16 | |
+#define DECSIXEL_PALETTE_MAX 1024 | |
+#define DECSIXEL_PARAMVALUE_MAX 65535 | |
+#define DECSIXEL_WIDTH_MAX 4096 | |
+#define DECSIXEL_HEIGHT_MAX 4096 | |
+ | |
+typedef unsigned short sixel_color_no_t; | |
+typedef unsigned int sixel_color_t; | |
+ | |
+typedef struct sixel_image_buffer { | |
+ sixel_color_no_t *data; | |
+ int width; | |
+ int height; | |
+ sixel_color_t palette[DECSIXEL_PALETTE_MAX]; | |
+ sixel_color_no_t ncolors; | |
+ int palette_modified; | |
+ int use_private_register; | |
+} sixel_image_t; | |
+ | |
+typedef enum parse_state { | |
+ PS_ESC = 1, /* ESC */ | |
+ PS_DECSIXEL = 2, /* DECSIXEL body part ", $, -, ? ... ~ */ | |
+ PS_DECGRA = 3, /* DECGRA Set Raster Attributes " Pan; Pad; Ph; Pv */ | |
+ PS_DECGRI = 4, /* DECGRI Graphics Repeat Introducer ! Pn Ch */ | |
+ PS_DECGCI = 5, /* DECGCI Graphics Color Introducer # Pc; Pu; Px; Py; Pz */ | |
+} parse_state_t; | |
+ | |
+typedef struct parser_context { | |
+ parse_state_t state; | |
+ int pos_x; | |
+ int pos_y; | |
+ int max_x; | |
+ int max_y; | |
+ int attributed_pan; | |
+ int attributed_pad; | |
+ int attributed_ph; | |
+ int attributed_pv; | |
+ int repeat_count; | |
+ int color_index; | |
+ int bgindex; | |
+ int grid_width; | |
+ int grid_height; | |
+ int param; | |
+ int nparams; | |
+ int params[DECSIXEL_PARAMS_MAX]; | |
+ sixel_image_t image; | |
+} sixel_state_t; | |
+ | |
+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 sixel_parser_parse(sixel_state_t *st, unsigned char *p, size_t len); | |
+int sixel_parser_set_default_color(sixel_state_t *st); | |
+int sixel_parser_finalize(sixel_state_t *st, unsigned char *pixels); | |
+void sixel_parser_deinit(sixel_state_t *st); | |
+ | |
+#endif | |
diff --git a/sixel_hls.c b/sixel_hls.c | |
new file mode 100644 | |
index 0000000..4f157b2 | |
--- /dev/null | |
+++ b/sixel_hls.c | |
@@ -0,0 +1,115 @@ | |
+// sixel.c (part of mintty) | |
+// this function is derived from a part of graphics.c | |
+// in Xterm pl#310 originally written by Ross Combs. | |
+// | |
+// Copyright 2013,2014 by Ross Combs | |
+// | |
+// All Rights Reserved | |
+// | |
+// Permission is hereby granted, free of charge, to any person obtaining a | |
+// copy of this software and associated documentation files (the | |
+// "Software"), to deal in the Software without restriction, including | |
+// without limitation the rights to use, copy, modify, merge, publish, | |
+// distribute, sublicense, and/or sell copies of the Software, and to | |
+// permit persons to whom the Software is furnished to do so, subject to | |
+// the following conditions: | |
+// | |
+// The above copyright notice and this permission notice shall be included | |
+// in all copies or substantial portions of the Software. | |
+// | |
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
+// IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY | |
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, | |
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
+// | |
+// Except as contained in this notice, the name(s) of the above copyright | |
+// holders shall not be used in advertising or otherwise to promote the | |
+// sale, use or other dealings in this Software without prior written | |
+// authorization. | |
+ | |
+#define SIXEL_RGB(r, g, b) (((r) << 16) + ((g) << 8) + (b)) | |
+ | |
+int | |
+hls_to_rgb(int hue, int lum, int sat) | |
+{ | |
+ double hs = (hue + 240) % 360; | |
+ double hv = hs / 360.0; | |
+ double lv = lum / 100.0; | |
+ double sv = sat / 100.0; | |
+ double c, x, m, c2; | |
+ double r1, g1, b1; | |
+ int r, g, b; | |
+ int hpi; | |
+ | |
+ if (sat == 0) { | |
+ r = g = b = lum * 255 / 100; | |
+ return SIXEL_RGB(r, g, b); | |
+ } | |
+ | |
+ if ((c2 = ((2.0 * lv) - 1.0)) < 0.0) { | |
+ c2 = -c2; | |
+ } | |
+ c = (1.0 - c2) * sv; | |
+ hpi = (int) (hv * 6.0); | |
+ x = (hpi & 1) ? c : 0.0; | |
+ m = lv - 0.5 * c; | |
+ | |
+ switch (hpi) { | |
+ case 0: | |
+ r1 = c; | |
+ g1 = x; | |
+ b1 = 0.0; | |
+ break; | |
+ case 1: | |
+ r1 = x; | |
+ g1 = c; | |
+ b1 = 0.0; | |
+ break; | |
+ case 2: | |
+ r1 = 0.0; | |
+ g1 = c; | |
+ b1 = x; | |
+ break; | |
+ case 3: | |
+ r1 = 0.0; | |
+ g1 = x; | |
+ b1 = c; | |
+ break; | |
+ case 4: | |
+ r1 = x; | |
+ g1 = 0.0; | |
+ b1 = c; | |
+ break; | |
+ case 5: | |
+ r1 = c; | |
+ g1 = 0.0; | |
+ b1 = x; | |
+ break; | |
+ default: | |
+ return SIXEL_RGB(255, 255, 255); | |
+ } | |
+ | |
+ r = (int) ((r1 + m) * 100.0 + 0.5); | |
+ g = (int) ((g1 + m) * 100.0 + 0.5); | |
+ b = (int) ((b1 + m) * 100.0 + 0.5); | |
+ | |
+ if (r < 0) { | |
+ r = 0; | |
+ } else if (r > 100) { | |
+ r = 100; | |
+ } | |
+ if (g < 0) { | |
+ g = 0; | |
+ } else if (g > 100) { | |
+ g = 100; | |
+ } | |
+ if (b < 0) { | |
+ b = 0; | |
+ } else if (b > 100) { | |
+ b = 100; | |
+ } | |
+ return SIXEL_RGB(r * 255 / 100, g * 255 / 100, b * 255 / 100); | |
+} | |
diff --git a/sixel_hls.h b/sixel_hls.h | |
new file mode 100644 | |
index 0000000..6176589 | |
--- /dev/null | |
+++ b/sixel_hls.h | |
@@ -0,0 +1,7 @@ | |
+/* | |
+ * Primary color hues: | |
+ * blue: 0 degrees | |
+ * red: 120 degrees | |
+ * green: 240 degrees | |
+ */ | |
+int hls_to_rgb(int hue, int lum, int sat); | |
diff --git a/st.c b/st.c | |
index 8d4a9f2..7df71d9 100644 | |
--- a/st.c | |
+++ b/st.c | |
@@ -35,6 +35,7 @@ char *argv0; | |
#include "win.h" | |
#include "st.h" | |
+#include "sixel.h" | |
#if defined(__linux) | |
#include <pty.h> | |
@@ -147,6 +148,7 @@ static void stty(void); | |
static void sigchld(int); | |
static void csidump(void); | |
+static void dcshandle(void); | |
static void csihandle(void); | |
static void csiparse(void); | |
static void csireset(void); | |
@@ -209,6 +211,7 @@ Term term; | |
Selection sel; | |
int cmdfd; | |
pid_t pid; | |
+sixel_state_t sixel_st; | |
char **opt_cmd = NULL; | |
char *opt_class = NULL; | |
char *opt_embed = NULL; | |
@@ -1009,6 +1012,7 @@ void | |
treset(void) | |
{ | |
uint i; | |
+ ImageList *im; | |
term.c = (TCursor){{ | |
.mode = ATTR_NULL, | |
@@ -1031,6 +1035,9 @@ treset(void) | |
tclearregion(0, 0, term.col-1, term.row-1); | |
tswapscreen(); | |
} | |
+ | |
+ for (im = term.images; im; im = im->next) | |
+ im->should_delete = 1; | |
} | |
void | |
@@ -1047,9 +1054,12 @@ void | |
tswapscreen(void) | |
{ | |
Line *tmp = term.line; | |
+ ImageList *im = term.images; | |
term.line = term.alt; | |
term.alt = tmp; | |
+ term.images = term.images_alt; | |
+ term.images_alt = im; | |
term.mode ^= MODE_ALTSCREEN; | |
tfulldirt(); | |
} | |
@@ -1059,6 +1069,7 @@ tscrolldown(int orig, int n) | |
{ | |
int i; | |
Line temp; | |
+ ImageList *im; | |
LIMIT(n, 0, term.bot-orig+1); | |
@@ -1071,6 +1082,13 @@ tscrolldown(int orig, int n) | |
term.line[i-n] = temp; | |
} | |
+ for (im = term.images; im; im = im->next) { | |
+ if (im->y < term.bot) | |
+ im->y += n; | |
+ if (im->y > term.bot) | |
+ im->should_delete = 1; | |
+ } | |
+ | |
selscroll(orig, n); | |
} | |
@@ -1079,6 +1097,7 @@ tscrollup(int orig, int n) | |
{ | |
int i; | |
Line temp; | |
+ ImageList *im; | |
LIMIT(n, 0, term.bot-orig+1); | |
@@ -1091,6 +1110,13 @@ tscrollup(int orig, int n) | |
term.line[i+n] = temp; | |
} | |
+ for (im = term.images; im; im = im->next) { | |
+ if (im->y+im->height/win.ch > term.top) | |
+ im->y -= n; | |
+ if (im->y+im->height/win.ch < term.top) | |
+ im->should_delete = 1; | |
+ } | |
+ | |
selscroll(orig, -n); | |
} | |
@@ -1609,6 +1635,23 @@ tsetmode(int priv, int set, int *args, int narg) | |
} | |
void | |
+dcshandle(void) | |
+{ | |
+ switch (csiescseq.mode[0]) { | |
+ default: | |
+ fprintf(stderr, "erresc: unknown csi "); | |
+ csidump(); | |
+ /* die(""); */ | |
+ break; | |
+ case 'q': /* DECSIXEL */ | |
+ if (sixel_parser_init(&sixel_st, 0, 0 << 16 | 0 << 8 | 0, 1, win.cw, win.ch) != 0) | |
+ perror("sixel_parser_init() failed"); | |
+ term.mode |= MODE_SIXEL; | |
+ break; | |
+ } | |
+} | |
+ | |
+void | |
csihandle(void) | |
{ | |
char buf[40]; | |
@@ -1853,6 +1896,8 @@ strhandle(void) | |
{ | |
char *p = NULL; | |
int j, narg, par; | |
+ ImageList *new_image; | |
+ int i; | |
term.esc &= ~(ESC_STR_END|ESC_STR); | |
strparse(); | |
@@ -1903,7 +1948,39 @@ strhandle(void) | |
xsettitle(strescseq.args[0]); | |
return; | |
case 'P': /* DCS -- Device Control String */ | |
- term.mode |= ESC_DCS; | |
+ if (IS_SET(MODE_SIXEL)) { | |
+ term.mode &= ~MODE_SIXEL; | |
+ new_image = malloc(sizeof(ImageList)); | |
+ memset(new_image, 0, sizeof(ImageList)); | |
+ new_image->x = term.c.x; | |
+ new_image->y = term.c.y; | |
+ new_image->width = sixel_st.image.width; | |
+ new_image->height = sixel_st.image.height; | |
+ new_image->pixels = malloc(new_image->width * new_image->height * 4); | |
+ if (sixel_parser_finalize(&sixel_st, new_image->pixels) != 0) { | |
+ perror("sixel_parser_finalize() failed"); | |
+ sixel_parser_deinit(&sixel_st); | |
+ return; | |
+ } | |
+ sixel_parser_deinit(&sixel_st); | |
+ if (term.images) { | |
+ ImageList *im; | |
+ for (im = term.images; im->next;) | |
+ im = im->next; | |
+ im->next = new_image; | |
+ new_image->prev = im; | |
+ } else { | |
+ term.images = new_image; | |
+ } | |
+ for (i = 0; i < (sixel_st.image.height + win.ch-1)/win.ch; ++i) { | |
+ int x; | |
+ tclearregion(term.c.x, term.c.y, term.c.x+(sixel_st.image.width+win.cw-1)/win.cw, term.c.y); | |
+ for (x = term.c.x; x < MIN(term.col, term.c.x+(sixel_st.image.width+win.cw-1)/win.cw); x++) | |
+ term.line[term.c.y][x].mode |= ATTR_SIXEL; | |
+ tnewline(1); | |
+ } | |
+ } | |
+ return; | |
case '_': /* APC -- Application Program Command */ | |
case '^': /* PM -- Privacy Message */ | |
return; | |
@@ -2274,6 +2351,7 @@ eschandle(uchar ascii) | |
term.esc |= ESC_UTF8; | |
return 0; | |
case 'P': /* DCS -- Device Control String */ | |
+ term.esc |= ESC_DCS; | |
case '_': /* APC -- Application Program Command */ | |
case '^': /* PM -- Privacy Message */ | |
case ']': /* OSC -- Operating System Command */ | |
@@ -2376,22 +2454,18 @@ tputc(Rune u) | |
if (u == '\a' || u == 030 || u == 032 || u == 033 || | |
ISCONTROLC1(u)) { | |
term.esc &= ~(ESC_START|ESC_STR|ESC_DCS); | |
- if (IS_SET(MODE_SIXEL)) { | |
- /* TODO: render sixel */; | |
- term.mode &= ~MODE_SIXEL; | |
- return; | |
- } | |
term.esc |= ESC_STR_END; | |
goto check_control_code; | |
} | |
if (IS_SET(MODE_SIXEL)) { | |
- /* TODO: implement sixel mode */ | |
+ if (sixel_parser_parse(&sixel_st, (unsigned char *)&u, 1) != 0) | |
+ perror("sixel_parser_parse() failed"); | |
return; | |
} | |
- if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q') | |
- term.mode |= MODE_SIXEL; | |
+ if (term.esc & ESC_DCS) | |
+ goto check_control_code; | |
if (strescseq.len+len >= sizeof(strescseq.buf)-1) { | |
/* | |
@@ -2438,6 +2512,15 @@ check_control_code: | |
csihandle(); | |
} | |
return; | |
+ } else if (term.esc & ESC_DCS) { | |
+ csiescseq.buf[csiescseq.len++] = u; | |
+ if (BETWEEN(u, 0x40, 0x7E) | |
+ || csiescseq.len >= \ | |
+ sizeof(csiescseq.buf)-1) { | |
+ csiparse(); | |
+ dcshandle(); | |
+ } | |
+ return; | |
} else if (term.esc & ESC_UTF8) { | |
tdefutf8(u); | |
} else if (term.esc & ESC_ALTCHARSET) { | |
diff --git a/st.h b/st.h | |
index 44d4938..61195be 100644 | |
--- a/st.h | |
+++ b/st.h | |
@@ -33,6 +33,7 @@ enum glyph_attribute { | |
ATTR_WRAP = 1 << 8, | |
ATTR_WIDE = 1 << 9, | |
ATTR_WDUMMY = 1 << 10, | |
+ ATTR_SIXEL = 1 << 11, | |
ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT, | |
}; | |
@@ -108,6 +109,17 @@ typedef struct { | |
char state; | |
} TCursor; | |
+typedef struct _ImageList { | |
+ struct _ImageList *next, *prev; | |
+ unsigned char *pixels; | |
+ void *pixmap; | |
+ int width; | |
+ int height; | |
+ int x; | |
+ int y; | |
+ int should_delete; | |
+} ImageList; | |
+ | |
/* Internal representation of the screen */ | |
typedef struct { | |
int row; /* nb row */ | |
@@ -126,6 +138,8 @@ typedef struct { | |
int icharset; /* selected charset for sequence */ | |
int numlock; /* lock numbers in keyboard */ | |
int *tabs; | |
+ ImageList *images; /* sixel images */ | |
+ ImageList *images_alt; /* sixel images for alternate screen */ | |
} Term; | |
/* Purely graphic info */ | |
diff --git a/x.c b/x.c | |
index fbfd350..38a3da0 100644 | |
--- a/x.c | |
+++ b/x.c | |
@@ -16,6 +16,7 @@ | |
#include <X11/XKBlib.h> | |
#include "arg.h" | |
+#include "compat.h" | |
#define Glyph Glyph_ | |
#define Font Font_ | |
@@ -1384,14 +1385,101 @@ xsettitle(char *p) | |
} | |
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); | |
+ free(im->pixels); | |
+ free(im); | |
+} | |
+ | |
+void | |
draw(void) | |
{ | |
+ ImageList *im; | |
+ int x, y; | |
+ int n = 0; | |
+ int nlimit = 256; | |
+ XRectangle *rects = NULL; | |
+ XGCValues gcvalues; | |
+ GC gc; | |
+ | |
drawregion(0, 0, term.col, term.row); | |
+ for (im = term.images; im; im = im->next) { | |
+ if (im->should_delete) { | |
+ delete_image(im); | |
+ continue; | |
+ } | |
+ if (!im->pixmap) { | |
+ im->pixmap = (void *)XCreatePixmap(xw.dpy, xw.win, im->width, im->height, DefaultDepth(xw.dpy, xw.scr)); | |
+ XImage ximage = { | |
+ .format = ZPixmap, | |
+ .data = (char *)im->pixels, | |
+ .width = im->width, | |
+ .height = im->height, | |
+ .xoffset = 0, | |
+ .byte_order = LSBFirst, | |
+ .bitmap_bit_order = MSBFirst, | |
+ .bits_per_pixel = 32, | |
+ .bytes_per_line = im->width * 4, | |
+ .bitmap_unit = 32, | |
+ .bitmap_pad = 32, | |
+ .depth = 24 | |
+ }; | |
+ XPutImage(xw.dpy, (Drawable)im->pixmap, dc.gc, &ximage, 0, 0, 0, 0, im->width, im->height); | |
+ free(im->pixels); | |
+ im->pixels = NULL; | |
+ } | |
+ n = 0; | |
+ memset(&gcvalues, 0, sizeof(gcvalues)); | |
+ gc = XCreateGC(xw.dpy, xw.win, 0, &gcvalues); | |
+ for (y = im->y; y < im->y + (im->height+win.ch-1)/win.ch; y++) { | |
+ if (y >= 0 && y < term.row) { | |
+ for (x = im->x; x < im->x + (im->width+win.cw-1)/win.cw; x++) { | |
+ if (!rects) | |
+ rects = xmalloc(sizeof(XRectangle) * nlimit); | |
+ if (term.line[y][x].mode & ATTR_SIXEL) { | |
+ if (n > 0 && rects[n-1].x+rects[n-1].width == borderpx+x*win.cw && rects[n-1].y == borderpx+y*win.ch) { | |
+ rects[n-1].width += win.cw; | |
+ } else { | |
+ rects[n].x = borderpx+x*win.cw; | |
+ rects[n].y = borderpx+y*win.ch; | |
+ rects[n].width = win.cw; | |
+ rects[n].height = win.ch; | |
+ if (++n == nlimit && (rects = realloc(rects, sizeof(XRectangle) * (nlimit *= 2))) == NULL) | |
+ die("Out of memory\n"); | |
+ } | |
+ } | |
+ } | |
+ } | |
+ if (n > 1 && rects[n-2].x == rects[n-1].x && rects[n-2].width == rects[n-1].width) { | |
+ if (rects[n-2].y+rects[n-2].height == rects[n-1].y) { | |
+ rects[n-2].height += win.ch; | |
+ n--; | |
+ } | |
+ } | |
+ } | |
+ if (n == 0) { | |
+ delete_image(im); | |
+ continue; | |
+ } | |
+ if (n > 1) | |
+ XSetClipRectangles(xw.dpy, gc, 0, 0, rects, n, YXSorted); | |
+ XCopyArea(xw.dpy, (Drawable)im->pixmap, xw.buf, gc, 0, 0, im->width, im->height, borderpx + im->x * win.cw, borderpx + im->y * win.ch); | |
+ XFreeGC(xw.dpy, gc); | |
+ } | |
XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, | |
win.h, 0, 0); | |
XSetForeground(xw.dpy, dc.gc, | |
dc.col[IS_SET(MODE_REVERSE)? | |
defaultfg : defaultbg].pixel); | |
+ free(rects); | |
} | |
void |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment