st-noxz

[fork] suckless st - personal fork
git clone https://noxz.tech/git/st-noxz.git
Log | Files | README | LICENSE

commit: 42fa95c93fbe56dc7f4f45e86146e1eb46d9e918
parent: da95370761134e74f4fed968d9871dc1e4c73c24
author: Chris Noxz <chris@noxz.tech>
date:   Sun, 19 May 2024 19:27:03 +0200
implement sixel support based on st-flexipatch
MMakefile2+-
Mconfig.def.h5+-
Mconfig.mk1+
Asixel.c692++++++++++++++++++++
Asixel.h63++
Asixel_hls.c115++++
Asixel_hls.h7+
Mst.c343++++++++--
Mst.h176++++-
Mx.c206+++---
10 files changed, 1450 insertions(+), 160 deletions(-)
diff --git a/Makefile b/Makefile
@@ -4,7 +4,7 @@
 
 include config.mk
 
-SRC = st.c x.c boxdraw.c
+SRC = st.c x.c boxdraw.c sixel.c sixel_hls.c
 OBJ = $(SRC:.c=.o)
 
 all: st
diff --git a/config.def.h b/config.def.h
@@ -25,8 +25,11 @@ char *utmp = NULL;
 char *scroll = NULL;
 char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400";
 
+/* sixel rgb byte order: LSBFirst or MSBFirst */
+int const sixelbyteorder = LSBFirst;
+
 /* identification sequence returned in DA and DECID */
-char *vtiden = "\033[?6c";
+char *vtiden = "\033[?62;4c"; /* VT200 family (62) with sixel (4) */
 
 /* Kerning / character bounding-box multipliers */
 static float cwscale = 1.0;
diff --git a/config.mk b/config.mk
@@ -17,6 +17,7 @@ INCS = -I$(X11INC) \
        `$(PKG_CONFIG) --cflags fontconfig` \
        `$(PKG_CONFIG) --cflags freetype2`
 LIBS = -L$(X11LIB) -lm -lrt -lX11 -lutil -lXft -lXcursor \
+       `$(PKG_CONFIG) --libs imlib2` \
        `$(PKG_CONFIG) --libs fontconfig` \
        `$(PKG_CONFIG) --libs freetype2`
 
diff --git a/sixel.c b/sixel.c
@@ -0,0 +1,692 @@
+// 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;
+}
diff --git a/sixel.h b/sixel.h
@@ -0,0 +1,63 @@
+#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 */
+	PS_ERROR      = 6,
+} 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 transparent;
+	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;
+
+void scroll_images(int n);
+void delete_image(ImageList *im);
+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 sixel_parser_parse(sixel_state_t *st, const unsigned char *p, size_t len);
+int sixel_parser_set_default_color(sixel_state_t *st);
+int sixel_parser_finalize(sixel_state_t *st, ImageList **newimages, int cx, int cy, int cw, int ch);
+void sixel_parser_deinit(sixel_state_t *st);
+Pixmap sixel_create_clipmask(char *pixels, int width, int height);
+
+#endif
diff --git a/sixel_hls.c 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) + ((g) << 8) + ((b) << 16) + (255 << 24))
+
+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
@@ -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
@@ -19,6 +19,7 @@
 
 #include "st.h"
 #include "win.h"
+#include "sixel.h"
 
 #if   defined(__linux)
  #include <pty.h>
