Skip to content

Instantly share code, notes, and snippets.

@saitoha
Last active May 14, 2024 06:08
Show Gist options
  • Save saitoha/70e0fdf22e3e8f63ce937c7f7da71809 to your computer and use it in GitHub Desktop.
Save saitoha/70e0fdf22e3e8f63ce937c7f7da71809 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)
commit ea830e03d4d4562b1ff225940f65bceddd9cad6c
Author: Hayaki Saito <saitoha@me.com>
Date: Sun Jun 11 23:46:45 2017 +0900
Add sixel graphics support
Signed-off-by: Hayaki Saito <saitoha@me.com>
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
@cpixl
Copy link

cpixl commented Jul 29, 2019

@charlesdaniels would you mind providing a single .diff file for the SIXEL support on st-0.8.1? Maybe this could be added to https://st.suckless.org/patches/.

@charlesdaniels
Copy link

Simply diff 85409d9d6e468862e8b608ce73595d5388666d38 and 2f2e437b7de2d953de5bcd39cfd0283f0f6f0af3 from the linked repo.

If I get around to it, I can try to pull out something suitable for a suckless patch, as there are some files that will show up in that diff that aren't relevant, such as my README.

There are some caveats though:

  • my implementation is very hacky, I am not pleased with the code quality
  • my implementation is also pretty buggy in general
  • I have not implemented the escape sequences which would allow sixel support to be correctly detected by programs running within the terminal, so many programs must be "forced" into their relevant sixel modes
  • Interaction with clear is a hack -- I just delete all sixels each time clear is issued
  • The snippet above is GPL licensed. Thus, using this patch implicitly makes your st fall under GPL too.

@xmiah0906
Copy link

Please update patch with st-version 0.8.2!!!

@ayush-india
Copy link

please update this for st 0.9

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment