makefile: fixup bad merge
[tmux.git] / image-sixel.c
blob41170ec08a702ef2ee945e57c2888f3ef93ca84c
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_COLOUR_REGISTERS 1024
27 #define SIXEL_WIDTH_LIMIT 10000
28 #define SIXEL_HEIGHT_LIMIT 10000
30 struct sixel_line {
31 u_int x;
32 uint16_t *data;
35 struct sixel_image {
36 u_int x;
37 u_int y;
38 u_int xpixel;
39 u_int ypixel;
41 u_int *colours;
42 u_int ncolours;
44 u_int dx;
45 u_int dy;
46 u_int dc;
48 struct sixel_line *lines;
51 static int
52 sixel_parse_expand_lines(struct sixel_image *si, u_int y)
54 if (y <= si->y)
55 return (0);
56 if (y > SIXEL_HEIGHT_LIMIT)
57 return (1);
58 si->lines = xrecallocarray(si->lines, si->y, y, sizeof *si->lines);
59 si->y = y;
60 return (0);
63 static int
64 sixel_parse_expand_line(struct sixel_image *si, struct sixel_line *sl, u_int x)
66 if (x <= sl->x)
67 return (0);
68 if (x > SIXEL_WIDTH_LIMIT)
69 return (1);
70 if (x > si->x)
71 si->x = x;
72 sl->data = xrecallocarray(sl->data, sl->x, si->x, sizeof *sl->data);
73 sl->x = si->x;
74 return (0);
77 static u_int
78 sixel_get_pixel(struct sixel_image *si, u_int x, u_int y)
80 struct sixel_line *sl;
82 if (y >= si->y)
83 return (0);
84 sl = &si->lines[y];
85 if (x >= sl->x)
86 return (0);
87 return (sl->data[x]);
90 static int
91 sixel_set_pixel(struct sixel_image *si, u_int x, u_int y, u_int c)
93 struct sixel_line *sl;
95 if (sixel_parse_expand_lines(si, y + 1) != 0)
96 return (1);
97 sl = &si->lines[y];
98 if (sixel_parse_expand_line(si, sl, x + 1) != 0)
99 return (1);
100 sl->data[x] = c;
101 return (0);
104 static int
105 sixel_parse_write(struct sixel_image *si, u_int ch)
107 struct sixel_line *sl;
108 u_int i;
110 if (sixel_parse_expand_lines(si, si->dy + 6) != 0)
111 return (1);
112 sl = &si->lines[si->dy];
114 for (i = 0; i < 6; i++) {
115 if (sixel_parse_expand_line(si, sl, si->dx + 1) != 0)
116 return (1);
117 if (ch & (1 << i))
118 sl->data[si->dx] = si->dc;
119 sl++;
121 return (0);
124 static const char *
125 sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end)
127 const char *last;
128 char *endptr;
129 u_int x, y;
131 last = cp;
132 while (last != end) {
133 if (*last != ';' && (*last < '0' || *last > '9'))
134 break;
135 last++;
137 strtoul(cp, &endptr, 10);
138 if (endptr == last || *endptr != ';')
139 return (last);
140 strtoul(endptr + 1, &endptr, 10);
141 if (endptr == last || *endptr != ';') {
142 log_debug("%s: missing ;", __func__);
143 return (NULL);
146 x = strtoul(endptr + 1, &endptr, 10);
147 if (endptr == last || *endptr != ';') {
148 log_debug("%s: missing ;", __func__);
149 return (NULL);
151 if (x > SIXEL_WIDTH_LIMIT) {
152 log_debug("%s: image is too wide", __func__);
153 return (NULL);
155 y = strtoul(endptr + 1, &endptr, 10);
156 if (endptr != last) {
157 log_debug("%s: extra ;", __func__);
158 return (NULL);
160 if (y > SIXEL_HEIGHT_LIMIT) {
161 log_debug("%s: image is too tall", __func__);
162 return (NULL);
165 si->x = x;
166 sixel_parse_expand_lines(si, y);
168 return (last);
171 static const char *
172 sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end)
174 const char *last;
175 char *endptr;
176 u_int c, type, r, g, b;
178 last = cp;
179 while (last != end) {
180 if (*last != ';' && (*last < '0' || *last > '9'))
181 break;
182 last++;
185 c = strtoul(cp, &endptr, 10);
186 if (c > SIXEL_COLOUR_REGISTERS) {
187 log_debug("%s: too many colours", __func__);
188 return (NULL);
190 si->dc = c + 1;
191 if (endptr == last || *endptr != ';')
192 return (last);
194 type = strtoul(endptr + 1, &endptr, 10);
195 if (endptr == last || *endptr != ';') {
196 log_debug("%s: missing ;", __func__);
197 return (NULL);
199 r = strtoul(endptr + 1, &endptr, 10);
200 if (endptr == last || *endptr != ';') {
201 log_debug("%s: missing ;", __func__);
202 return (NULL);
204 g = strtoul(endptr + 1, &endptr, 10);
205 if (endptr == last || *endptr != ';') {
206 log_debug("%s: missing ;", __func__);
207 return (NULL);
209 b = strtoul(endptr + 1, &endptr, 10);
210 if (endptr != last) {
211 log_debug("%s: missing ;", __func__);
212 return (NULL);
215 if (type != 1 && type != 2) {
216 log_debug("%s: invalid type %d", __func__, type);
217 return (NULL);
219 if (c + 1 > si->ncolours) {
220 si->colours = xrecallocarray(si->colours, si->ncolours, c + 1,
221 sizeof *si->colours);
222 si->ncolours = c + 1;
224 si->colours[c] = (type << 24) | (r << 16) | (g << 8) | b;
225 return (last);
228 static const char *
229 sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end)
231 const char *last;
232 char tmp[32], ch;
233 u_int n = 0, i;
234 const char *errstr = NULL;
236 last = cp;
237 while (last != end) {
238 if (*last < '0' || *last > '9')
239 break;
240 tmp[n++] = *last++;
241 if (n == (sizeof tmp) - 1) {
242 log_debug("%s: repeat not terminated", __func__);
243 return (NULL);
246 if (n == 0 || last == end) {
247 log_debug("%s: repeat not terminated", __func__);
248 return (NULL);
250 tmp[n] = '\0';
252 n = strtonum(tmp, 1, SIXEL_WIDTH_LIMIT, &errstr);
253 if (n == 0 || errstr != NULL) {
254 log_debug("%s: repeat too wide", __func__);
255 return (NULL);
258 ch = (*last++) - 0x3f;
259 for (i = 0; i < n; i++) {
260 if (sixel_parse_write(si, ch) != 0) {
261 log_debug("%s: width limit reached", __func__);
262 return (NULL);
264 si->dx++;
266 return (last);
269 struct sixel_image *
270 sixel_parse(const char *buf, size_t len, u_int xpixel, u_int ypixel)
272 struct sixel_image *si;
273 const char *cp = buf, *end = buf + len;
274 char ch;
276 if (len == 0 || len == 1 || *cp++ != 'q') {
277 log_debug("%s: empty image", __func__);
278 return (NULL);
281 si = xcalloc (1, sizeof *si);
282 si->xpixel = xpixel;
283 si->ypixel = ypixel;
285 while (cp != end) {
286 ch = *cp++;
287 switch (ch) {
288 case '"':
289 cp = sixel_parse_attributes(si, cp, end);
290 if (cp == NULL)
291 goto bad;
292 break;
293 case '#':
294 cp = sixel_parse_colour(si, cp, end);
295 if (cp == NULL)
296 goto bad;
297 break;
298 case '!':
299 cp = sixel_parse_repeat(si, cp, end);
300 if (cp == NULL)
301 goto bad;
302 break;
303 case '-':
304 si->dx = 0;
305 si->dy += 6;
306 break;
307 case '$':
308 si->dx = 0;
309 break;
310 default:
311 if (ch < 0x20)
312 break;
313 if (ch < 0x3f || ch > 0x7e)
314 goto bad;
315 if (sixel_parse_write(si, ch - 0x3f) != 0) {
316 log_debug("%s: width limit reached", __func__);
317 goto bad;
319 si->dx++;
320 break;
324 if (si->x == 0 || si->y == 0)
325 goto bad;
326 return (si);
328 bad:
329 free(si);
330 return (NULL);
333 void
334 sixel_free(struct sixel_image *si)
336 u_int y;
338 for (y = 0; y < si->y; y++)
339 free(si->lines[y].data);
340 free(si->lines);
342 free(si->colours);
343 free(si);
346 void
347 sixel_log(struct sixel_image *si)
349 struct sixel_line *sl;
350 char s[SIXEL_WIDTH_LIMIT + 1];
351 u_int i, x, y, cx, cy;
353 sixel_size_in_cells(si, &cx, &cy);
354 log_debug("%s: image %ux%u (%ux%u)", __func__, si->x, si->y, cx, cy);
355 for (i = 0; i < si->ncolours; i++)
356 log_debug("%s: colour %u is %07x", __func__, i, si->colours[i]);
357 for (y = 0; y < si->y; y++) {
358 sl = &si->lines[y];
359 for (x = 0; x < si->x; x++) {
360 if (x >= sl->x)
361 s[x] = '_';
362 else if (sl->data[x] != 0)
363 s[x] = '0' + (sl->data[x] - 1) % 10;
364 else
365 s[x] = '.';
367 s[x] = '\0';
368 log_debug("%s: %4u: %s", __func__, y, s);
372 void
373 sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y)
375 if ((si->x % si->xpixel) == 0)
376 *x = (si->x / si->xpixel);
377 else
378 *x = 1 + (si->x / si->xpixel);
379 if ((si->y % si->ypixel) == 0)
380 *y = (si->y / si->ypixel);
381 else
382 *y = 1 + (si->y / si->ypixel);
385 struct sixel_image *
386 sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox,
387 u_int oy, u_int sx, u_int sy, int colours)
389 struct sixel_image *new;
390 u_int cx, cy, pox, poy, psx, psy, tsx, tsy, px, py;
391 u_int x, y, i;
394 * We want to get the section of the image at ox,oy in image cells and
395 * map it onto the same size in terminal cells, remembering that we
396 * can only draw vertical sections of six pixels.
399 sixel_size_in_cells(si, &cx, &cy);
400 if (ox >= cx)
401 return (NULL);
402 if (oy >= cy)
403 return (NULL);
404 if (ox + sx >= cx)
405 sx = cx - ox;
406 if (oy + sy >= cy)
407 sy = cy - oy;
409 if (xpixel == 0)
410 xpixel = si->xpixel;
411 if (ypixel == 0)
412 ypixel = si->ypixel;
414 pox = ox * si->xpixel;
415 poy = oy * si->ypixel;
416 psx = sx * si->xpixel;
417 psy = sy * si->ypixel;
419 tsx = sx * xpixel;
420 tsy = ((sy * ypixel) / 6) * 6;
422 new = xcalloc (1, sizeof *si);
423 new->xpixel = xpixel;
424 new->ypixel = ypixel;
426 for (y = 0; y < tsy; y++) {
427 py = poy + ((double)y * psy / tsy);
428 for (x = 0; x < tsx; x++) {
429 px = pox + ((double)x * psx / tsx);
430 sixel_set_pixel(new, x, y, sixel_get_pixel(si, px, py));
434 if (colours) {
435 new->colours = xmalloc(si->ncolours * sizeof *new->colours);
436 for (i = 0; i < si->ncolours; i++)
437 new->colours[i] = si->colours[i];
438 new->ncolours = si->ncolours;
440 return (new);
443 static void
444 sixel_print_add(char **buf, size_t *len, size_t *used, const char *s,
445 size_t slen)
447 if (*used + slen >= *len + 1) {
448 (*len) *= 2;
449 *buf = xrealloc(*buf, *len);
451 memcpy(*buf + *used, s, slen);
452 (*used) += slen;
455 static void
456 sixel_print_repeat(char **buf, size_t *len, size_t *used, u_int count, char ch)
458 char tmp[16];
459 size_t tmplen;
461 if (count == 1)
462 sixel_print_add(buf, len, used, &ch, 1);
463 else if (count == 2) {
464 sixel_print_add(buf, len, used, &ch, 1);
465 sixel_print_add(buf, len, used, &ch, 1);
466 } else if (count == 3) {
467 sixel_print_add(buf, len, used, &ch, 1);
468 sixel_print_add(buf, len, used, &ch, 1);
469 sixel_print_add(buf, len, used, &ch, 1);
470 } else if (count != 0) {
471 tmplen = xsnprintf(tmp, sizeof tmp, "!%u%c", count, ch);
472 sixel_print_add(buf, len, used, tmp, tmplen);
476 char *
477 sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size)
479 char *buf, tmp[64], *contains, data, last = 0;
480 size_t len, used = 0, tmplen;
481 u_int *colours, ncolours, i, c, x, y, count;
482 struct sixel_line *sl;
484 if (map != NULL) {
485 colours = map->colours;
486 ncolours = map->ncolours;
487 } else {
488 colours = si->colours;
489 ncolours = si->ncolours;
491 contains = xcalloc(1, ncolours);
493 len = 8192;
494 buf = xmalloc(len);
496 sixel_print_add(&buf, &len, &used, "\033Pq", 3);
498 tmplen = xsnprintf(tmp, sizeof tmp, "\"1;1;%u;%u", si->x, si->y);
499 sixel_print_add(&buf, &len, &used, tmp, tmplen);
501 for (i = 0; i < ncolours; i++) {
502 c = colours[i];
503 tmplen = xsnprintf(tmp, sizeof tmp, "#%u;%u;%u;%u;%u",
504 i, c >> 24, (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff);
505 sixel_print_add(&buf, &len, &used, tmp, tmplen);
508 for (y = 0; y < si->y; y += 6) {
509 memset(contains, 0, ncolours);
510 for (x = 0; x < si->x; x++) {
511 for (i = 0; i < 6; i++) {
512 if (y + i >= si->y)
513 break;
514 sl = &si->lines[y + i];
515 if (x < sl->x && sl->data[x] != 0)
516 contains[sl->data[x] - 1] = 1;
520 for (c = 0; c < ncolours; c++) {
521 if (!contains[c])
522 continue;
523 tmplen = xsnprintf(tmp, sizeof tmp, "#%u", c);
524 sixel_print_add(&buf, &len, &used, tmp, tmplen);
526 count = 0;
527 for (x = 0; x < si->x; x++) {
528 data = 0;
529 for (i = 0; i < 6; i++) {
530 if (y + i >= si->y)
531 break;
532 sl = &si->lines[y + i];
533 if (x < sl->x && sl->data[x] == c + 1)
534 data |= (1 << i);
536 data += 0x3f;
537 if (data != last) {
538 sixel_print_repeat(&buf, &len, &used,
539 count, last);
540 last = data;
541 count = 1;
542 } else
543 count++;
545 sixel_print_repeat(&buf, &len, &used, count, data);
546 sixel_print_add(&buf, &len, &used, "$", 1);
549 if (buf[used - 1] == '$')
550 used--;
551 if (buf[used - 1] != '-')
552 sixel_print_add(&buf, &len, &used, "-", 1);
554 if (buf[used - 1] == '$' || buf[used - 1] == '-')
555 used--;
557 sixel_print_add(&buf, &len, &used, "\033\\", 2);
559 buf[used] = '\0';
560 if (size != NULL)
561 *size = used;
563 free(contains);
564 return (buf);
567 struct screen *
568 sixel_to_screen(struct sixel_image *si)
570 struct screen *s;
571 struct screen_write_ctx ctx;
572 struct grid_cell gc;
573 u_int x, y, sx, sy;
575 sixel_size_in_cells(si, &sx, &sy);
577 s = xmalloc(sizeof *s);
578 screen_init(s, sx, sy, 0);
580 memcpy(&gc, &grid_default_cell, sizeof gc);
581 gc.attr |= (GRID_ATTR_CHARSET|GRID_ATTR_DIM);
582 utf8_set(&gc.data, '~');
584 screen_write_start(&ctx, s);
585 if (sx == 1 || sy == 1) {
586 for (y = 0; y < sy; y++) {
587 for (x = 0; x < sx; x++)
588 grid_view_set_cell(s->grid, x, y, &gc);
590 } else {
591 screen_write_box(&ctx, sx, sy, BOX_LINES_DEFAULT, NULL, NULL);
592 for (y = 1; y < sy - 1; y++) {
593 for (x = 1; x < sx - 1; x++)
594 grid_view_set_cell(s->grid, x, y, &gc);
597 screen_write_stop(&ctx);
598 return (s);