@@ -45,13 +46,16 @@
 #define ISDELIM(u)		(u && wcschr(worddelimiters, u))
 
 enum term_mode {
-	MODE_WRAP        = 1 << 0,
-	MODE_INSERT      = 1 << 1,
-	MODE_ALTSCREEN   = 1 << 2,
-	MODE_CRLF        = 1 << 3,
-	MODE_ECHO        = 1 << 4,
-	MODE_PRINT       = 1 << 5,
-	MODE_UTF8        = 1 << 6,
+	MODE_WRAP           = 1 << 0,
+	MODE_INSERT         = 1 << 1,
+	MODE_ALTSCREEN      = 1 << 2,
+	MODE_CRLF           = 1 << 3,
+	MODE_ECHO           = 1 << 4,
+	MODE_PRINT          = 1 << 5,
+	MODE_UTF8           = 1 << 6,
+	MODE_SIXEL          = 1 << 7,
+	MODE_SIXEL_CUR_RT   = 1 << 8,
+	MODE_SIXEL_SDM      = 1 << 9,
 };
 
 enum cursor_movement {
@@ -76,61 +80,16 @@ enum charset {
 };
 
 enum escape_state {
-	ESC_START      = 1,
-	ESC_CSI        = 2,
-	ESC_STR        = 4,  /* DCS, OSC, PM, APC */
-	ESC_ALTCHARSET = 8,
-	ESC_STR_END    = 16, /* a final string was encountered */
-	ESC_TEST       = 32, /* Enter in test mode */
-	ESC_UTF8       = 64,
+	ESC_START      = 1 << 0,
+	ESC_CSI        = 1 << 1,
+	ESC_STR        = 1 << 2,  /* DCS, OSC, PM, APC */
+	ESC_ALTCHARSET = 1 << 3,
+	ESC_STR_END    = 1 << 4, /* a final string was encountered */
+	ESC_TEST       = 1 << 5, /* Enter in test mode */
+	ESC_UTF8       = 1 << 6,
+	ESC_DCS        = 1 << 7,
 };
 
-typedef struct {
-	Glyph attr; /* current char attributes */
-	int x;
-	int y;
-	char state;
-} TCursor;
-
-typedef struct {
-	int mode;
-	int type;
-	int snap;
-	/*
-	 * Selection variables:
-	 * nb – normalized coordinates of the beginning of the selection
-	 * ne – normalized coordinates of the end of the selection
-	 * ob – original coordinates of the beginning of the selection
-	 * oe – original coordinates of the end of the selection
-	 */
-	struct {
-		int x, y;
-	} nb, ne, ob, oe;
-
-	int alt;
-} Selection;
-
-/* Internal representation of the screen */
-typedef struct {
-	int row;      /* nb row */
-	int col;      /* nb col */
-	Line *line;   /* screen */
-	Line *alt;    /* alternate screen */
-	int *dirty;   /* dirtyness of lines */
-	TCursor c;    /* cursor */
-	int ocx;      /* old cursor col */
-	int ocy;      /* old cursor row */
-	int top;      /* top    scroll limit */
-	int bot;      /* bottom scroll limit */
-	int mode;     /* terminal mode flags */
-	int esc;      /* escape state flags */
-	char trantbl[4]; /* charset table translation */
-	int charset;  /* current charset */
-	int icharset; /* selected charset for sequence */
-	int *tabs;
-	Rune lastc;   /* last printed char outside of sequence, 0 if control */
-} Term;
-
 /* CSI Escape sequence structs */
 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
 typedef struct {
@@ -161,6 +120,7 @@ static void ttywriteraw(const char *, size_t);
 
 static void csidump(void);
 static void csihandle(void);
+static void dcshandle(void);
 static void readcolonargs(char **, int, int[][CAR_PER_ARG]);
 static void csiparse(void);
 static void csireset(void);
@@ -178,6 +138,7 @@ static void tdump(void);
 static void tclearregion(int, int, int, int);
 static void tcursor(int);
 static void tdeletechar(int);
+static void tdeleteimages(void);
 static void tdeleteline(int);
 static void tinsertblank(int);
 static void tinsertblankline(int);
@@ -221,13 +182,13 @@ static char base64dec_getc(const char **);
 static ssize_t xwrite(int, const char *, size_t);
 
 /* Globals */
-static Term term;
 static Selection sel;
 static CSIEscape csiescseq;
 static STREscape strescseq;
 static int iofd = 1;
 static int cmdfd;
 static pid_t pid;
+sixel_state_t sixel_st;
 
 static const uchar utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
 static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
@@ -1033,6 +994,7 @@ treset(void)
 		tmoveto(0, 0);
 		tcursor(CURSOR_SAVE);
 		tclearregion(0, 0, term.col-1, term.row-1);
+		tdeleteimages();
 		tswapscreen();
 	}
 }
@@ -1049,9 +1011,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();
 }
@@ -1061,6 +1026,10 @@ tscrolldown(int orig, int n)
 {
 	int i;
 	Line temp;
+	int bot = term.bot;
+	int scr = 0;
+	int itop = orig + scr, ibot = bot + scr;
+	ImageList *im, *next;
 
 	LIMIT(n, 0, term.bot-orig+1);
 
@@ -1073,6 +1042,16 @@ tscrolldown(int orig, int n)
 		term.line[i-n] = temp;
 	}
 
+	/* move images, if they are inside the scrolling region */
+	for (im = term.images; im; im = next) {
+		next = im->next;
+		if (im->y >= itop && im->y <= ibot) {
+			im->y += n;
+			if (im->y > ibot)
+				delete_image(im);
+		}
+	}
+
 	selscroll(orig, n);
 }
 
