Portable tmux needs to check ENABLE_SIXEL.
[tmux.git] / image-sixel.c
blob8fa02a82c54de116e45d2c5140a5968200647144
1 /* $OpenBSD$ */
3 /*
4 * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 #include <sys/types.h>
21 #include <stdlib.h>
22 #include <string.h>
24 #include "tmux.h"
26 #define SIXEL_WIDTH_LIMIT 10000
27 #define SIXEL_HEIGHT_LIMIT 10000
29 struct sixel_line {
30 u_int x;
31 uint16_t *data;
34 struct sixel_image {
35 u_int x;
36 u_int y;
37 u_int xpixel;
38 u_int ypixel;
40 u_int set_ra;
41 u_int ra_x;
42 u_int ra_y;
44 u_int *colours;
45 u_int ncolours;
46 u_int p2;
48 u_int dx;
49 u_int dy;
50 u_int dc;
52 struct sixel_line *lines;
55 static int
56 sixel_parse_expand_lines(struct sixel_image *si, u_int y)
58 if (y <= si->y)
59 return (0);
60 if (y > SIXEL_HEIGHT_LIMIT)
61 return (1);
62 si->lines = xrecallocarray(si->lines, si->y, y, sizeof *si->lines);
63 si->y = y;
64 return (0);
67 static int
68 sixel_parse_expand_line(struct sixel_image *si, struct sixel_line *sl, u_int x)
70 if (x <= sl->x)
71 return (0);
72 if (x > SIXEL_WIDTH_LIMIT)
73 return (1);
74 if (x > si->x)
75 si->x = x;
76 sl->data = xrecallocarray(sl->data, sl->x, si->x, sizeof *sl->data);
77 sl->x = si->x;
78 return (0);
81 static u_int
82 sixel_get_pixel(struct sixel_image *si, u_int x, u_int y)
84 struct sixel_line *sl;
86 if (y >= si->y)
87 return (0);
88 sl = &si->lines[y];
89 if (x >= sl->x)
90 return (0);
91 return (sl->data[x]);
94 static int
95 sixel_set_pixel(struct sixel_image *si, u_int x, u_int y, u_int c)
97 struct sixel_line *sl;
99 if (sixel_parse_expand_lines(si, y + 1) != 0)
100 return (1);
101 sl = &si->lines[y];
102 if (sixel_parse_expand_line(si, sl, x + 1) != 0)
103 return (1);
104 sl->data[x] = c;
105 return (0);
108 static int
109 sixel_parse_write(struct sixel_image *si, u_int ch)
111 struct sixel_line *sl;
112 u_int i;
114 if (sixel_parse_expand_lines(si, si->dy + 6) != 0)
115 return (1);
116 sl = &si->lines[si->dy];
118 for (i = 0; i < 6; i++) {
119 if (sixel_parse_expand_line(si, sl, si->dx + 1) != 0)
120 return (1);
121 if (ch & (1 << i))
122 sl->data[si->dx] = si->dc;
123 sl++;
125 return (0);
128 static const char *
129 sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end)
131 const char *last;
132 char *endptr;
133 u_int x, y;
135 last = cp;
136 while (last != end) {
137 if (*last != ';' && (*last < '0' || *last > '9'))
138 break;
139 last++;
141 strtoul(cp, &endptr, 10);
142 if (endptr == last || *endptr != ';')
143 return (last);
144 strtoul(endptr + 1, &endptr, 10);
145 if (endptr == last)
146 return (last);
147 if (*endptr != ';') {
148 log_debug("%s: missing ;", __func__);
149 return (NULL);
152 x = strtoul(endptr + 1, &endptr, 10);
153 if (endptr == last || *endptr != ';') {
154 log_debug("%s: missing ;", __func__);
155 return (NULL);
157 if (x > SIXEL_WIDTH_LIMIT) {
158 log_debug("%s: image is too wide", __func__);
159 return (NULL);
161 y = strtoul(endptr + 1, &endptr, 10);
162 if (endptr != last) {
163 log_debug("%s: extra ;", __func__);
164 return (NULL);
166 if (y > SIXEL_HEIGHT_LIMIT) {
167 log_debug("%s: image is too tall", __func__);
168 return (NULL);
171 si->x = x;
172 sixel_parse_expand_lines(si, y);
174 si->set_ra = 1;
175 si->ra_x = x;
176 si->ra_y = y;
178 return (last);
181 static const char *
182 sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end)
184 const char *last;
185 char *endptr;
186 u_int c, type, r, g, b;
188 last = cp;
189 while (last != end) {
190 if (*last != ';' && (*last < '0' || *last > '9'))
191 break;
192 last++;
195 c = strtoul(cp, &endptr, 10);
196 if (c > SIXEL_COLOUR_REGISTERS) {
197 log_debug("%s: too many colours", __func__);
198 return (NULL);
200 si->dc = c + 1;
201 if (endptr == last || *endptr != ';')
202 return (last);
204 type = strtoul(endptr + 1, &endptr, 10);
205 if (endptr == last || *endptr != ';') {
206 log_debug("%s: missing ;", __func__);
207 return (NULL);
209 r = strtoul(endptr + 1, &endptr, 10);
210 if (endptr == last || *endptr != ';') {
211 log_debug("%s: missing ;", __func__);
212 return (NULL);
214 g = strtoul(endptr + 1, &endptr, 10);
215 if (endptr == last || *endptr != ';') {
216 log_debug("%s: missing ;", __func__);
217 return (NULL);
219 b = strtoul(endptr + 1, &endptr, 10);
220 if (endptr != last) {
221 log_debug("%s: missing ;", __func__);
222 return (NULL);
225 if (type != 1 && type != 2) {
226 log_debug("%s: invalid type %d", __func__, type);
227 return (NULL);
229 if (c + 1 > si->ncolours) {
230 si->colours = xrecallocarray(si->colours, si->ncolours, c + 1,
231 sizeof *si->colours);
232 si->ncolours = c + 1;
234 si->colours[c] = (type << 24) | (r << 16) | (g << 8) | b;
235 return (last);
238 static const char *
239 sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end)
241 const char *last;
242 char tmp[32], ch;
243 u_int n = 0, i;
244 const char *errstr = NULL;
246 last = cp;
247 while (last != end) {
248 if (*last < '0' || *last > '9')
249 break;
250 tmp[n++] = *last++;
251 if (n == (sizeof tmp) - 1) {
252 log_debug("%s: repeat not terminated", __func__);
253 return (NULL);
256 if (n == 0 || last == end) {
257 log_debug("%s: repeat not terminated", __func__);
258 return (NULL);
260 tmp[n] = '\0';
262 n = strtonum(tmp, 1, SIXEL_WIDTH_LIMIT, &errstr);
263 if (n == 0 || errstr != NULL) {
264 log_debug("%s: repeat too wide", __func__);
265 return (NULL);
268 ch = (*last++) - 0x3f;
269 for (i = 0; i < n; i++) {
270 if (sixel_parse_write(si, ch) != 0) {
271 log_debug("%s: width limit reached", __func__);
272 return (NULL);
274 si->dx++;
276 return (last);
279 struct sixel_image *
280 sixel_parse(const char *buf, size_t len, u_int p2, u_int xpixel, u_int ypixel)
282 struct sixel_image *si;
283 const char *cp = buf, *end = buf + len;
284 char ch;
286 if (len == 0 || len == 1 || *cp++ != 'q') {
287 log_debug("%s: empty image", __func__);
288 return (NULL);
291 si = xcalloc (1, sizeof *si);
292 si->xpixel = xpixel;
293 si->ypixel = ypixel;
294 si->p2 = p2;
296 while (cp != end) {
297 ch = *cp++;
298 switch (ch) {
299 case '"':
300 cp = sixel_parse_attributes(si, cp, end);
301 if (cp == NULL)
302 goto bad;
303 break;
304 case '#':
305 cp = sixel_parse_colour(si, cp, end);
306 if (cp == NULL)
307 goto bad;
308 break;
309 case '!':
310 cp = sixel_parse_repeat(si, cp, end);
311 if (cp == NULL)
312 goto bad;
313 break;
314 case '-':
315 si->dx = 0;
316 si->dy += 6;
317 break;
318 case '$':
319 si->dx = 0;
320 break;
321 default:
322 if (ch < 0x20)
323 break;
324 if (ch < 0x3f || ch > 0x7e)
325 goto bad;
326 if (sixel_parse_write(si, ch - 0x3f) != 0) {
327 log_debug("%s: width limit reached", __func__);
328 goto bad;
330 si->dx++;
331 break;
335 if (si->x == 0 || si->y == 0)
336 goto bad;
337 return (si);
339 bad:
340 free(si);
341 return (NULL);
344 void
345 sixel_free(struct sixel_image *si)
347 u_int y;
349 for (y = 0; y < si->y; y++)
350 free(si->lines[y].data);
351 free(si->lines);
353 free(si->colours);
354 free(si);
357 void
358 sixel_log(struct sixel_image *si)
360 struct sixel_line *sl;
361 char s[SIXEL_WIDTH_LIMIT + 1];
362 u_int i, x, y, cx, cy;
364 sixel_size_in_cells(si, &cx, &cy);
365 log_debug("%s: image %ux%u (%ux%u)", __func__, si->x, si->y, cx, cy);
366 for (i = 0; i < si->ncolours; i++)
367 log_debug("%s: colour %u is %07x", __func__, i, si->colours[i]);
368 for (y = 0; y < si->y; y++) {
369 sl = &si->lines[y];
370 for (x = 0; x < si->x; x++) {
371 if (x >= sl->x)
372 s[x] = '_';
373 else if (sl->data[x] != 0)
374 s[x] = '0' + (sl->data[x] - 1) % 10;
375 else
376 s[x] = '.';
378 s[x] = '\0';
379 log_debug("%s: %4u: %s", __func__, y, s);
383 void
384 sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y)
386 if ((si->x % si->xpixel) == 0)
387 *x = (si->x / si->xpixel);
388 else
389 *x = 1 + (si->x / si->xpixel);
390 if ((si->y % si->ypixel) == 0)
391 *y = (si->y / si->ypixel);
392 else
393 *y = 1 + (si->y / si->ypixel);
396 struct sixel_image *
397 sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox,
398 u_int oy, u_int sx, u_int sy, int colours)
400 struct sixel_image *new;
401 u_int cx, cy, pox, poy, psx, psy, tsx, tsy, px, py;
402 u_int x, y, i;
405 * We want to get the section of the image at ox,oy in image cells and
406 * map it onto the same size in terminal cells, remembering that we
407 * can only draw vertical sections of six pixels.
410 sixel_size_in_cells(si, &cx, &cy);
411 if (ox >= cx)
412 return (NULL);
413 if (oy >= cy)
414 return (NULL);
415 if (ox + sx >= cx)
416 sx = cx - ox;
417 if (oy + sy >= cy)
418 sy = cy - oy;
420 if (xpixel == 0)
421 xpixel = si->xpixel;
422 if (ypixel == 0)
423 ypixel = si->ypixel;
425 pox = ox * si->xpixel;
426 poy = oy * si->ypixel;
427 psx = sx * si->xpixel;
428 psy = sy * si->ypixel;
430 tsx = sx * xpixel;
431 tsy = ((sy * ypixel) / 6) * 6;
433 new = xcalloc (1, sizeof *si);
434 new->xpixel = xpixel;
435 new->ypixel = ypixel;
436 new->p2 = si->p2;
438 new->set_ra = si->set_ra;
439 /* clamp to slice end */
440 new->ra_x = si->ra_x < psx ? si->ra_x : psx;
441 new->ra_y = si->ra_y < psy ? si->ra_y : psy;
442 /* subtract slice origin */
443 new->ra_x = new->ra_x > pox ? new->ra_x - pox : 0;
444 new->ra_y = new->ra_y > poy ? new->ra_y - poy : 0;
445 /* resize */
446 new->ra_x = new->ra_x * xpixel / si->xpixel;
447 new->ra_y = new->ra_y * ypixel / si->ypixel;
449 for (y = 0; y < tsy; y++) {
450 py = poy + ((double)y * psy / tsy);
451 for (x = 0; x < tsx; x++) {
452 px = pox + ((double)x * psx / tsx);
453 sixel_set_pixel(new, x, y, sixel_get_pixel(si, px, py));
457 if (colours) {
458 new->colours = xmalloc(si->ncolours * sizeof *new->colours);
459 for (i = 0; i < si->ncolours; i++)
460 new->colours[i] = si->colours[i];
461 new->ncolours = si->ncolours;
463 return (new);
466 static void
467 sixel_print_add(char **buf, size_t *len, size_t *used, const char *s,
468 size_t slen)
470 if (*used + slen >= *len + 1) {
471 (*len) *= 2;
472 *buf = xrealloc(*buf, *len);
474 memcpy(*buf + *used, s, slen);
475 (*used) += slen;
478 static void
479 sixel_print_repeat(char **buf, size_t *len, size_t *used, u_int count, char ch)
481 char tmp[16];
482 size_t tmplen;
484 if (count == 1)
485 sixel_print_add(buf, len, used, &ch, 1);
486 else if (count == 2) {
487 sixel_print_add(buf, len, used, &ch, 1);
488 sixel_print_add(buf, len, used, &ch, 1);
489 } else if (count == 3) {
490 sixel_print_add(buf, len, used, &ch, 1);
491 sixel_print_add(buf, len, used, &ch, 1);
492 sixel_print_add(buf, len, used, &ch, 1);
493 } else if (count != 0) {
494 tmplen = xsnprintf(tmp, sizeof tmp, "!%u%c", count, ch);
495 sixel_print_add(buf, len, used, tmp, tmplen);
499 char *
500 sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size)
502 char *buf, tmp[64], *contains, data, last = 0;
503 size_t len, used = 0, tmplen;
504 u_int *colours, ncolours, i, c, x, y, count;
505 struct sixel_line *sl;
507 if (map != NULL) {
508 colours = map->colours;
509 ncolours = map->ncolours;
510 } else {
511 colours = si->colours;
512 ncolours = si->ncolours;
515 if (ncolours == 0)
516 return (NULL);
517 contains = xcalloc(1, ncolours);
519 len = 8192;
520 buf = xmalloc(len);
522 tmplen = xsnprintf(tmp, sizeof tmp, "\033P0;%uq", si->p2);
523 sixel_print_add(&buf, &len, &used, tmp, tmplen);
525 if (si->set_ra) {
526 tmplen = xsnprintf(tmp, sizeof tmp, "\"1;1;%u;%u", si->ra_x,
527 si->ra_y);
528 sixel_print_add(&buf, &len, &used, tmp, tmplen);
531 for (i = 0; i < ncolours; i++) {
532 c = colours[i];
533 tmplen = xsnprintf(tmp, sizeof tmp, "#%u;%u;%u;%u;%u",
534 i, c >> 24, (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff);
535 sixel_print_add(&buf, &len, &used, tmp, tmplen);
538 for (y = 0; y < si->y; y += 6) {
539 memset(contains, 0, ncolours);
540 for (x = 0; x < si->x; x++) {
541 for (i = 0; i < 6; i++) {
542 if (y + i >= si->y)
543 break;
544 sl = &si->lines[y + i];
545 if (x < sl->x && sl->data[x] != 0)
546 contains[sl->data[x] - 1] = 1;
550 for (c = 0; c < ncolours; c++) {
551 if (!contains[c])
552 continue;
553 tmplen = xsnprintf(tmp, sizeof tmp, "#%u", c);
554 sixel_print_add(&buf, &len, &used, tmp, tmplen);
556 count = 0;
557 for (x = 0; x < si->x; x++) {
558 data = 0;
559 for (i = 0; i < 6; i++) {
560 if (y + i >= si->y)
561 break;
562 sl = &si->lines[y + i];
563 if (x < sl->x && sl->data[x] == c + 1)
564 data |= (1 << i);
566 data += 0x3f;
567 if (data != last) {
568 sixel_print_repeat(&buf, &len, &used,
569 count, last);
570 last = data;
571 count = 1;
572 } else
573 count++;
575 sixel_print_repeat(&buf, &len, &used, count, data);
576 sixel_print_add(&buf, &len, &used, "$", 1);
579 if (buf[used - 1] == '$')
580 used--;
581 sixel_print_add(&buf, &len, &used, "-", 1);
583 if (buf[used - 1] == '$' || buf[used - 1] == '-')
584 used--;
586 sixel_print_add(&buf, &len, &used, "\033\\", 2);
588 buf[used] = '\0';
589 if (size != NULL)
590 *size = used;
592 free(contains);
593 return (buf);
596 struct screen *
597 sixel_to_screen(struct sixel_image *si)
599 struct screen *s;
600 struct screen_write_ctx ctx;
601 struct grid_cell gc;
602 u_int x, y, sx, sy;
604 sixel_size_in_cells(si, &sx, &sy);
606 s = xmalloc(sizeof *s);
607 screen_init(s, sx, sy, 0);
609 memcpy(&gc, &grid_default_cell, sizeof gc);
610 gc.attr |= (GRID_ATTR_CHARSET|GRID_ATTR_DIM);
611 utf8_set(&gc.data, '~');
613 screen_write_start(&ctx, s);
614 if (sx == 1 || sy == 1) {
615 for (y = 0; y < sy; y++) {
616 for (x = 0; x < sx; x++)
617 grid_view_set_cell(s->grid, x, y, &gc);
619 } else {
620 screen_write_box(&ctx, sx, sy, BOX_LINES_DEFAULT, NULL, NULL);
621 for (y = 1; y < sy - 1; y++) {
622 for (x = 1; x < sx - 1; x++)
623 grid_view_set_cell(s->grid, x, y, &gc);
626 screen_write_stop(&ctx);
627 return (s);