Merge branch 'obsd-master'
[tmux.git] / image-sixel.c
blobe23d17f90295f868b843ec4b7a24a80f5498d669
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 *colours;
41 u_int ncolours;
43 u_int dx;
44 u_int dy;
45 u_int dc;
47 struct sixel_line *lines;
50 static int
51 sixel_parse_expand_lines(struct sixel_image *si, u_int y)
53 if (y <= si->y)
54 return (0);
55 if (y > SIXEL_HEIGHT_LIMIT)
56 return (1);
57 si->lines = xrecallocarray(si->lines, si->y, y, sizeof *si->lines);
58 si->y = y;
59 return (0);
62 static int
63 sixel_parse_expand_line(struct sixel_image *si, struct sixel_line *sl, u_int x)
65 if (x <= sl->x)
66 return (0);
67 if (x > SIXEL_WIDTH_LIMIT)
68 return (1);
69 if (x > si->x)
70 si->x = x;
71 sl->data = xrecallocarray(sl->data, sl->x, si->x, sizeof *sl->data);
72 sl->x = si->x;
73 return (0);
76 static u_int
77 sixel_get_pixel(struct sixel_image *si, u_int x, u_int y)
79 struct sixel_line *sl;
81 if (y >= si->y)
82 return (0);
83 sl = &si->lines[y];
84 if (x >= sl->x)
85 return (0);
86 return (sl->data[x]);
89 static int
90 sixel_set_pixel(struct sixel_image *si, u_int x, u_int y, u_int c)
92 struct sixel_line *sl;
94 if (sixel_parse_expand_lines(si, y + 1) != 0)
95 return (1);
96 sl = &si->lines[y];
97 if (sixel_parse_expand_line(si, sl, x + 1) != 0)
98 return (1);
99 sl->data[x] = c;
100 return (0);
103 static int
104 sixel_parse_write(struct sixel_image *si, u_int ch)
106 struct sixel_line *sl;
107 u_int i;
109 if (sixel_parse_expand_lines(si, si->dy + 6) != 0)
110 return (1);
111 sl = &si->lines[si->dy];
113 for (i = 0; i < 6; i++) {
114 if (sixel_parse_expand_line(si, sl, si->dx + 1) != 0)
115 return (1);
116 if (ch & (1 << i))
117 sl->data[si->dx] = si->dc;
118 sl++;
120 return (0);
123 static const char *
124 sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end)
126 const char *last;
127 char *endptr;
128 u_int x, y;
130 last = cp;
131 while (last != end) {
132 if (*last != ';' && (*last < '0' || *last > '9'))
133 break;
134 last++;
136 strtoul(cp, &endptr, 10);
137 if (endptr == last || *endptr != ';')
138 return (last);
139 strtoul(endptr + 1, &endptr, 10);
140 if (endptr == last)
141 return (last);
142 if (*endptr != ';') {
143 log_debug("%s: missing ;", __func__);
144 return (NULL);
147 x = strtoul(endptr + 1, &endptr, 10);
148 if (endptr == last || *endptr != ';') {
149 log_debug("%s: missing ;", __func__);
150 return (NULL);
152 if (x > SIXEL_WIDTH_LIMIT) {
153 log_debug("%s: image is too wide", __func__);
154 return (NULL);
156 y = strtoul(endptr + 1, &endptr, 10);
157 if (endptr != last) {
158 log_debug("%s: extra ;", __func__);
159 return (NULL);
161 if (y > SIXEL_HEIGHT_LIMIT) {
162 log_debug("%s: image is too tall", __func__);
163 return (NULL);
166 si->x = x;
167 sixel_parse_expand_lines(si, y);
169 return (last);
172 static const char *
173 sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end)
175 const char *last;
176 char *endptr;
177 u_int c, type, r, g, b;
179 last = cp;
180 while (last != end) {
181 if (*last != ';' && (*last < '0' || *last > '9'))
182 break;
183 last++;
186 c = strtoul(cp, &endptr, 10);
187 if (c > SIXEL_COLOUR_REGISTERS) {
188 log_debug("%s: too many colours", __func__);
189 return (NULL);
191 si->dc = c + 1;
192 if (endptr == last || *endptr != ';')
193 return (last);
195 type = strtoul(endptr + 1, &endptr, 10);
196 if (endptr == last || *endptr != ';') {
197 log_debug("%s: missing ;", __func__);
198 return (NULL);
200 r = strtoul(endptr + 1, &endptr, 10);
201 if (endptr == last || *endptr != ';') {
202 log_debug("%s: missing ;", __func__);
203 return (NULL);
205 g = strtoul(endptr + 1, &endptr, 10);
206 if (endptr == last || *endptr != ';') {
207 log_debug("%s: missing ;", __func__);
208 return (NULL);
210 b = strtoul(endptr + 1, &endptr, 10);
211 if (endptr != last) {
212 log_debug("%s: missing ;", __func__);
213 return (NULL);
216 if (type != 1 && type != 2) {
217 log_debug("%s: invalid type %d", __func__, type);
218 return (NULL);
220 if (c + 1 > si->ncolours) {
221 si->colours = xrecallocarray(si->colours, si->ncolours, c + 1,
222 sizeof *si->colours);
223 si->ncolours = c + 1;
225 si->colours[c] = (type << 24) | (r << 16) | (g << 8) | b;
226 return (last);
229 static const char *
230 sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end)
232 const char *last;
233 char tmp[32], ch;
234 u_int n = 0, i;
235 const char *errstr = NULL;
237 last = cp;
238 while (last != end) {
239 if (*last < '0' || *last > '9')
240 break;
241 tmp[n++] = *last++;
242 if (n == (sizeof tmp) - 1) {
243 log_debug("%s: repeat not terminated", __func__);
244 return (NULL);
247 if (n == 0 || last == end) {
248 log_debug("%s: repeat not terminated", __func__);
249 return (NULL);
251 tmp[n] = '\0';
253 n = strtonum(tmp, 1, SIXEL_WIDTH_LIMIT, &errstr);
254 if (n == 0 || errstr != NULL) {
255 log_debug("%s: repeat too wide", __func__);
256 return (NULL);
259 ch = (*last++) - 0x3f;
260 for (i = 0; i < n; i++) {
261 if (sixel_parse_write(si, ch) != 0) {
262 log_debug("%s: width limit reached", __func__);
263 return (NULL);
265 si->dx++;
267 return (last);
270 struct sixel_image *
271 sixel_parse(const char *buf, size_t len, u_int xpixel, u_int ypixel)
273 struct sixel_image *si;
274 const char *cp = buf, *end = buf + len;
275 char ch;
277 if (len == 0 || len == 1 || *cp++ != 'q') {
278 log_debug("%s: empty image", __func__);
279 return (NULL);
282 si = xcalloc (1, sizeof *si);
283 si->xpixel = xpixel;
284 si->ypixel = ypixel;
286 while (cp != end) {
287 ch = *cp++;
288 switch (ch) {
289 case '"':
290 cp = sixel_parse_attributes(si, cp, end);
291 if (cp == NULL)
292 goto bad;
293 break;
294 case '#':
295 cp = sixel_parse_colour(si, cp, end);
296 if (cp == NULL)
297 goto bad;
298 break;
299 case '!':
300 cp = sixel_parse_repeat(si, cp, end);
301 if (cp == NULL)
302 goto bad;
303 break;
304 case '-':
305 si->dx = 0;
306 si->dy += 6;
307 break;
308 case '$':
309 si->dx = 0;
310 break;
311 default:
312 if (ch < 0x20)
313 break;
314 if (ch < 0x3f || ch > 0x7e)
315 goto bad;
316 if (sixel_parse_write(si, ch - 0x3f) != 0) {
317 log_debug("%s: width limit reached", __func__);
318 goto bad;
320 si->dx++;
321 break;
325 if (si->x == 0 || si->y == 0)
326 goto bad;
327 return (si);
329 bad:
330 free(si);
331 return (NULL);
334 void
335 sixel_free(struct sixel_image *si)
337 u_int y;
339 for (y = 0; y < si->y; y++)
340 free(si->lines[y].data);
341 free(si->lines);
343 free(si->colours);
344 free(si);
347 void
348 sixel_log(struct sixel_image *si)
350 struct sixel_line *sl;
351 char s[SIXEL_WIDTH_LIMIT + 1];
352 u_int i, x, y, cx, cy;
354 sixel_size_in_cells(si, &cx, &cy);
355 log_debug("%s: image %ux%u (%ux%u)", __func__, si->x, si->y, cx, cy);
356 for (i = 0; i < si->ncolours; i++)
357 log_debug("%s: colour %u is %07x", __func__, i, si->colours[i]);
358 for (y = 0; y < si->y; y++) {
359 sl = &si->lines[y];
360 for (x = 0; x < si->x; x++) {
361 if (x >= sl->x)
362 s[x] = '_';
363 else if (sl->data[x] != 0)
364 s[x] = '0' + (sl->data[x] - 1) % 10;
365 else
366 s[x] = '.';
368 s[x] = '\0';
369 log_debug("%s: %4u: %s", __func__, y, s);
373 void
374 sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y)
376 if ((si->x % si->xpixel) == 0)
377 *x = (si->x / si->xpixel);
378 else
379 *x = 1 + (si->x / si->xpixel);
380 if ((si->y % si->ypixel) == 0)
381 *y = (si->y / si->ypixel);
382 else
383 *y = 1 + (si->y / si->ypixel);
386 struct sixel_image *
387 sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox,
388 u_int oy, u_int sx, u_int sy, int colours)
390 struct sixel_image *new;
391 u_int cx, cy, pox, poy, psx, psy, tsx, tsy, px, py;
392 u_int x, y, i;
395 * We want to get the section of the image at ox,oy in image cells and
396 * map it onto the same size in terminal cells, remembering that we
397 * can only draw vertical sections of six pixels.
400 sixel_size_in_cells(si, &cx, &cy);
401 if (ox >= cx)
402 return (NULL);
403 if (oy >= cy)
404 return (NULL);
405 if (ox + sx >= cx)
406 sx = cx - ox;
407 if (oy + sy >= cy)
408 sy = cy - oy;
410 if (xpixel == 0)
411 xpixel = si->xpixel;
412 if (ypixel == 0)
413 ypixel = si->ypixel;
415 pox = ox * si->xpixel;
416 poy = oy * si->ypixel;
417 psx = sx * si->xpixel;
418 psy = sy * si->ypixel;
420 tsx = sx * xpixel;
421 tsy = ((sy * ypixel) / 6) * 6;
423 new = xcalloc (1, sizeof *si);
424 new->xpixel = xpixel;
425 new->ypixel = ypixel;
427 for (y = 0; y < tsy; y++) {
428 py = poy + ((double)y * psy / tsy);
429 for (x = 0; x < tsx; x++) {
430 px = pox + ((double)x * psx / tsx);
431 sixel_set_pixel(new, x, y, sixel_get_pixel(si, px, py));
435 if (colours) {
436 new->colours = xmalloc(si->ncolours * sizeof *new->colours);
437 for (i = 0; i < si->ncolours; i++)
438 new->colours[i] = si->colours[i];
439 new->ncolours = si->ncolours;
441 return (new);
444 static void
445 sixel_print_add(char **buf, size_t *len, size_t *used, const char *s,
446 size_t slen)
448 if (*used + slen >= *len + 1) {
449 (*len) *= 2;
450 *buf = xrealloc(*buf, *len);
452 memcpy(*buf + *used, s, slen);
453 (*used) += slen;
456 static void
457 sixel_print_repeat(char **buf, size_t *len, size_t *used, u_int count, char ch)
459 char tmp[16];
460 size_t tmplen;
462 if (count == 1)
463 sixel_print_add(buf, len, used, &ch, 1);
464 else if (count == 2) {
465 sixel_print_add(buf, len, used, &ch, 1);
466 sixel_print_add(buf, len, used, &ch, 1);
467 } else if (count == 3) {
468 sixel_print_add(buf, len, used, &ch, 1);
469 sixel_print_add(buf, len, used, &ch, 1);
470 sixel_print_add(buf, len, used, &ch, 1);
471 } else if (count != 0) {
472 tmplen = xsnprintf(tmp, sizeof tmp, "!%u%c", count, ch);
473 sixel_print_add(buf, len, used, tmp, tmplen);
477 char *
478 sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size)
480 char *buf, tmp[64], *contains, data, last = 0;
481 size_t len, used = 0, tmplen;
482 u_int *colours, ncolours, i, c, x, y, count;
483 struct sixel_line *sl;
485 if (map != NULL) {
486 colours = map->colours;
487 ncolours = map->ncolours;
488 } else {
489 colours = si->colours;
490 ncolours = si->ncolours;
493 if (ncolours == 0)
494 return (NULL);
495 contains = xcalloc(1, ncolours);
497 len = 8192;
498 buf = xmalloc(len);
500 sixel_print_add(&buf, &len, &used, "\033Pq", 3);
502 tmplen = xsnprintf(tmp, sizeof tmp, "\"1;1;%u;%u", si->x, si->y);
503 sixel_print_add(&buf, &len, &used, tmp, tmplen);
505 for (i = 0; i < ncolours; i++) {
506 c = colours[i];
507 tmplen = xsnprintf(tmp, sizeof tmp, "#%u;%u;%u;%u;%u",
508 i, c >> 24, (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff);
509 sixel_print_add(&buf, &len, &used, tmp, tmplen);
512 for (y = 0; y < si->y; y += 6) {
513 memset(contains, 0, ncolours);
514 for (x = 0; x < si->x; x++) {
515 for (i = 0; i < 6; i++) {
516 if (y + i >= si->y)
517 break;
518 sl = &si->lines[y + i];
519 if (x < sl->x && sl->data[x] != 0)
520 contains[sl->data[x] - 1] = 1;
524 for (c = 0; c < ncolours; c++) {
525 if (!contains[c])
526 continue;
527 tmplen = xsnprintf(tmp, sizeof tmp, "#%u", c);
528 sixel_print_add(&buf, &len, &used, tmp, tmplen);
530 count = 0;
531 for (x = 0; x < si->x; x++) {
532 data = 0;
533 for (i = 0; i < 6; i++) {
534 if (y + i >= si->y)
535 break;
536 sl = &si->lines[y + i];
537 if (x < sl->x && sl->data[x] == c + 1)
538 data |= (1 << i);
540 data += 0x3f;
541 if (data != last) {
542 sixel_print_repeat(&buf, &len, &used,
543 count, last);
544 last = data;
545 count = 1;
546 } else
547 count++;
549 sixel_print_repeat(&buf, &len, &used, count, data);
550 sixel_print_add(&buf, &len, &used, "$", 1);
553 if (buf[used - 1] == '$')
554 used--;
555 if (buf[used - 1] != '-')
556 sixel_print_add(&buf, &len, &used, "-", 1);
558 if (buf[used - 1] == '$' || buf[used - 1] == '-')
559 used--;
561 sixel_print_add(&buf, &len, &used, "\033\\", 2);
563 buf[used] = '\0';
564 if (size != NULL)
565 *size = used;
567 free(contains);
568 return (buf);
571 struct screen *
572 sixel_to_screen(struct sixel_image *si)
574 struct screen *s;
575 struct screen_write_ctx ctx;
576 struct grid_cell gc;
577 u_int x, y, sx, sy;
579 sixel_size_in_cells(si, &sx, &sy);
581 s = xmalloc(sizeof *s);
582 screen_init(s, sx, sy, 0);
584 memcpy(&gc, &grid_default_cell, sizeof gc);
585 gc.attr |= (GRID_ATTR_CHARSET|GRID_ATTR_DIM);
586 utf8_set(&gc.data, '~');
588 screen_write_start(&ctx, s);
589 if (sx == 1 || sy == 1) {
590 for (y = 0; y < sy; y++) {
591 for (x = 0; x < sx; x++)
592 grid_view_set_cell(s->grid, x, y, &gc);
594 } else {
595 screen_write_box(&ctx, sx, sy, BOX_LINES_DEFAULT, NULL, NULL);
596 for (y = 1; y < sy - 1; y++) {
597 for (x = 1; x < sx - 1; x++)
598 grid_view_set_cell(s->grid, x, y, &gc);
601 screen_write_stop(&ctx);
602 return (s);