@@ -1081,6 +1060,10 @@ tscrollup(int orig, int n)
 {
 	int i;
 	Line temp;
+	int bot = term.bot;
+	int scr = 0;
+	int itop = orig + scr, ibot = bot + scr;
+	ImageList *im, *next;
 
 	LIMIT(n, 0, term.bot-orig+1);
 
@@ -1093,6 +1076,16 @@ tscrollup(int orig, int n)
 		term.line[i+n] = temp;
 	}
 
+	/* move images, if they are inside the scrolling region */
+	for (im = term.images; im; im = next) {
+		next = im->next;
+		if (im->y >= itop && im->y <= ibot) {
+			im->y -= n;
+			if (im->y < itop)
+				delete_image(im);
+		}
+	}
+
 	selscroll(orig, -n);
 }
 
@@ -1316,6 +1309,17 @@ tinsertblankline(int n)
 		tscrolldown(term.c.y, n);
 }
 
+void
+tdeleteimages(void)
+{
+	ImageList *im, *next;
+
+	for (im = term.images; im; im = next) {
+		next = im->next;
+		delete_image(im);
+	}
+}
+
 void
 tdeleteline(int n)
 {
@@ -1612,6 +1616,12 @@ tsetmode(int priv, int set, const int *args, int narg)
 				      and can be mistaken for other control
 				      codes. */
 				break;
+			case 80: /* DECSDM -- Sixel Display Mode */
+				MODBIT(term.mode, set, MODE_SIXEL_SDM);
+				break;
+			case 8452: /* sixel scrolling leaves cursor to right of graphic */
+				MODBIT(term.mode, set, MODE_SIXEL_CUR_RT);
+				break;
 			default:
 				fprintf(stderr,
 					"erresc: unknown private set/reset mode %d\n",
@@ -1648,7 +1658,9 @@ void
 csihandle(void)
 {
 	char buf[40];
-	int len;
+	int n = 0, len;
+	ImageList *im, *next;
+	int pi, pa;
 
 	switch (csiescseq.mode[0]) {
 	default:
@@ -1759,6 +1771,11 @@ csihandle(void)
 			break;
 		case 2: /* all */
 			tclearregion(0, 0, term.col-1, term.row-1);
+			tdeleteimages();
+			break;
+		case 6: /* sixels */
+			tdeleteimages();
+			tfulldirt();
 			break;
 		default:
 			goto unknown;
@@ -1779,6 +1796,35 @@ csihandle(void)
 		}
 		break;
 	case 'S': /* SU -- Scroll <n> line up */
+		if (csiescseq.priv) {
+			if (csiescseq.narg > 1) {
+			/* XTSMGRAPHICS */
+				pi = csiescseq.arg[0];
+				pa = csiescseq.arg[1];
+				if (pi == 1 && (pa == 1 || pa == 2 || pa == 4)) {
+					/* number of sixel color registers */
+					/* (read, reset and read the maximum value give the same 
+					 * response) */
+					n = snprintf(buf, sizeof buf, "\033[?1;0;%dS", DECSIXEL_PALETTE_MAX);
+					ttywrite(buf, n, 1);
+					break;
+				} else if (pi == 2 && (pa == 1 || pa == 2 || pa == 4)) {
+					/* sixel graphics geometry (in pixels) */
+					/* (read, reset and read the maximum value give the same 
+					 * response) */
+					n = snprintf(buf, sizeof buf, "\033[?2;0;%d;%dS",
+					MIN(term.col * win.cw, DECSIXEL_WIDTH_MAX),
+					MIN(term.row * win.ch, DECSIXEL_HEIGHT_MAX));
+					ttywrite(buf, n, 1);
+					break;
+				}
+				/* the number of color registers and sixel geometry can't be 
+				 * changed */
+				n = snprintf(buf, sizeof buf, "\033[?%d;3;0S", pi); /* failure */
+				ttywrite(buf, n, 1);
+			}
+			goto unknown;
+		}
 		DEFAULT(csiescseq.arg[0], 1);
 		tscrollup(term.top, csiescseq.arg[0]);
 		break;
@@ -1847,6 +1893,29 @@ csihandle(void)
 	case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
 		tcursor(CURSOR_SAVE);
 		break;
+	case 't': /* title stack operations ; XTWINOPS */
+		switch (csiescseq.arg[0]) {
+			case 14: /* text area size in pixels */
+				if (csiescseq.narg > 1)
+				goto unknown;
+				n = snprintf(buf, sizeof buf, "\033[4;%d;%dt",
+				             term.row * win.ch, term.col * win.cw);
+				ttywrite(buf, n, 1);
+				break;
+			case 16: /* character cell size in pixels */
+				n = snprintf(buf, sizeof buf, "\033[6;%d;%dt",
+				             win.ch, win.cw);
+				ttywrite(buf, n, 1);
+				break;
+			case 18: /* size of the text area in characters */
+				n = snprintf(buf, sizeof buf, "\033[8;%d;%dt",
+				             term.row, term.col);
+				ttywrite(buf, n, 1);
+				break;
+			default:
+				goto unknown;
+		}
+		break;
 	case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
 		tcursor(CURSOR_LOAD);
 		break;
@@ -1928,6 +1997,11 @@ strhandle(void)
 		{ defaultbg, "background" },
 		{ defaultcs, "cursor" }
 	};
+	ImageList *im, *newimages, *next, *tail;
+	int i, x, y, x1, y1, x2, y2, numimages;
+	int cx, cy;
+	Line line;
+	int scr = 0;
 
 	term.esc &= ~(ESC_STR_END|ESC_STR);
 	strparse();
@@ -2010,6 +2084,73 @@ strhandle(void)
 		xsettitle(strescseq.args[0]);
 		return;
 	case 'P': /* DCS -- Device Control String */
+		if (IS_SET(MODE_SIXEL)) {
+			term.mode &= ~MODE_SIXEL;
+			if (!sixel_st.image.data) {
+				sixel_parser_deinit(&sixel_st);
+				return;
+			}
+			cx = IS_SET(MODE_SIXEL_SDM) ? 0 : term.c.x;
+			cy = IS_SET(MODE_SIXEL_SDM) ? 0 : term.c.y;
+			if ((numimages = sixel_parser_finalize(&sixel_st, &newimages,
+					cx, cy + scr, win.cw, win.ch)) <= 0) {
+				sixel_parser_deinit(&sixel_st);
+				perror("sixel_parser_finalize() failed");
+				return;
+			}
+			sixel_parser_deinit(&sixel_st);
+			x1 = newimages->x;
+			y1 = newimages->y;
+			x2 = x1 + newimages->cols;
+			y2 = y1 + numimages;
+			if (newimages->transparent) {
+				for (tail = term.images; tail && tail->next; tail = tail->next);
+			} else {
+				for (tail = NULL, im = term.images; im; im = next) {
+					next = im->next;
+					if (im->x >= x1 && im->x + im->cols <= x2 &&
+					    im->y >= y1 && im->y <= y2) {
+						delete_image(im);
+						continue;
+					}
+					tail = im;
+				}
+			}
+			if (tail) {
+				tail->next = newimages;
+				newimages->prev = tail;
+			} else {
+				term.images = newimages;
+			}
+			x2 = MIN(x2, term.col);
+			for (i = 0, im = newimages; im; im = next, i++) {
+				next = im->next;
+				if (IS_SET(MODE_SIXEL_SDM)) {
+					if (i >= term.row) {
+						delete_image(im);
+						continue;
+					}
+					im->y = i + scr;
+					line = term.line[i];
+				} else {
+					im->y = term.c.y + scr;
+					line = term.line[term.c.y];
+				}
+				for (x = im->x; x < x2; x++) {
+					line[x].mode |= ATTR_SIXEL;
+				}
+				term.dirty[MIN(im->y, term.row-1)] = 1;
+				if (!IS_SET(MODE_SIXEL_SDM) && i < numimages-1) {
+					im->next = NULL;
+					tnewline(0);
+					im->next = next;
+				}
+			}
+			/* if mode 8452 is set, sixel scrolling leaves cursor to right of graphic */
+			if (!IS_SET(MODE_SIXEL_SDM) && IS_SET(MODE_SIXEL_CUR_RT))
+				term.c.x = MIN(term.c.x + newimages->cols, term.col-1);
+		}
+		return;
 	case '_': /* APC -- Application Program Command */
 	case '^': /* PM -- Privacy Message */
 		return;
@@ -2203,9 +2344,12 @@ tdectest(char c)
 void
 tstrsequence(uchar c)
 {
+	strreset();
+
 	switch (c) {
 	case 0x90:   /* DCS -- Device Control String */
 		c = 'P';
+		term.esc |= ESC_DCS;
 		break;
 	case 0x9f:   /* APC -- Application Program Command */
 		c = '_';
@@ -2319,6 +2463,39 @@ tcontrolcode(uchar ascii)
 	term.esc &= ~(ESC_STR_END|ESC_STR);
 }
 
+void
+dcshandle(void)
+{
+	int bgcolor, transparent;
+	unsigned char r, g, b, a = 255;
+
+	switch (csiescseq.mode[0]) {
+	default:
+	unknown:
+		fprintf(stderr, "erresc: unknown csi ");
+		csidump();
+			/* die(""); */
+		break;
+	case 'q': /* DECSIXEL */
+		transparent = (csiescseq.narg >= 2 && csiescseq.arg[1] == 1);
+		if (IS_TRUECOL(term.c.attr.bg)) {
+			r = term.c.attr.bg >> 16 & 255;
+			g = term.c.attr.bg >> 8 & 255;
+			b = term.c.attr.bg >> 0 & 255;
+		} else {
+			xgetcolor(term.c.attr.bg, &r, &g, &b);
+			if (term.c.attr.bg == defaultbg)
+				a = dc.col[defaultbg].pixel >> 24 & 255;
+		}
+		bgcolor = a << 24 | r << 16 | g << 8 | b;
+		if (sixel_parser_init(&sixel_st, transparent, (255 << 24), bgcolor, 1, win.cw, win.ch) != 0)
+			perror("sixel_parser_init() failed");
+		term.mode |= MODE_SIXEL;
+		break;
+	}
+}
+
+
 /*
  * returns 1 when the sequence is finished and it hasn't to read
  * more characters for this sequence, otherwise 0
@@ -2337,6 +2514,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 */
@@ -2435,13 +2613,16 @@ tputc(Rune u)
 	 * character.
 	 */
 	if (term.esc & ESC_STR) {
-		if (u == '\a' || u == 030 || u == 032 || u == 033 ||
-		   ISCONTROLC1(u)) {
+		if (u == '\a' || u == 030 || u == 032 || u == 033 || ISCONTROLC1(u)) {
+			term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
 			term.esc &= ~(ESC_START|ESC_STR);
 			term.esc |= ESC_STR_END;
 			goto check_control_code;
 		}
 
+		if (term.esc & ESC_DCS)
+			goto check_control_code;
+
 		if (strescseq.len+len >= strescseq.siz) {
 			/*
 			 * Here is a bug in terminals. If the user never sends
@@ -2495,6 +2676,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) {
@@ -2565,7 +2755,10 @@ twrite(const char *buf, int buflen, int show_ctrl)
 	int n;
 
 	for (n = 0; n < buflen; n += charsize) {
-		if (IS_SET(MODE_UTF8)) {
+		if (IS_SET(MODE_SIXEL) && sixel_st.state != PS_ESC) {
+			charsize = sixel_parser_parse(&sixel_st, (const unsigned char*)buf + n, buflen - n);
+			continue;
+		} else if (IS_SET(MODE_UTF8)) {
 			/* process a complete utf8 char */
 			charsize = utf8decode(buf + n, &u, buflen - n);
 			if (charsize == 0)
@@ -2597,6 +2790,9 @@ tresize(int col, int row)
 	int mincol = MIN(col, term.col);
 	int *bp;
 	TCursor c;
+	int x, x2;
+	Line line;
+	ImageList *im, *next;
 
 	if (col < 1 || row < 1) {
 		fprintf(stderr,
@@ -2669,6 +2865,23 @@ tresize(int col, int row)
 		tcursor(CURSOR_LOAD);
 	}
 	term.c = c;
+
+	/* expand images into new text cells to prevent them from being deleted in
+	 * xfinishdraw() that draws the images */
+	for (i = 0; i < 2; i++) {
+		for (im = term.images; im; im = next) {
+			next = im->next;
+			if (im->y < 0 || im->y >= term.row) {
+				delete_image(im);
+				continue;
+			}
+			line = term.line[im->y];
+			x2 = MIN(im->x + im->cols, term.col);
+			for (x = im->x; x < x2; x++)
+				line[x].mode |= ATTR_SIXEL;
+		}
+		tswapscreen();
+	}
 }
 
 void
diff --git a/st.h b/st.h
@@ -1,7 +1,14 @@
 /* See LICENSE for license details. */
 
 #include <stdint.h>
+#include <time.h>
 #include <sys/types.h>
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/cursorfont.h>
+#include <X11/keysym.h>
+#include <X11/Xft/Xft.h>
+#include <X11/XKBlib.h>
 
 /* macros */
 #define MIN(a, b)		((a) < (b) ? (a) : (b))
@@ -21,23 +28,39 @@
 #define IS_TRUECOL(x)		(1 << 24 & (x))
 
 enum glyph_attribute {
-	ATTR_NULL       = 0,
-	ATTR_BOLD       = 1 << 0,
-	ATTR_FAINT      = 1 << 1,
-	ATTR_ITALIC     = 1 << 2,
-	ATTR_UNDERLINE  = 1 << 3,
-	ATTR_BLINK      = 1 << 4,
-	ATTR_REVERSE    = 1 << 5,
-	ATTR_INVISIBLE  = 1 << 6,
-	ATTR_STRUCK     = 1 << 7,
-	ATTR_WRAP       = 1 << 8,
-	ATTR_WIDE       = 1 << 9,
-	ATTR_WDUMMY     = 1 << 10,
-	ATTR_BOXDRAW    = 1 << 11,
-	ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
-	ATTR_DIRTYUNDERLINE = 1 << 15,
+	ATTR_NULL               = 0,
+	ATTR_BOLD               = 1 << 0,
+	ATTR_FAINT              = 1 << 1,
+	ATTR_ITALIC             = 1 << 2,
+	ATTR_UNDERLINE          = 1 << 3,
+	ATTR_BLINK              = 1 << 4,
+	ATTR_REVERSE            = 1 << 5,
+	ATTR_INVISIBLE          = 1 << 6,
+	ATTR_STRUCK             = 1 << 7,
+	ATTR_WRAP               = 1 << 8,
+	ATTR_WIDE               = 1 << 9,
+	ATTR_WDUMMY             = 1 << 10,
+	ATTR_BOXDRAW            = 1 << 11,
+	ATTR_SIXEL              = 1 << 12,
+	ATTR_DIRTYUNDERLINE     = 1 << 13,
+	ATTR_BOLD_FAINT         = ATTR_BOLD | ATTR_FAINT,
 };
 
+typedef struct _ImageList {
+	struct _ImageList *next, *prev;
+	unsigned char *pixels;
+	void *pixmap;
+	void *clipmask;
+	int width;
+	int height;
+	int x;
+	int y;
+	int cols;
+	int cw;
+	int ch;
+	int transparent;
+} ImageList;
+
 enum drawing_mode {
     DRAW_NONE = 0,
     DRAW_BG = 1 << 0,
@@ -67,6 +90,10 @@ typedef unsigned short ushort;
 
 typedef uint_least32_t Rune;
 
+typedef XftDraw *Draw;
+typedef XftColor Color;
+typedef XftGlyphFontSpec GlyphFontSpec;
+
 #define Glyph Glyph_
 typedef struct {
 	Rune u;           /* character code */
@@ -79,6 +106,32 @@ typedef struct {
 
 typedef Glyph *Line;
 
+typedef struct {
+	Glyph attr; /* current char attributes */
+	int x;
+	int y;
+	char state;
+} TCursor;
+
+typedef struct {
+	int mode;
+	int type;
+	int snap;
+	/*
+	 * Selection variables:
+	 * nb – normalized coordinates of the beginning of the selection
+	 * ne – normalized coordinates of the end of the selection
+	 * ob – original coordinates of the beginning of the selection
+	 * oe – original coordinates of the end of the selection
+	 */
+	struct {
+		int x, y;
+	} nb, ne, ob, oe;
+
+	int alt;
+} Selection;
+
+
 typedef union {
 	int i;
 	uint ui;
@@ -87,6 +140,93 @@ typedef union {
 	const char *s;
 } Arg;
 
+typedef struct {
+	Display *dpy;
+	Colormap cmap;
+	Window win;
+	Drawable buf;
+	GlyphFontSpec *specbuf; /* font spec buffer used for rendering */
+	Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;
+	struct {
+		XIM xim;
+		XIC xic;
+		XPoint spot;
+		XVaNestedList spotlist;
+	} ime;
+	Draw draw;
+	Visual *vis;
+	XSetWindowAttributes attrs;
+	int scr;
+	int isfixed; /* is fixed geometry? */
+	int l, t; /* left and top offset */
+	int gm; /* geometry mask */
+} XWindow;
+
+typedef struct {
+	Atom xtarget;
+	char *primary, *clipboard;
+	struct timespec tclick1;
+	struct timespec tclick2;
+} XSelection;
+
+/* Font structure */
+#define Font Font_
+typedef struct {
+	int height;
+	int width;
+	int ascent;
+	int descent;
+	int badslant;
+	int badweight;
+	short lbearing;
+	short rbearing;
+	XftFont *match;
+	FcFontSet *set;
+	FcPattern *pattern;
+} Font;
+
+/* Internal representation of the screen */
+typedef struct {
+	int row;      /* nb row */
+	int col;      /* nb col */
+	Line *line;   /* screen */
+	Line *alt;    /* alternate screen */
+	int *dirty;   /* dirtyness of lines */
+	TCursor c;    /* cursor */
+	int ocx;      /* old cursor col */
+	int ocy;      /* old cursor row */
+	int top;      /* top    scroll limit */
+	int bot;      /* bottom scroll limit */
+	int mode;     /* terminal mode flags */
+	int esc;      /* escape state flags */
+	char trantbl[4]; /* charset table translation */
+	int charset;  /* current charset */
+	int icharset; /* selected charset for sequence */
+	int *tabs;
+	ImageList *images;     /* sixel images */
+	ImageList *images_alt; /* sixel images for alternate screen */
+	Rune lastc;   /* last printed char outside of sequence, 0 if control */
+} Term;
+
+/* Purely graphic info */
+typedef struct {
+	int tw, th; /* tty width and height */
+	int w, h; /* window width and height */
+	int ch; /* char height */
+	int cw; /* char width  */
+	int cyo; /* char y offset */
+	int mode; /* window state/mode flags */
+	int cursor; /* cursor style */
+} TermWindow;
+
+/* Drawing Context */
+typedef struct {
+	Color *col;
+	size_t collen;
+	Font font, bfont, ifont, ibfont;
+	GC gc;
+} DC;
+
 void die(const char *, ...);
 void redraw(void);
 void draw(void);
@@ -144,3 +284,9 @@ extern unsigned int defaultfg;
 extern unsigned int defaultbg;
 extern unsigned int defaultcs;
 extern const int boxdraw, boxdraw_bold, boxdraw_braille;
+
+extern DC dc;
+extern XWindow xw;
+extern XSelection xsel;
+extern TermWindow win;
+extern Term term;
diff --git a/x.c b/x.c
@@ -16,10 +16,12 @@
 #include <X11/XKBlib.h>
 #include <X11/Xcursor/Xcursor.h>
 #include <X11/Xresource.h>
+#include <Imlib2.h>
 
 char *argv0;
 #include "arg.h"
 #include "st.h"
+#include "sixel.h"
 #include "win.h"
 
 /* types used in config.h */
@@ -96,74 +98,6 @@ static void ttysend(const Arg *);
 #define TRUEGREEN(x)		(((x) & 0xff00))
 #define TRUEBLUE(x)		(((x) & 0xff) << 8)
 
-typedef XftDraw *Draw;
-typedef XftColor Color;
-typedef XftGlyphFontSpec GlyphFontSpec;
-
-/* Purely graphic info */
-typedef struct {
-	int tw, th; /* tty width and height */
-	int w, h; /* window width and height */
-	int ch; /* char height */
-	int cw; /* char width  */
-	int cyo; /* char y offset */
-	int mode; /* window state/mode flags */
-	int cursor; /* cursor style */
-} TermWindow;
-
-typedef struct {
-	Display *dpy;
-	Colormap cmap;
-	Window win;
-	Drawable buf;
-	GlyphFontSpec *specbuf; /* font spec buffer used for rendering */
-	Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;
-	struct {
-		XIM xim;
-		XIC xic;
-		XPoint spot;
-		XVaNestedList spotlist;
-	} ime;
-	Draw draw;
-	Visual *vis;
-	XSetWindowAttributes attrs;
-	int scr;
-	int isfixed; /* is fixed geometry? */
-	int l, t; /* left and top offset */
-	int gm; /* geometry mask */
-} XWindow;
-
-typedef struct {
-	Atom xtarget;
-	char *primary, *clipboard;
-	struct timespec tclick1;
-	struct timespec tclick2;
-} XSelection;
-
-/* Font structure */
-#define Font Font_
-typedef struct {
-	int height;
-	int width;
-	int ascent;
-	int descent;
-	int badslant;
-	int badweight;
-	short lbearing;
-	short rbearing;
-	XftFont *match;
-	FcFontSet *set;
-	FcPattern *pattern;
-} Font;
-
-/* Drawing Context */
-typedef struct {
-	Color *col;
-	size_t collen;
-	Font font, bfont, ifont, ibfont;
-	GC gc;
-} DC;
-
 static inline ushort sixd_to_16bit(int);
 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int);
@@ -240,10 +174,11 @@ static void (*handler[LASTEvent])(XEvent *) = {
 };
 
 /* Globals */
-static DC dc;
-static XWindow xw;
-static XSelection xsel;
-static TermWindow win;
+Term term;
+DC dc;
+XWindow xw;
+XSelection xsel;
+TermWindow win;
 
 /* Font Ring Cache */
 enum {
@@ -325,14 +260,27 @@ zoom(const Arg *arg)
 	Arg larg;
 
 	larg.f = usedfontsize + arg->f;
-	zoomabs(&larg);
+	if (larg.f >= 1.0)
+		zoomabs(&larg);
 }
 
 void
 zoomabs(const Arg *arg)
 {
+	ImageList *im;
+
 	xunloadfonts();
 	xloadfonts(usedfont, arg->f);
+
+	for (im = term.images; im; im = im->next) {
+		if (im->pixmap)
+			XFreePixmap(xw.dpy, (Drawable)im->pixmap);
+		if (im->clipmask)
+			XFreePixmap(xw.dpy, (Drawable)im->clipmask);
+		im->pixmap = NULL;
+		im->clipmask = NULL;
+	}
+
 	cresize(0, 0);
 	redraw();
 	xhints();
@@ -2115,11 +2063,113 @@ xdrawline(Line line, int x1, int y1, int x2)
 void
 xfinishdraw(void)
 {
-	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);
+	ImageList *im, *next;
+	Imlib_Image origin, scaled;
+	XGCValues gcvalues;
+	GC gc;
+	int width, height;
+	int x, x2, del, destx, desty;
+	Line line;
+
+	for (im = term.images; im; im = next) {
+		next = im->next;
+
+		/* do not draw or process the image, if it is not visible */
+		if (im->x >= term.col || im->y >= term.row || im->y < 0)
+			continue;
+
+		/* scale the image */
+		width = MAX(im->width * win.cw / im->cw, 1);
+		height = MAX(im->height * win.ch / im->ch, 1);
+		if (!im->pixmap) {
+			im->pixmap = (void *)XCreatePixmap(xw.dpy, xw.win, width, height,
+				DefaultDepth(xw.dpy, xw.scr)
+			);
+			if (!im->pixmap)
+				continue;
+			if (win.cw == im->cw && win.ch == im->ch) {
+				XImage ximage = {
+					.format = ZPixmap,
+					.data = (char *)im->pixels,
+					.width = im->width,
+					.height = im->height,
+					.xoffset = 0,
+					.byte_order = sixelbyteorder,
+					.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, width, height);
+				if (im->transparent)
+					im->clipmask = (void *)sixel_create_clipmask((char *)im->pixels, width, height);
+			} else {
+				origin = imlib_create_image_using_data(im->width, im->height, (DATA32 *)im->pixels);
+				if (!origin)
+					continue;
+				imlib_context_set_image(origin);
+				imlib_image_set_has_alpha(1);
+				imlib_context_set_anti_alias(im->transparent ? 0 : 1); /* anti-aliasing messes up the clip mask */
+				scaled = imlib_create_cropped_scaled_image(0, 0, im->width, im->height, width, height);
+				imlib_free_image_and_decache();
+				if (!scaled)
+					continue;
+				imlib_context_set_image(scaled);
+				imlib_image_set_has_alpha(1);
+				XImage ximage = {
+					.format = ZPixmap,
+					.data = (char *)imlib_image_get_data_for_reading_only(),
+					.width = width,
+					.height = height,
+					.xoffset = 0,
+					.byte_order = sixelbyteorder,
+					.bitmap_bit_order = MSBFirst,
+					.bits_per_pixel = 32,
+					.bytes_per_line = width * 4,
+					.bitmap_unit = 32,
+					.bitmap_pad = 32,
+					.depth = 24
+				};
+				XPutImage(xw.dpy, (Drawable)im->pixmap, dc.gc, &ximage, 0, 0, 0, 0, width, height);
+				if (im->transparent)
+					im->clipmask = (void *)sixel_create_clipmask((char *)imlib_image_get_data_for_reading_only(), width, height);
+				imlib_free_image_and_decache();
+			}
+		}
+
+		/* clip the image so it does not go over to borders */
+		x2 = MIN(im->x + im->cols, term.col);
+		width = MIN(width, (x2 - im->x) * win.cw);
+
+		/* delete the image if the text cells behind it have been changed */
+		line = term.line[im->y];
+		for (del = 0, x = im->x; x < x2; x++) {
+			if ((del = !(line[x].mode & ATTR_SIXEL)))
+				break;
+		}
+		if (del) {
+			delete_image(im);
+			continue;
+		}
+
+		/* draw the image */
+		memset(&gcvalues, 0, sizeof(gcvalues));
+		gcvalues.graphics_exposures = False;
+		gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, &gcvalues);
+		destx = borderpx + im->x * win.cw;
+		desty = borderpx + im->y * win.ch;
+		if (im->clipmask) {
+			XSetClipMask(xw.dpy, gc, (Drawable)im->clipmask);
+			XSetClipOrigin(xw.dpy, gc, destx, desty);
+		}
+		XCopyArea(xw.dpy, (Drawable)im->pixmap, xw.buf, gc, 0, 0, width, height, destx, desty);
+		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);
 }
 
 void