Merge branch 'obsd-master'
[tmux.git] / image-sixel.c
bloba03c8619066de2f3db16f3ea669e930a1d62d4ee
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 struct sixel_chunk {
56 u_int next_x;
57 u_int next_y;
59 u_int count;
60 char pattern;
61 char next_pattern;
63 size_t len;
64 size_t used;
65 char *data;
68 static int
69 sixel_parse_expand_lines(struct sixel_image *si, u_int y)
71 if (y <= si->y)
72 return (0);
73 if (y > SIXEL_HEIGHT_LIMIT)
74 return (1);
75 si->lines = xrecallocarray(si->lines, si->y, y, sizeof *si->lines);
76 si->y = y;
77 return (0);
80 static int
81 sixel_parse_expand_line(struct sixel_image *si, struct sixel_line *sl, u_int x)
83 if (x <= sl->x)
84 return (0);
85 if (x > SIXEL_WIDTH_LIMIT)
86 return (1);
87 if (x > si->x)
88 si->x = x;
89 sl->data = xrecallocarray(sl->data, sl->x, si->x, sizeof *sl->data);
90 sl->x = si->x;
91 return (0);
94 static u_int
95 sixel_get_pixel(struct sixel_image *si, u_int x, u_int y)
97 struct sixel_line *sl;
99 if (y >= si->y)
100 return (0);
101 sl = &si->lines[y];
102 if (x >= sl->x)
103 return (0);
104 return (sl->data[x]);
107 static int
108 sixel_set_pixel(struct sixel_image *si, u_int x, u_int y, u_int c)
110 struct sixel_line *sl;
112 if (sixel_parse_expand_lines(si, y + 1) != 0)
113 return (1);
114 sl = &si->lines[y];
115 if (sixel_parse_expand_line(si, sl, x + 1) != 0)
116 return (1);
117 sl->data[x] = c;
118 return (0);
121 static int
122 sixel_parse_write(struct sixel_image *si, u_int ch)
124 struct sixel_line *sl;
125 u_int i;
127 if (sixel_parse_expand_lines(si, si->dy + 6) != 0)
128 return (1);
129 sl = &si->lines[si->dy];
131 for (i = 0; i < 6; i++) {
132 if (sixel_parse_expand_line(si, sl, si->dx + 1) != 0)
133 return (1);
134 if (ch & (1 << i))
135 sl->data[si->dx] = si->dc;
136 sl++;
138 return (0);
141 static const char *
142 sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end)
144 const char *last;
145 char *endptr;
146 u_int x, y;
148 last = cp;
149 while (last != end) {
150 if (*last != ';' && (*last < '0' || *last > '9'))
151 break;
152 last++;
154 strtoul(cp, &endptr, 10);
155 if (endptr == last || *endptr != ';')
156 return (last);
157 strtoul(endptr + 1, &endptr, 10);
158 if (endptr == last)
159 return (last);
160 if (*endptr != ';') {
161 log_debug("%s: missing ;", __func__);
162 return (NULL);
165 x = strtoul(endptr + 1, &endptr, 10);
166 if (endptr == last || *endptr != ';') {
167 log_debug("%s: missing ;", __func__);
168 return (NULL);
170 if (x > SIXEL_WIDTH_LIMIT) {
171 log_debug("%s: image is too wide", __func__);
172 return (NULL);
174 y = strtoul(endptr + 1, &endptr, 10);
175 if (endptr != last) {
176 log_debug("%s: extra ;", __func__);
177 return (NULL);
179 if (y > SIXEL_HEIGHT_LIMIT) {
180 log_debug("%s: image is too tall", __func__);
181 return (NULL);
184 si->x = x;
185 sixel_parse_expand_lines(si, y);
187 si->set_ra = 1;
188 si->ra_x = x;
189 si->ra_y = y;
191 return (last);
194 static const char *
195 sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end)
197 const char *last;
198 char *endptr;
199 u_int c, type, r, g, b;
201 last = cp;
202 while (last != end) {
203 if (*last != ';' && (*last < '0' || *last > '9'))
204 break;
205 last++;
208 c = strtoul(cp, &endptr, 10);
209 if (c > SIXEL_COLOUR_REGISTERS) {
210 log_debug("%s: too many colours", __func__);
211 return (NULL);
213 si->dc = c + 1;
214 if (endptr == last || *endptr != ';')
215 return (last);
217 type = strtoul(endptr + 1, &endptr, 10);
218 if (endptr == last || *endptr != ';') {
219 log_debug("%s: missing ;", __func__);
220 return (NULL);
222 r = strtoul(endptr + 1, &endptr, 10);
223 if (endptr == last || *endptr != ';') {
224 log_debug("%s: missing ;", __func__);
225 return (NULL);
227 g = strtoul(endptr + 1, &endptr, 10);
228 if (endptr == last || *endptr != ';') {
229 log_debug("%s: missing ;", __func__);
230 return (NULL);
232 b = strtoul(endptr + 1, &endptr, 10);
233 if (endptr != last) {
234 log_debug("%s: missing ;", __func__);
235 return (NULL);
238 if (type != 1 && type != 2) {
239 log_debug("%s: invalid type %d", __func__, type);
240 return (NULL);
242 if (c + 1 > si->ncolours) {
243 si->colours = xrecallocarray(si->colours, si->ncolours, c + 1,
244 sizeof *si->colours);
245 si->ncolours = c + 1;
247 si->colours[c] = (type << 24) | (r << 16) | (g << 8) | b;
248 return (last);
251 static const char *
252 sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end)
254 const char *last;
255 char tmp[32], ch;
256 u_int n = 0, i;
257 const char *errstr = NULL;
259 last = cp;
260 while (last != end) {
261 if (*last < '0' || *last > '9')
262 break;
263 tmp[n++] = *last++;
264 if (n == (sizeof tmp) - 1) {
265 log_debug("%s: repeat not terminated", __func__);
266 return (NULL);
269 if (n == 0 || last == end) {
270 log_debug("%s: repeat not terminated", __func__);
271 return (NULL);
273 tmp[n] = '\0';
275 n = strtonum(tmp, 1, SIXEL_WIDTH_LIMIT, &errstr);
276 if (n == 0 || errstr != NULL) {
277 log_debug("%s: repeat too wide", __func__);
278 return (NULL);
281 ch = (*last++) - 0x3f;
282 for (i = 0; i < n; i++) {
283 if (sixel_parse_write(si, ch) != 0) {
284 log_debug("%s: width limit reached", __func__);
285 return (NULL);
287 si->dx++;
289 return (last);
292 struct sixel_image *
293 sixel_parse(const char *buf, size_t len, u_int p2, u_int xpixel, u_int ypixel)
295 struct sixel_image *si;
296 const char *cp = buf, *end = buf + len;
297 char ch;
299 if (len == 0 || len == 1 || *cp++ != 'q') {
300 log_debug("%s: empty image", __func__);
301 return (NULL);
304 si = xcalloc (1, sizeof *si);
305 si->xpixel = xpixel;
306 si->ypixel = ypixel;
307 si->p2 = p2;
309 while (cp != end) {
310 ch = *cp++;
311 switch (ch) {
312 case '"':
313 cp = sixel_parse_attributes(si, cp, end);
314 if (cp == NULL)
315 goto bad;
316 break;
317 case '#':
318 cp = sixel_parse_colour(si, cp, end);
319 if (cp == NULL)
320 goto bad;
321 break;
322 case '!':
323 cp = sixel_parse_repeat(si, cp, end);
324 if (cp == NULL)
325 goto bad;
326 break;
327 case '-':
328 si->dx = 0;
329 si->dy += 6;
330 break;
331 case '$':
332 si->dx = 0;
333 break;
334 default:
335 if (ch < 0x20)
336 break;
337 if (ch < 0x3f || ch > 0x7e)
338 goto bad;
339 if (sixel_parse_write(si, ch - 0x3f) != 0) {
340 log_debug("%s: width limit reached", __func__);
341 goto bad;
343 si->dx++;
344 break;
348 if (si->x == 0 || si->y == 0)
349 goto bad;
350 return (si);
352 bad:
353 free(si);
354 return (NULL);
357 void
358 sixel_free(struct sixel_image *si)
360 u_int y;
362 for (y = 0; y < si->y; y++)
363 free(si->lines[y].data);
364 free(si->lines);
366 free(si->colours);
367 free(si);
370 void
371 sixel_log(struct sixel_image *si)
373 struct sixel_line *sl;
374 char s[SIXEL_WIDTH_LIMIT + 1];
375 u_int i, x, y, cx, cy;
377 sixel_size_in_cells(si, &cx, &cy);
378 log_debug("%s: image %ux%u (%ux%u)", __func__, si->x, si->y, cx, cy);
379 for (i = 0; i < si->ncolours; i++)
380 log_debug("%s: colour %u is %07x", __func__, i, si->colours[i]);
381 for (y = 0; y < si->y; y++) {
382 sl = &si->lines[y];
383 for (x = 0; x < si->x; x++) {
384 if (x >= sl->x)
385 s[x] = '_';
386 else if (sl->data[x] != 0)
387 s[x] = '0' + (sl->data[x] - 1) % 10;
388 else
389 s[x] = '.';
391 s[x] = '\0';
392 log_debug("%s: %4u: %s", __func__, y, s);
396 void
397 sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y)
399 if ((si->x % si->xpixel) == 0)
400 *x = (si->x / si->xpixel);
401 else
402 *x = 1 + (si->x / si->xpixel);
403 if ((si->y % si->ypixel) == 0)
404 *y = (si->y / si->ypixel);
405 else
406 *y = 1 + (si->y / si->ypixel);
409 struct sixel_image *
410 sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox,
411 u_int oy, u_int sx, u_int sy, int colours)
413 struct sixel_image *new;
414 u_int cx, cy, pox, poy, psx, psy, tsx, tsy, px, py;
415 u_int x, y, i;
418 * We want to get the section of the image at ox,oy in image cells and
419 * map it onto the same size in terminal cells, remembering that we
420 * can only draw vertical sections of six pixels.
423 sixel_size_in_cells(si, &cx, &cy);
424 if (ox >= cx)
425 return (NULL);
426 if (oy >= cy)
427 return (NULL);
428 if (ox + sx >= cx)
429 sx = cx - ox;
430 if (oy + sy >= cy)
431 sy = cy - oy;
433 if (xpixel == 0)
434 xpixel = si->xpixel;
435 if (ypixel == 0)
436 ypixel = si->ypixel;
438 pox = ox * si->xpixel;
439 poy = oy * si->ypixel;
440 psx = sx * si->xpixel;
441 psy = sy * si->ypixel;
443 tsx = sx * xpixel;
444 tsy = ((sy * ypixel) / 6) * 6;
446 new = xcalloc (1, sizeof *si);
447 new->xpixel = xpixel;
448 new->ypixel = ypixel;
449 new->p2 = si->p2;
451 new->set_ra = si->set_ra;
452 /* clamp to slice end */
453 new->ra_x = si->ra_x < psx ? si->ra_x : psx;
454 new->ra_y = si->ra_y < psy ? si->ra_y : psy;
455 /* subtract slice origin */
456 new->ra_x = new->ra_x > pox ? new->ra_x - pox : 0;
457 new->ra_y = new->ra_y > poy ? new->ra_y - poy : 0;
458 /* resize */
459 new->ra_x = new->ra_x * xpixel / si->xpixel;
460 new->ra_y = new->ra_y * ypixel / si->ypixel;
462 for (y = 0; y < tsy; y++) {
463 py = poy + ((double)y * psy / tsy);
464 for (x = 0; x < tsx; x++) {
465 px = pox + ((double)x * psx / tsx);
466 sixel_set_pixel(new, x, y, sixel_get_pixel(si, px, py));
470 if (colours) {
471 new->colours = xmalloc(si->ncolours * sizeof *new->colours);
472 for (i = 0; i < si->ncolours; i++)
473 new->colours[i] = si->colours[i];
474 new->ncolours = si->ncolours;
476 return (new);
479 static void
480 sixel_print_add(char **buf, size_t *len, size_t *used, const char *s,
481 size_t slen)
483 if (*used + slen >= *len + 1) {
484 (*len) *= 2;
485 *buf = xrealloc(*buf, *len);
487 memcpy(*buf + *used, s, slen);
488 (*used) += slen;
491 static void
492 sixel_print_repeat(char **buf, size_t *len, size_t *used, u_int count, char ch)
494 char tmp[16];
495 size_t tmplen;
497 if (count == 1)
498 sixel_print_add(buf, len, used, &ch, 1);
499 else if (count == 2) {
500 sixel_print_add(buf, len, used, &ch, 1);
501 sixel_print_add(buf, len, used, &ch, 1);
502 } else if (count == 3) {
503 sixel_print_add(buf, len, used, &ch, 1);
504 sixel_print_add(buf, len, used, &ch, 1);
505 sixel_print_add(buf, len, used, &ch, 1);
506 } else if (count != 0) {
507 tmplen = xsnprintf(tmp, sizeof tmp, "!%u%c", count, ch);
508 sixel_print_add(buf, len, used, tmp, tmplen);
512 static void
513 sixel_print_compress_colors(struct sixel_image *si, struct sixel_chunk *chunks,
514 u_int y, u_int *active, u_int *nactive)
516 u_int i, x, c, dx, colors[6];
517 struct sixel_chunk *chunk = NULL;
518 struct sixel_line *sl;
520 for (x = 0; x < si->x; x++) {
521 for (i = 0; i < 6; i++) {
522 colors[i] = 0;
523 if (y + i < si->y) {
524 sl = &si->lines[y + i];
525 if (x < sl->x && sl->data[x] != 0) {
526 colors[i] = sl->data[x];
527 c = sl->data[x] - 1;
528 chunks[c].next_pattern |= 1 << i;
533 for (i = 0; i < 6; i++) {
534 if (colors[i] == 0)
535 continue;
537 c = colors[i] - 1;
538 chunk = &chunks[c];
539 if (chunk->next_x == x + 1)
540 continue;
542 if (chunk->next_y < y + 1) {
543 chunk->next_y = y + 1;
544 active[(*nactive)++] = c;
547 dx = x - chunk->next_x;
548 if (chunk->pattern != chunk->next_pattern || dx != 0) {
549 sixel_print_repeat(&chunk->data, &chunk->len,
550 &chunk->used, chunk->count,
551 chunk->pattern + 0x3f);
552 sixel_print_repeat(&chunk->data, &chunk->len,
553 &chunk->used, dx, '?');
554 chunk->pattern = chunk->next_pattern;
555 chunk->count = 0;
557 chunk->count++;
558 chunk->next_pattern = 0;
559 chunk->next_x = x + 1;
564 char *
565 sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size)
567 char *buf, tmp[64];
568 size_t len, used = 0, tmplen;
569 u_int *colours, ncolours, i, c, y, *active, nactive;
570 struct sixel_chunk *chunks, *chunk;
572 if (map != NULL) {
573 colours = map->colours;
574 ncolours = map->ncolours;
575 } else {
576 colours = si->colours;
577 ncolours = si->ncolours;
580 if (ncolours == 0)
581 return (NULL);
583 len = 8192;
584 buf = xmalloc(len);
586 tmplen = xsnprintf(tmp, sizeof tmp, "\033P0;%uq", si->p2);
587 sixel_print_add(&buf, &len, &used, tmp, tmplen);
589 if (si->set_ra) {
590 tmplen = xsnprintf(tmp, sizeof tmp, "\"1;1;%u;%u", si->ra_x,
591 si->ra_y);
592 sixel_print_add(&buf, &len, &used, tmp, tmplen);
595 chunks = xcalloc(ncolours, sizeof *chunks);
596 active = xcalloc(ncolours, sizeof *active);
598 for (i = 0; i < ncolours; i++) {
599 c = colours[i];
600 tmplen = xsnprintf(tmp, sizeof tmp, "#%u;%u;%u;%u;%u",
601 i, c >> 24, (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff);
602 sixel_print_add(&buf, &len, &used, tmp, tmplen);
604 chunk = &chunks[i];
605 chunk->len = 8;
606 chunk->data = xmalloc(chunk->len);
609 for (y = 0; y < si->y; y += 6) {
610 nactive = 0;
611 sixel_print_compress_colors(si, chunks, y, active, &nactive);
613 for (i = 0; i < nactive; i++) {
614 c = active[i];
615 chunk = &chunks[c];
616 tmplen = xsnprintf(tmp, sizeof tmp, "#%u", c);
617 sixel_print_add(&buf, &len, &used, tmp, tmplen);
618 sixel_print_add(&buf, &len, &used, chunk->data,
619 chunk->used);
620 sixel_print_repeat(&buf, &len, &used, chunk->count,
621 chunk->pattern + 0x3f);
622 sixel_print_add(&buf, &len, &used, "$", 1);
623 chunk->used = chunk->next_x = chunk->count = 0;
626 if (buf[used - 1] == '$')
627 used--;
628 sixel_print_add(&buf, &len, &used, "-", 1);
630 if (buf[used - 1] == '-')
631 used--;
633 sixel_print_add(&buf, &len, &used, "\033\\", 2);
635 buf[used] = '\0';
636 if (size != NULL)
637 *size = used;
639 for (i = 0; i < ncolours; i++)
640 free(chunks[i].data);
641 free(active);
642 free(chunks);
644 return (buf);
647 struct screen *
648 sixel_to_screen(struct sixel_image *si)
650 struct screen *s;
651 struct screen_write_ctx ctx;
652 struct grid_cell gc;
653 u_int x, y, sx, sy;
655 sixel_size_in_cells(si, &sx, &sy);
657 s = xmalloc(sizeof *s);
658 screen_init(s, sx, sy, 0);
660 memcpy(&gc, &grid_default_cell, sizeof gc);
661 gc.attr |= (GRID_ATTR_CHARSET|GRID_ATTR_DIM);
662 utf8_set(&gc.data, '~');
664 screen_write_start(&ctx, s);
665 if (sx == 1 || sy == 1) {
666 for (y = 0; y < sy; y++) {
667 for (x = 0; x < sx; x++)
668 grid_view_set_cell(s->grid, x, y, &gc);
670 } else {
671 screen_write_box(&ctx, sx, sy, BOX_LINES_DEFAULT, NULL, NULL);
672 for (y = 1; y < sy - 1; y++) {
673 for (x = 1; x < sx - 1; x++)
674 grid_view_set_cell(s->grid, x, y, &gc);
677 screen_write_stop(&ctx);
678 return (s);