sixel.c
1// sixel.c (part of mintty)
2// originally written by kmiya@cluti (https://github.com/saitoha/sixel/blob/master/fromsixel.c)
3// Licensed under the terms of the GNU General Public License v3 or later.
4
5#include <stdlib.h>
6#include <string.h> /* memcpy */
7
8#include "st.h"
9#include "win.h"
10#include "sixel.h"
11#include "sixel_hls.h"
12
13#define SIXEL_RGB(r, g, b) ((255 << 24) + ((r) << 16) + ((g) << 8) + (b))
14#define SIXEL_PALVAL(n,a,m) (((n) * (a) + ((m) / 2)) / (m))
15#define SIXEL_XRGB(r,g,b) SIXEL_RGB(SIXEL_PALVAL(r, 255, 100), SIXEL_PALVAL(g, 255, 100), SIXEL_PALVAL(b, 255, 100))
16
17static sixel_color_t const sixel_default_color_table[] = {
18 SIXEL_XRGB( 0, 0, 0), /* 0 Black */
19 SIXEL_XRGB(20, 20, 80), /* 1 Blue */
20 SIXEL_XRGB(80, 13, 13), /* 2 Red */
21 SIXEL_XRGB(20, 80, 20), /* 3 Green */
22 SIXEL_XRGB(80, 20, 80), /* 4 Magenta */
23 SIXEL_XRGB(20, 80, 80), /* 5 Cyan */
24 SIXEL_XRGB(80, 80, 20), /* 6 Yellow */
25 SIXEL_XRGB(53, 53, 53), /* 7 Gray 50% */
26 SIXEL_XRGB(26, 26, 26), /* 8 Gray 25% */
27 SIXEL_XRGB(33, 33, 60), /* 9 Blue* */
28 SIXEL_XRGB(60, 26, 26), /* 10 Red* */
29 SIXEL_XRGB(33, 60, 33), /* 11 Green* */
30 SIXEL_XRGB(60, 33, 60), /* 12 Magenta* */
31 SIXEL_XRGB(33, 60, 60), /* 13 Cyan* */
32 SIXEL_XRGB(60, 60, 33), /* 14 Yellow* */
33 SIXEL_XRGB(80, 80, 80), /* 15 Gray 75% */
34};
35
36void
37scroll_images(int n) {
38 ImageList *im, *next;
39 #if SCROLLBACK_PATCH || REFLOW_PATCH
40 int top = tisaltscr() ? 0 : term.scr - HISTSIZE;
41 #else
42 int top = 0;
43 #endif // SCROLLBACK_PATCH
44
45 for (im = term.images; im; im = next) {
46 next = im->next;
47 im->y += n;
48
49 /* check if the current sixel has exceeded the maximum
50 * draw distance, and should therefore be deleted */
51 if (im->y < top) {
52 //fprintf(stderr, "im@0x%08x exceeded maximum distance\n");
53 delete_image(im);
54 }
55 }
56}
57
58void
59delete_image(ImageList *im)
60{
61 if (im->prev)
62 im->prev->next = im->next;
63 else
64 term.images = im->next;
65 if (im->next)
66 im->next->prev = im->prev;
67 if (im->pixmap)
68 XFreePixmap(xw.dpy, (Drawable)im->pixmap);
69 if (im->clipmask)
70 XFreePixmap(xw.dpy, (Drawable)im->clipmask);
71 free(im->pixels);
72 free(im);
73}
74
75static int
76set_default_color(sixel_image_t *image)
77{
78 int i;
79 int n;
80 int r;
81 int g;
82 int b;
83
84 /* palette initialization */
85 for (n = 1; n < 17; n++) {
86 image->palette[n] = sixel_default_color_table[n - 1];
87 }
88
89 /* colors 17-232 are a 6x6x6 color cube */
90 for (r = 0; r < 6; r++) {
91 for (g = 0; g < 6; g++) {
92 for (b = 0; b < 6; b++) {
93 image->palette[n++] = SIXEL_RGB(r * 51, g * 51, b * 51);
94 }
95 }
96 }
97
98 /* colors 233-256 are a grayscale ramp, intentionally leaving out */
99 for (i = 0; i < 24; i++) {
100 image->palette[n++] = SIXEL_RGB(i * 11, i * 11, i * 11);
101 }
102
103 for (; n < DECSIXEL_PALETTE_MAX; n++) {
104 image->palette[n] = SIXEL_RGB(255, 255, 255);
105 }
106
107 return (0);
108}
109
110static int
111sixel_image_init(
112 sixel_image_t *image,
113 int width,
114 int height,
115 int fgcolor,
116 int bgcolor,
117 int use_private_register)
118{
119 int status = (-1);
120 size_t size;
121
122 size = (size_t)(width * height) * sizeof(sixel_color_no_t);
123 image->width = width;
124 image->height = height;
125 image->data = (sixel_color_no_t *)malloc(size);
126 image->ncolors = 2;
127 image->use_private_register = use_private_register;
128
129 if (image->data == NULL) {
130 status = (-1);
131 goto end;
132 }
133 memset(image->data, 0, size);
134
135 image->palette[0] = bgcolor;
136
137 if (image->use_private_register)
138 image->palette[1] = fgcolor;
139
140 image->palette_modified = 0;
141
142 status = (0);
143
144end:
145 return status;
146}
147
148
149static int
150image_buffer_resize(
151 sixel_image_t *image,
152 int width,
153 int height)
154{
155 int status = (-1);
156 size_t size;
157 sixel_color_no_t *alt_buffer;
158 int n;
159 int min_height;
160
161 size = (size_t)(width * height) * sizeof(sixel_color_no_t);
162 alt_buffer = (sixel_color_no_t *)malloc(size);
163 if (alt_buffer == NULL) {
164 /* free source image */
165 free(image->data);
166 image->data = NULL;
167 status = (-1);
168 goto end;
169 }
170
171 min_height = height > image->height ? image->height: height;
172 if (width > image->width) { /* if width is extended */
173 for (n = 0; n < min_height; ++n) {
174 /* copy from source image */
175 memcpy(alt_buffer + width * n,
176 image->data + image->width * n,
177 (size_t)image->width * sizeof(sixel_color_no_t));
178 /* fill extended area with background color */
179 memset(alt_buffer + width * n + image->width,
180 0,
181 (size_t)(width - image->width) * sizeof(sixel_color_no_t));
182 }
183 } else {
184 for (n = 0; n < min_height; ++n) {
185 /* copy from source image */
186 memcpy(alt_buffer + width * n,
187 image->data + image->width * n,
188 (size_t)width * sizeof(sixel_color_no_t));
189 }
190 }
191
192 if (height > image->height) { /* if height is extended */
193 /* fill extended area with background color */
194 memset(alt_buffer + width * image->height,
195 0,
196 (size_t)(width * (height - image->height)) * sizeof(sixel_color_no_t));
197 }
198
199 /* free source image */
200 free(image->data);
201
202 image->data = alt_buffer;
203 image->width = width;
204 image->height = height;
205
206 status = (0);
207
208end:
209 return status;
210}
211
212static void
213sixel_image_deinit(sixel_image_t *image)
214{
215 if (image->data)
216 free(image->data);
217 image->data = NULL;
218}
219
220int
221sixel_parser_init(sixel_state_t *st,
222 int transparent,
223 sixel_color_t fgcolor, sixel_color_t bgcolor,
224 unsigned char use_private_register,
225 int cell_width, int cell_height)
226{
227 int status = (-1);
228
229 st->state = PS_DECSIXEL;
230 st->pos_x = 0;
231 st->pos_y = 0;
232 st->max_x = 0;
233 st->max_y = 0;
234 st->attributed_pan = 2;
235 st->attributed_pad = 1;
236 st->attributed_ph = 0;
237 st->attributed_pv = 0;
238 st->transparent = transparent;
239 st->repeat_count = 1;
240 st->color_index = 16;
241 st->grid_width = cell_width;
242 st->grid_height = cell_height;
243 st->nparams = 0;
244 st->param = 0;
245
246 /* buffer initialization */
247 status = sixel_image_init(&st->image, 1, 1, fgcolor, transparent ? 0 : bgcolor, use_private_register);
248
249 return status;
250}
251
252int
253sixel_parser_set_default_color(sixel_state_t *st)
254{
255 return set_default_color(&st->image);
256}
257
258int
259sixel_parser_finalize(sixel_state_t *st, ImageList **newimages, int cx, int cy, int cw, int ch)
260{
261 sixel_image_t *image = &st->image;
262 int x, y;
263 sixel_color_no_t *src;
264 sixel_color_t *dst;
265 int color;
266 int w, h;
267 int i, j, cols, numimages;
268 ImageList *im, *next, *tail;
269
270 if (!image->data)
271 return -1;
272
273 if (++st->max_x < st->attributed_ph)
274 st->max_x = st->attributed_ph;
275
276 if (++st->max_y < st->attributed_pv)
277 st->max_y = st->attributed_pv;
278
279 if (image->use_private_register && image->ncolors > 2 && !image->palette_modified) {
280 if (set_default_color(image) < 0)
281 return -1;
282 }
283
284 w = MIN(st->max_x, image->width);
285 h = MIN(st->max_y, image->height);
286
287 if ((numimages = (h + ch-1) / ch) <= 0)
288 return -1;
289
290 cols = (w + cw-1) / cw;
291
292 *newimages = NULL, tail = NULL;
293 for (y = 0, i = 0; i < numimages; i++) {
294 if ((im = malloc(sizeof(ImageList)))) {
295 if (!tail) {
296 *newimages = tail = im;
297 im->prev = im->next = NULL;
298 } else {
299 tail->next = im;
300 im->prev = tail;
301 im->next = NULL;
302 tail = im;
303 }
304 im->x = cx;
305 im->y = cy + i;
306 im->cols = cols;
307 im->width = w;
308 im->height = MIN(h - ch * i, ch);
309 im->pixels = malloc(im->width * im->height * 4);
310 im->pixmap = NULL;
311 im->clipmask = NULL;
312 im->cw = cw;
313 im->ch = ch;
314 im->transparent = st->transparent;
315 }
316 if (!im || !im->pixels) {
317 for (im = *newimages; im; im = next) {
318 next = im->next;
319 if (im->pixels)
320 free(im->pixels);
321 free(im);
322 }
323 *newimages = NULL;
324 return -1;
325 }
326 dst = (sixel_color_t *)im->pixels;
327 for (j = 0; j < im->height && y < h; j++, y++) {
328 src = st->image.data + image->width * y;
329 for (x = 0; x < w; x++)
330 *dst++ = st->image.palette[*src++];
331 }
332 }
333
334 return numimages;
335}
336
337/* convert sixel data into indexed pixel bytes and palette data */
338int
339sixel_parser_parse(sixel_state_t *st, const unsigned char *p, size_t len)
340{
341 int n = 0;
342 int i;
343 int x;
344 int y;
345 int bits;
346 int sx;
347 int sy;
348 int c;
349 int pos;
350 int width;
351 const unsigned char *p0 = p, *p2 = p + len;
352 sixel_image_t *image = &st->image;
353 sixel_color_no_t *data, color_index;
354
355 if (!image->data)
356 st->state = PS_ERROR;
357
358 while (p < p2) {
359 switch (st->state) {
360 case PS_ESC:
361 goto end;
362
363 case PS_DECSIXEL:
364 switch (*p) {
365 case '\x1b':
366 st->state = PS_ESC;
367 break;
368 case '"':
369 st->param = 0;
370 st->nparams = 0;
371 st->state = PS_DECGRA;
372 p++;
373 break;
374 case '!':
375 st->param = 0;
376 st->nparams = 0;
377 st->state = PS_DECGRI;
378 p++;
379 break;
380 case '#':
381 st->param = 0;
382 st->nparams = 0;
383 st->state = PS_DECGCI;
384 p++;
385 break;
386 case '$':
387 /* DECGCR Graphics Carriage Return */
388 st->pos_x = 0;
389 p++;
390 break;
391 case '-':
392 /* DECGNL Graphics Next Line */
393 st->pos_x = 0;
394 if (st->pos_y < DECSIXEL_HEIGHT_MAX - 5 - 6)
395 st->pos_y += 6;
396 else
397 st->pos_y = DECSIXEL_HEIGHT_MAX + 1;
398 p++;
399 break;
400 default:
401 if (*p >= '?' && *p <= '~') { /* sixel characters */
402 if ((image->width < (st->pos_x + st->repeat_count) || image->height < (st->pos_y + 6))
403 && image->width < DECSIXEL_WIDTH_MAX && image->height < DECSIXEL_HEIGHT_MAX) {
404 sx = image->width * 2;
405 sy = image->height * 2;
406 while (sx < (st->pos_x + st->repeat_count) || sy < (st->pos_y + 6)) {
407 sx *= 2;
408 sy *= 2;
409 }
410
411 sx = MIN(sx, DECSIXEL_WIDTH_MAX);
412 sy = MIN(sy, DECSIXEL_HEIGHT_MAX);
413
414 if (image_buffer_resize(image, sx, sy) < 0) {
415 perror("sixel_parser_parse() failed");
416 st->state = PS_ERROR;
417 p++;
418 break;
419 }
420 }
421
422 if (st->color_index > image->ncolors)
423 image->ncolors = st->color_index;
424
425 if (st->pos_x + st->repeat_count > image->width)
426 st->repeat_count = image->width - st->pos_x;
427
428 if (st->repeat_count > 0 && st->pos_y + 5 < image->height) {
429 bits = *p - '?';
430 if (bits != 0) {
431 data = image->data + image->width * st->pos_y + st->pos_x;
432 width = image->width;
433 color_index = st->color_index;
434 if (st->repeat_count <= 1) {
435 if (bits & 0x01)
436 *data = color_index, n = 0;
437 data += width;
438 if (bits & 0x02)
439 *data = color_index, n = 1;
440 data += width;
441 if (bits & 0x04)
442 *data = color_index, n = 2;
443 data += width;
444 if (bits & 0x08)
445 *data = color_index, n = 3;
446 data += width;
447 if (bits & 0x10)
448 *data = color_index, n = 4;
449 if (bits & 0x20)
450 data[width] = color_index, n = 5;
451 if (st->max_x < st->pos_x)
452 st->max_x = st->pos_x;
453 } else {
454 /* st->repeat_count > 1 */
455 for (i = 0; bits; bits >>= 1, i++, data += width) {
456 if (bits & 1) {
457 data[0] = color_index;
458 data[1] = color_index;
459 for (x = 2; x < st->repeat_count; x++)
460 data[x] = color_index;
461 n = i;
462 }
463 }
464 if (st->max_x < (st->pos_x + st->repeat_count - 1))
465 st->max_x = st->pos_x + st->repeat_count - 1;
466 }
467 if (st->max_y < (st->pos_y + n))
468 st->max_y = st->pos_y + n;
469 }
470 }
471 if (st->repeat_count > 0)
472 st->pos_x += st->repeat_count;
473 st->repeat_count = 1;
474 }
475 p++;
476 break;
477 }
478 break;
479
480 case PS_DECGRA:
481 /* DECGRA Set Raster Attributes " Pan; Pad; Ph; Pv */
482 switch (*p) {
483 case '\x1b':
484 st->state = PS_ESC;
485 break;
486 case '0':
487 case '1':
488 case '2':
489 case '3':
490 case '4':
491 case '5':
492 case '6':
493 case '7':
494 case '8':
495 case '9':
496 st->param = st->param * 10 + *p - '0';
497 st->param = MIN(st->param, DECSIXEL_PARAMVALUE_MAX);
498 p++;
499 break;
500 case ';':
501 if (st->nparams < DECSIXEL_PARAMS_MAX)
502 st->params[st->nparams++] = st->param;
503 st->param = 0;
504 p++;
505 break;
506 default:
507 if (st->nparams < DECSIXEL_PARAMS_MAX)
508 st->params[st->nparams++] = st->param;
509 if (st->nparams > 0)
510 st->attributed_pad = st->params[0];
511 if (st->nparams > 1)
512 st->attributed_pan = st->params[1];
513 if (st->nparams > 2 && st->params[2] > 0)
514 st->attributed_ph = st->params[2];
515 if (st->nparams > 3 && st->params[3] > 0)
516 st->attributed_pv = st->params[3];
517
518 if (st->attributed_pan <= 0)
519 st->attributed_pan = 1;
520 if (st->attributed_pad <= 0)
521 st->attributed_pad = 1;
522
523 if (image->width < st->attributed_ph ||
524 image->height < st->attributed_pv) {
525 sx = MAX(image->width, st->attributed_ph);
526 sy = MAX(image->height, st->attributed_pv);
527
528 /* the height of the image buffer must be divisible by 6
529 * to avoid unnecessary resizing of the image buffer when
530 * parsing the last sixel line */
531 sy = (sy + 5) / 6 * 6;
532
533 sx = MIN(sx, DECSIXEL_WIDTH_MAX);
534 sy = MIN(sy, DECSIXEL_HEIGHT_MAX);
535
536 if (image_buffer_resize(image, sx, sy) < 0) {
537 perror("sixel_parser_parse() failed");
538 st->state = PS_ERROR;
539 break;
540 }
541 }
542 st->state = PS_DECSIXEL;
543 st->param = 0;
544 st->nparams = 0;
545 }
546 break;
547
548 case PS_DECGRI:
549 /* DECGRI Graphics Repeat Introducer ! Pn Ch */
550 switch (*p) {
551 case '\x1b':
552 st->state = PS_ESC;
553 break;
554 case '0':
555 case '1':
556 case '2':
557 case '3':
558 case '4':
559 case '5':
560 case '6':
561 case '7':
562 case '8':
563 case '9':
564 st->param = st->param * 10 + *p - '0';
565 st->param = MIN(st->param, DECSIXEL_PARAMVALUE_MAX);
566 p++;
567 break;
568 default:
569 st->repeat_count = MAX(st->param, 1);
570 st->state = PS_DECSIXEL;
571 st->param = 0;
572 st->nparams = 0;
573 break;
574 }
575 break;
576
577 case PS_DECGCI:
578 /* DECGCI Graphics Color Introducer # Pc; Pu; Px; Py; Pz */
579 switch (*p) {
580 case '\x1b':
581 st->state = PS_ESC;
582 break;
583 case '0':
584 case '1':
585 case '2':
586 case '3':
587 case '4':
588 case '5':
589 case '6':
590 case '7':
591 case '8':
592 case '9':
593 st->param = st->param * 10 + *p - '0';
594 st->param = MIN(st->param, DECSIXEL_PARAMVALUE_MAX);
595 p++;
596 break;
597 case ';':
598 if (st->nparams < DECSIXEL_PARAMS_MAX)
599 st->params[st->nparams++] = st->param;
600 st->param = 0;
601 p++;
602 break;
603 default:
604 st->state = PS_DECSIXEL;
605 if (st->nparams < DECSIXEL_PARAMS_MAX)
606 st->params[st->nparams++] = st->param;
607 st->param = 0;
608
609 if (st->nparams > 0) {
610 st->color_index = 1 + st->params[0]; /* offset 1(background color) added */
611 if (st->color_index < 0)
612 st->color_index = 0;
613 else if (st->color_index >= DECSIXEL_PALETTE_MAX)
614 st->color_index = DECSIXEL_PALETTE_MAX - 1;
615 }
616
617 if (st->nparams > 4) {
618 st->image.palette_modified = 1;
619 if (st->params[1] == 1) {
620 /* HLS */
621 st->params[2] = MIN(st->params[2], 360);
622 st->params[3] = MIN(st->params[3], 100);
623 st->params[4] = MIN(st->params[4], 100);
624 image->palette[st->color_index]
625 = hls_to_rgb(st->params[2], st->params[3], st->params[4]);
626 } else if (st->params[1] == 2) {
627 /* RGB */
628 st->params[2] = MIN(st->params[2], 100);
629 st->params[3] = MIN(st->params[3], 100);
630 st->params[4] = MIN(st->params[4], 100);
631 image->palette[st->color_index]
632 = SIXEL_XRGB(st->params[2], st->params[3], st->params[4]);
633 }
634 }
635 break;
636 }
637 break;
638
639 case PS_ERROR:
640 if (*p == '\x1b') {
641 st->state = PS_ESC;
642 goto end;
643 }
644 p++;
645 break;
646 default:
647 break;
648 }
649 }
650
651end:
652 return p - p0;
653}
654
655void
656sixel_parser_deinit(sixel_state_t *st)
657{
658 if (st)
659 sixel_image_deinit(&st->image);
660}
661
662Pixmap
663sixel_create_clipmask(char *pixels, int width, int height)
664{
665 char c, *clipdata, *dst;
666 int b, i, n, y, w;
667 int msb = (XBitmapBitOrder(xw.dpy) == MSBFirst);
668 sixel_color_t *src = (sixel_color_t *)pixels;
669 Pixmap clipmask;
670
671 clipdata = dst = malloc((width+7)/8 * height);
672 if (!clipdata)
673 return (Pixmap)None;
674
675 for (y = 0; y < height; y++) {
676 for (w = width; w > 0; w -= n) {
677 n = MIN(w, 8);
678 if (msb) {
679 for (b = 0x80, c = 0, i = 0; i < n; i++, b >>= 1)
680 c |= (*src++) ? b : 0;
681 } else {
682 for (b = 0x01, c = 0, i = 0; i < n; i++, b <<= 1)
683 c |= (*src++) ? b : 0;
684 }
685 *dst++ = c;
686 }
687 }
688
689 clipmask = XCreateBitmapFromData(xw.dpy, xw.win, clipdata, width, height);
690 free(clipdata);
691 return clipmask;
692}