--- src/Makefile.in +++ src/Makefile.in @@ -40,7 +40,7 @@ COMMON = \ screen.o scrollbar.o scrollbar-next.o scrollbar-rxvt.o \ scrollbar-xterm.o scrollbar-plain.o xdefaults.o encoding.o \ rxvttoolkit.o rxvtutil.o keyboard.o rxvtimg.o \ - ev_cpp.o fdpass_wrapper.o ptytty_wrapper.o @PERL_O@ + ev_cpp.o fdpass_wrapper.o ptytty_wrapper.o sixel.o @PERL_O@ COMMON_DAEMON = rxvtdaemon.o @@ -289,3 +289,5 @@ rxvtperl.o: ../libptytty/src/estl.h emman.h rxvtfont.h rxvttoolkit.h rxvtperl.o: callback.h rxvtimg.h scrollbar.h ../libptytty/src/libptytty.h rxvtperl.o: rxvtperl.h hookinc.h rsinc.h optinc.h keyboard.h perlxsi.c rxvtperl.o: iom_perl.h +sixel.o: sixel.h +sixel_hls.o: sixel_hls.h --- src/command.C +++ src/command.C @@ -51,6 +51,7 @@ #include "rxvtperl.h" #include "version.h" #include "command.h" +#include "sixel.h" #ifdef KEYSYM_RESOURCE # include "keyboard.h" @@ -2865,6 +2866,8 @@ rxvt_term::process_csi_seq () case '?': if (ch == 'h' || ch == 'l' || ch == 'r' || ch == 's' || ch == 't') process_terminal_mode (ch, priv, nargs, arg); + else if (ch == 'S') + process_graphics_attributes (nargs, arg); break; case '!': @@ -3262,15 +3265,215 @@ rxvt_term::get_to_st (unicode_t &ends_how) void rxvt_term::process_dcs_seq () { - /* - * Not handled yet - */ - - unicode_t eh; - char *s = get_to_st (eh); - if (s) - free (s); + unicode_t ch; + unsigned char c; + const int max_params = 16; + int params[max_params] = { 0 }; + int nparams = 0; + int cmd = 0; + enum { + DCS_START, + DCS_PARAM, + DCS_INTERMEDIATE, + DCS_PASSTHROUGH, + DCS_IGNORE, + DCS_ESC + }; + int st = DCS_START; + int x, y; + sixel_state_t sixel_st = { PS_GROUND }; + imagelist_t *new_image; + line_t l; + + while (1) { + if ((ch = next_char ()) == NOCHAR) { + pty_fill (); + continue; + } + c = ch & 0xff; + switch (st) { + case DCS_START: + case DCS_PARAM: + switch (c) { + case '\030': /* CAN */ + goto end; + case '\032': /* SUB */ + st = DCS_IGNORE; + break; + case '\033': + st = DCS_ESC; + break; + case ' ' ... '/': + cmd = cmd << 8 | c; + st = cmd > (0xff << 16) ? DCS_IGNORE : DCS_INTERMEDIATE; + break; + case '0' ... '9': + params[nparams] = params[nparams] * 10 + c - '0'; + st = params[nparams] > 256 ? DCS_IGNORE : DCS_PARAM; + break; + case ';': + if (++nparams == max_params) + st = DCS_IGNORE; + else + params[nparams] = 0; + break; + case ':': + st = DCS_IGNORE; + break; + case '<' ... '?': + cmd = cmd << 8 | c; + st = cmd > (0xff << 16) ? DCS_IGNORE : DCS_PARAM; + break; + case '@' ... '~': + cmd = cmd << 8 | c; + st = cmd > (0xff << 16) ? DCS_IGNORE : DCS_PASSTHROUGH; + break; + default: + st = DCS_IGNORE; + break; + } + break; + case DCS_INTERMEDIATE: + switch (c) { + case '\030': /* CAN */ + goto end; + case '\032': /* SUB */ + st = DCS_IGNORE; + break; + case '\033': + st = DCS_ESC; + break; + case ' ' ... '/': + cmd = cmd << 8 | c; + st = cmd > (0xff << 16) ? DCS_IGNORE : DCS_INTERMEDIATE; + break; + case '@' ... '~': + cmd = cmd << 8 | c; + st = cmd > (0xff << 16) ? DCS_IGNORE : DCS_PASSTHROUGH; + break; + default: + st = DCS_IGNORE; + break; + } + break; + case DCS_PASSTHROUGH: + switch (c) { + case '\030': /* CAN */ + goto end; + case '\032': /* SUB */ + st = DCS_IGNORE; + break; + case '\033': + st = DCS_ESC; + break; + default: + switch (cmd) { + case 'q': /* DECSIXEL */ + switch (sixel_st.state) { + case PS_GROUND: + { + rgba fg = pix_colors[Color_fg]; + rgba bg = pix_colors[Color_bg]; + sixel_parser_init(&sixel_st, + fg.b >> 8 << 16 | fg.g >> 8 << 8 | fg.r >> 8, + bg.b >> 8 << 16 | bg.g >> 8 << 8 | bg.r >> 8, + 1, fwidth, fheight); + } + break; + default: + sixel_parser_parse(&sixel_st, &c, 1); + break; + } + break; + default: + break; + } + break; + } + break; + case DCS_IGNORE: + switch (c) { + case '\030': /* CAN */ + goto end; + case '\032': /* SUB */ + st = DCS_IGNORE; + break; + case '\033': + st = DCS_ESC; + break; + default: + st = DCS_IGNORE; + break; + } + break; + case DCS_ESC: + switch (c) { + case '\\': + switch (cmd) { + case 'q': /* DECSIXEL */ + new_image = (imagelist_t *)rxvt_calloc (1, sizeof(imagelist_t)); + new_image->pixels = (unsigned char *)rxvt_malloc (sixel_st.image.width * sixel_st.image.height * 4); + (void) sixel_parser_finalize (&sixel_st, new_image->pixels); + sixel_parser_deinit(&sixel_st); + new_image->col = screen.cur.col; + new_image->row = screen.cur.row + virtual_lines; + new_image->pxwidth = sixel_st.image.width; + new_image->pxheight = sixel_st.image.height; + if (this->images) { + imagelist_t *im; + for (im = this->images; im->next; im = im->next) + ; + new_image->prev = im; + im->next = new_image; + } else { + this->images = new_image; + } + for (y = 0; y < Pixel2Row (new_image->pxheight + fheight - 1); ++y) + { + if ((priv_modes & PrivMode_SixelDisplay)) + l = ROW(screen.cur.row + y); + else + l = ROW(screen.cur.row); + for (x = 0; x < min (ncol - screen.cur.col, Pixel2Col (new_image->pxwidth + fwidth - 1)); ++x) + { + l.t[screen.cur.col + x] = CHAR_IMAGE; + l.r[screen.cur.col + x] = RS_None; + } + if (!(priv_modes & PrivMode_SixelDisplay)) + { + if (y == Pixel2Row (new_image->pxheight + fheight - 1) - 1) // on last row + { + if ((priv_modes & PrivMode_SixelScrsRight)) + { + screen.cur.col += x; + } + else + { + scr_index (UP); + if ((priv_modes & PrivMode_SixelScrsLeft)) + scr_gotorc (0, 0, R_RELATIVE); + } + } + else + { + scr_index (UP); + } + } + } + break; + default: + break; + } + goto end; + default: + goto end; + } + default: + break; + } + } +end: return; } @@ -3695,6 +3898,7 @@ rxvt_term::process_terminal_mode (int mode, int priv ecb_unused, unsigned int na #ifndef NO_BACKSPACE_KEY { 67, PrivMode_BackSpace }, // DECBKM #endif + { 80, PrivMode_SixelDisplay }, // DECSDM sixel display mode { 1000, PrivMode_MouseX11 }, { 1002, PrivMode_MouseBtnEvent }, { 1003, PrivMode_MouseAnyEvent }, @@ -3715,6 +3919,10 @@ rxvt_term::process_terminal_mode (int mode, int priv ecb_unused, unsigned int na { 1049, PrivMode_Screen }, /* xterm extension, clear screen on ti rather than te */ // 1051, 1052, 1060, 1061 keyboard emulation NYI { 2004, PrivMode_BracketPaste }, + // 7730 sixel-scrolls-left mode, originally proposed by mintty + { 7730, PrivMode_SixelScrsLeft }, + // 8452 sixel-scrolls-right mode, originally proposed by RLogin + { 8452, PrivMode_SixelScrsRight }, }; if (nargs == 0) @@ -4031,6 +4239,62 @@ rxvt_term::process_sgr_mode (unsigned int nargs, const int *arg) } } +void +rxvt_term::process_graphics_attributes (unsigned int nargs, const int *arg) +{ + if (nargs != 3) + return; + switch (arg[0]) + { + case 1: /* number of sixel color palette */ + switch (arg[1]) + { + case 1: /* read */ + tt_printf ("\033[?%d;%d;%dS", arg[0], 0, DECSIXEL_PALETTE_MAX); + break; + case 2: /* reset to default */ + tt_printf ("\033[?%d;%d;%dS", arg[0], 0, DECSIXEL_PALETTE_MAX); + break; + case 3: /* set */ + if (arg[2] == DECSIXEL_PALETTE_MAX) + tt_printf ("\033[?%d;%d;%dS", arg[0], 0, DECSIXEL_PALETTE_MAX); + else + tt_printf ("\033[?%d;%d;%dS", arg[0], 3, 0); + break; + case 4: /* read the maximum value */ + tt_printf ("\033[?%d;%d;%dS", arg[0], 0, DECSIXEL_PALETTE_MAX); + break; + default: + tt_printf ("\033[?%d;%d;%dS", arg[0], 2, 0); + break; + } + break; + case 2: /* geometory of sixel graphics */ + switch (arg[1]) + { + case 1: /* read */ + tt_printf ("\033[?%d;%d;%d;%dS", arg[0], 0, ncol * fwidth, nrow * fheight); + break; + case 2: /* reset to default */ + tt_printf ("\033[?%d;%d;%d;%dS", arg[0], 3, 0, 0); + break; + case 3: /* set */ + tt_printf ("\033[?%d;%d;%d;%dS", arg[0], 3, 0, 0); + break; + case 4: /* read the maximum value */ + tt_printf ("\033[?%d;%d;%d;%dS", arg[0], 3, 0, 0); + break; + default: + tt_printf ("\033[?%d;%d;%d;%dS", arg[0], 2, 0, 0); + break; + } + break; + default: + tt_printf ("\033[?%d;%d;%dS", arg[0], 1, 0); + break; + } +} + void rxvt_term::set_cursor_style (int style) { --- src/command.h +++ src/command.h @@ -58,7 +58,7 @@ * two strings should be the same so that identical read (2) calls may be * used. */ -#define VT100_ANS "\033[?1;2c" /* vt100 answerback */ +#define VT100_ANS "\033[?1;2;4c" /* vt100 answerback(1;2) + sixel graphics(4) */ #ifndef ESCZ_ANSWER # define ESCZ_ANSWER VT100_ANS /* obsolete ANSI ESC[c */ #endif --- src/rxvt.h +++ src/rxvt.h @@ -389,6 +389,7 @@ enum { C0_CAN, C0_EM , C0_SUB, C0_ESC, C0_IS4, C0_IS3, C0_IS2, C0_IS1, }; #define CHAR_ST 0x9c /* 0234 */ +#define CHAR_IMAGE 0x01 /* special character for image area */ /* * XTerm Operating System Commands: ESC ] Ps;Pt (ST|BEL) @@ -575,6 +576,16 @@ enum { #define PrivMode_ExtMouseLeft (1UL<<23) #define PrivMode_ExtMouseRight (1UL<<24) // xterm pseudo-utf-8, but works in non-utf-8-locales #define PrivMode_BlinkingCursor (1UL<<25) +#define PrivMode_ExtMouseSgr (1UL<<26) // sgr mouse extension +#define PrivMode_SixelDisplay (1UL<<27) // sixel display mode +/* DECSET 7730: sixel scrolling end position + * on: sixel scrolling moves cursor to beginning of the line + * off(default): sixel scrolling moves cursor to left of graphics */ +#define PrivMode_SixelScrsLeft (1UL<<28) +/* DECSET 8452: sixel scrolling end position right + * on: sixel scrolling leaves cursor to right of graphic + * off(default): position after sixel depends on sixel_scrolls_left */ +#define PrivMode_SixelScrsRight (1UL<<29) #define PrivMode_mouse_report (PrivMode_MouseX10|PrivMode_MouseX11|PrivMode_MouseBtnEvent|PrivMode_MouseAnyEvent) @@ -901,6 +911,7 @@ struct TermWin_t int term_start; /* term lines start here */ int view_start; /* scrollback view starts here */ int top_row; /* topmost row index of scrollback */ + int virtual_lines; Window parent; /* parent identifier */ Window vt; /* vt100 window */ GC gc; /* GC for drawing */ @@ -984,6 +995,18 @@ enum { Opt_count }; +struct imagelist_t +{ + imagelist_t *prev, *next; + unsigned char *pixels; + Drawable drawable; + void *storage; + int col; + int row; + int pxwidth; + int pxheight; +}; + /* ------------------------------------------------------------------------- */ struct rxvt_vars : TermWin_t @@ -1005,6 +1028,7 @@ struct rxvt_vars : TermWin_t #ifdef OFF_FOCUS_FADING rxvt_color pix_colors_unfocused[TOTAL_COLORS]; #endif + imagelist_t *images; }; struct rxvt_term : zero_initialized, rxvt_vars, rxvt_screen @@ -1297,6 +1321,7 @@ struct rxvt_term : zero_initialized, rxvt_vars, rxvt_screen int privcases (int mode, unsigned long bit); void process_terminal_mode (int mode, int priv, unsigned int nargs, const int *arg); void process_sgr_mode (unsigned int nargs, const int *arg); + void process_graphics_attributes (unsigned int nargs, const int *arg); void set_cursor_style (int style); // init.C void init (stringvec *argv, stringvec *envv); --- src/screen.C +++ src/screen.C @@ -30,6 +30,174 @@ #include "rxvtperl.h" /* NECESSARY */ #include +#include + +// tempfile_t manipulation + +typedef struct { + FILE *fp; + unsigned int ref_counter; +} tempfile_t; + +typedef struct { + tempfile_t *tempfile; + size_t position; +} temp_storage_t; + +static tempfile_t *tempfile_current = NULL; +static size_t tempfile_num = 0; +static size_t const TEMPFILE_MAX_SIZE = 1024 * 1024 * 16; /* 16MB */ +static size_t const TEMPFILE_MAX_NUM = 16; + +static tempfile_t * +tempfile_new (void) +{ + tempfile_t *tempfile; + FILE *fp; + + fp = tmpfile(); + if (!fp) + return NULL; + + tempfile = (tempfile_t *)rxvt_malloc (sizeof(tempfile_t)); + if (!tempfile) + return NULL; + + tempfile->fp = fp; + tempfile->ref_counter = 1; + + tempfile_num++; + + return tempfile; +} + +static void +tempfile_destroy(tempfile_t *tempfile) +{ + if (tempfile == tempfile_current) + tempfile_current = NULL; + fclose((FILE *)tempfile->fp); + free (tempfile); + tempfile_num--; +} + +static void +tempfile_ref(tempfile_t *tempfile) +{ + tempfile->ref_counter++; +} + +static void +tempfile_deref(tempfile_t *tempfile) +{ + if (--tempfile->ref_counter == 0) + tempfile_destroy(tempfile); +} + +static size_t +tempfile_size(tempfile_t *tempfile) +{ + struct stat info; + + fstat(fileno(tempfile->fp), &info); + + return info.st_size; +} + +static tempfile_t * +tempfile_get(void) +{ + size_t size; + + if (!tempfile_current) + { + tempfile_current = tempfile_new(); + return tempfile_current; + } + + /* get file size */ + size = tempfile_size(tempfile_current); + + /* if the file size reaches TEMPFILE_MAX_SIZE, return new temporary file */ + if (size > TEMPFILE_MAX_SIZE) + { + tempfile_current = tempfile_new(); + return tempfile_current; + } + + /* increment reference counter */ + tempfile_ref (tempfile_current); + + return tempfile_current; +} + +static bool +tempfile_write(tempfile_t *tempfile, void *p, size_t pos, size_t size) +{ + size_t nbytes; + + fseek ((FILE *)tempfile->fp, pos, SEEK_SET); + nbytes = fwrite(p, 1, size, tempfile->fp); + if (nbytes != size) + return false; + + return true; +} + +static bool +tempfile_read(tempfile_t *tempfile, void *p, size_t pos, size_t size) +{ + size_t nbytes; + + fflush((FILE *)tempfile->fp); + fseek((FILE *)tempfile->fp, pos, SEEK_SET); + nbytes = fread (p, 1, size, (FILE *)tempfile->fp); + if (nbytes != size) + return false; + + return true; +} + +// temp_storage_t implementation + +static temp_storage_t * +storage_create(void) +{ + temp_storage_t *storage; + tempfile_t *tempfile; + + tempfile = tempfile_get (); + if (!tempfile) + return NULL; + + storage = (temp_storage_t *)rxvt_malloc (sizeof(temp_storage_t)); + if (!storage) + return NULL; + + storage->tempfile = tempfile; + storage->position = tempfile_size(storage->tempfile); + + return storage; +} + +static void +storage_destroy (temp_storage_t *storage) +{ + tempfile_deref (storage->tempfile); + free (storage); +} + +static bool +storage_write(temp_storage_t *storage, void *p, size_t size) +{ + return tempfile_write (storage->tempfile, p, storage->position, size); +} + +static bool +storage_read(temp_storage_t *storage, void *p, size_t size) +{ + return tempfile_read (storage->tempfile, p, storage->position, size); +} static inline void fill_text (text_t *start, text_t value, int len) @@ -673,6 +841,7 @@ rxvt_term::scr_scroll_text (int row1, int row2, int count) NOTHROW // scroll everything up 'count' lines term_start = (term_start + count) % total_rows; + virtual_lines += count; // now copy lines below the scroll region bottom to the // bottom of the screen again, so they look as if they @@ -2446,6 +2615,168 @@ rxvt_term::scr_refresh () NOTHROW } /* for (col....) */ } /* for (row....) */ + /* + * F: draw sixel images + */ + if (images) + { + imagelist_t *im; + GC clipgc; + int trow, brow; // top, bottom + int nlimit = 256; // num of allocated rects + XRectangle *rects = NULL, *itrect = NULL, *p = NULL; + int n, x, y; + XImage xi; + + // for each images + for (im = images; im; im = im->next) + { + trow = im->row - virtual_lines; + brow = trow + (im->pxheight + fheight - 1) / fheight; + + // if the image is out of scrollback, delete it. + if (brow <= top_row) + { + if (im->prev) + im->prev->next = im->next; + else + images = im->next; + if (im->next) + im->next->prev = im->prev; + if (im->drawable) + XFreePixmap (dpy, im->drawable); + if (im->storage) + storage_destroy ((temp_storage_t *)im->storage); + free (im->pixels); + free (im); + continue; + } + + // if the image is out of view, serialize into the storage object(im->storage). + if (trow >= view_start + nrow || brow <= view_start) + { + if (! im->storage) + { + if (im->drawable) + { + XFreePixmap (dpy, im->drawable); + im->drawable = NULL; + } + if (! im->storage) + im->storage = storage_create(); + if (! storage_write ((temp_storage_t *)im->storage, im->pixels, im->pxwidth * im->pxheight * 4)) + break; + free (im->pixels); + im->pixels = NULL; + } + continue; + } + + // ensure the pixmap(im->drawable) is available + if (! im->drawable) + { + // ensure the in-memory pixel buffer(im->pixels) is available + if (! im->pixels) + { + im->pixels = (unsigned char *)rxvt_malloc (im->pxwidth * im->pxheight * 4); + if (! storage_read ((temp_storage_t *)im->storage, im->pixels, im->pxwidth * im->pxheight * 4)) + break; + storage_destroy ((temp_storage_t *)im->storage); + im->storage = NULL; + assert (im->pixels); + } + + // create the pixmap object(im->drawable). + XInitImage (&xi); + xi.format = ZPixmap; + xi.data = (char *)im->pixels; + xi.width = im->pxwidth; + xi.height = im->pxheight; + xi.xoffset = 0; + xi.byte_order = LSBFirst; + xi.bitmap_bit_order = MSBFirst; + xi.bits_per_pixel = 32; + xi.bytes_per_line = im->pxwidth * 4; + xi.bitmap_unit = 32; + xi.bitmap_pad = 32; + xi.depth = 24; + im->drawable = XCreatePixmap(dpy, vt, im->pxwidth, im->pxheight, DefaultDepth (dpy, 0)); + XPutImage (dpy, im->drawable, gc, &xi, 0, 0, 0, 0, im->pxwidth, im->pxheight); + } + + // XXX: this is shoddy work!! + // construct clipping rectangle array + itrect = rects = NULL; + for (y = trow - view_start; y < brow - view_start; y++) // for rows + { + if (y >= 0 && y < nrow) + { + // compare recent two clip rectangles, combine them if they has same (left, width). + if (itrect - rects > 0 && itrect->x == (itrect - 1)->x && itrect->width == (itrect - 1)->width) + { + // test whether itrect adjacent to itrect - 1 + if ((itrect - 1)->y + (itrect - 1)->height == itrect->y) + { + itrect--; + itrect->height += fheight; + } + } + + for (x = im->col; x < im->col + Pixel2Col (im->pxwidth + fwidth - 1) && x < ncol; x++) // for cols + { + // lazy initialization for rectangle array + if (!rects) + itrect = rects = (XRectangle *)rxvt_malloc (sizeof (XRectangle) * nlimit); + if (rects == NULL) + break; + + // resize rectangle array + if (itrect - rects == nlimit) + { + p = (XRectangle *)rxvt_realloc (rects, sizeof (XRectangle) * (nlimit *= 2)); + if (p == NULL) + break; + rects = p; + } + + if (ROW(view_start + y).t[x] == CHAR_IMAGE) + { + // check if current cell is combinable with last clip rectangle + if (itrect - rects > 0 && (itrect - 1)->x + (itrect - 1)->width == Col2Pixel (x) && (itrect - 1)->y == Row2Pixel (y)) + { + (itrect - 1)->width += fwidth; + } + else + { + // add new clip rectangle + itrect->x = Col2Pixel (x); + itrect->y = Row2Pixel (y); + itrect->width = fwidth; + itrect->height = fheight; + itrect++; + } + } + } + } + } + + if (itrect - rects > 0) // if it should be drawn + { + clipgc = XCreateGC (dpy, vt, 0, 0); + // set clipping region + XSetClipRectangles (dpy, clipgc, 0, 0, rects, itrect - rects, YXSorted); + XCopyArea (dpy, im->drawable, vt, + clipgc, 0, 0, + im->pxwidth, im->pxheight, + (unsigned int)Width2Pixel (im->col), + (unsigned int)Height2Pixel (im->row - virtual_lines - view_start)); + XFreeGC (dpy, clipgc); + } + + free (rects); + } + } + /* * G: cleanup cursor and display outline cursor if necessary */ --- /dev/null +++ src/sixel.C @@ -0,0 +1,681 @@ +// 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 +#include /* isdigit */ +#include /* memcpy */ + +#include "sixel.h" + +#define SIXEL_XRGB(r,g,b) (((r) * 255 + 50) / 100) << 0 | \ + (((g) * 255 + 50) / 100) << 8 | \ + (((b) * 255 + 50) / 100) << 16 + +static colour 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% */ +}; + +/* + * Primary color hues: + * blue: 0 degrees + * red: 120 degrees + * green: 240 degrees + */ +int +hls_to_rgb(int hue, int lum, int sat) +{ + double min, max; + int r, g, b; + + if (sat == 0) { + r = g = b = lum; + } + + /* https://wikimedia.org/api/rest_v1/media/math/render/svg/17e876f7e3260ea7fed73f69e19c71eb715dd09d */ + max = lum + sat * (1.0 - (lum > 50 ? (((lum << 2) / 100.0) - 1.0): - (2 * (lum / 100.0) - 1.0))) / 2.0; + + /* https://wikimedia.org/api/rest_v1/media/math/render/svg/f6721b57985ad83db3d5b800dc38c9980eedde1d */ + min = lum - sat * (1.0 - (lum > 50 ? (((lum << 2) / 100.0) - 1.0): - (2 * (lum / 100.0) - 1.0))) / 2.0; + + /* sixel hue color ring is roteted -120 degree from nowdays general one. */ + hue = (hue + 240) % 360; + + /* https://wikimedia.org/api/rest_v1/media/math/render/svg/937e8abdab308a22ff99de24d645ec9e70f1e384 */ + switch (hue / 60) { + case 0: /* 0 <= hue < 60 */ + r = max; + g = (min + (max - min) * (hue / 60.0)); + b = min; + break; + case 1: /* 60 <= hue < 120 */ + r = min + (max - min) * ((120 - hue) / 60.0); + g = max; + b = min; + break; + case 2: /* 120 <= hue < 180 */ + r = min; + g = max; + b = (min + (max - min) * ((hue - 120) / 60.0)); + break; + case 3: /* 180 <= hue < 240 */ + r = min; + g = (min + (max - min) * ((240 - hue) / 60.0)); + b = max; + break; + case 4: /* 240 <= hue < 300 */ + r = (min + (max - min) * ((hue - 240) / 60.0)); + g = min; + b = max; + break; + case 5: /* 300 <= hue < 360 */ + r = max; + g = min; + b = (min + (max - min) * ((360 - hue) / 60.0)); + break; + default: + break; + } + + return SIXEL_XRGB(r, g, b); +} + +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_XRGB(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_XRGB(i * 11, i * 11, i * 11); + } + + for (; n < DECSIXEL_PALETTE_MAX; n++) { + image->palette[n] = SIXEL_XRGB(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, + colour fgcolor, colour 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); +} --- /dev/null +++ src/sixel.h @@ -0,0 +1,61 @@ +#ifndef SIXEL_H +#define SIXEL_H + +#include "config.h" + +#define DECSIXEL_PARAMS_MAX 16 +#define DECSIXEL_PALETTE_MAX 65535 +#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 colour; + +typedef struct sixel_image_buffer { + sixel_color_no_t *data; + int width; + int height; + colour palette[DECSIXEL_PALETTE_MAX]; + sixel_color_no_t ncolors; + int palette_modified; + int use_private_register; +} sixel_image_t; + +typedef enum parse_state { + PS_GROUND = 0, + 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, colour fgcolor, colour 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