// 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 #include /* 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; }