1 /* $OpenBSD$ */
2
3 /*
4 * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
5 *
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.
9 *
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.
17 */
18
19 #include <sys/types.h>
20
21 #include <stdlib.h>
22 #include <string.h>
23
24 #include "tmux.h"
25
26 #define SIXEL_WIDTH_LIMIT 10000
27 #define SIXEL_HEIGHT_LIMIT 10000
28
29 struct sixel_line {
30 u_int x;
31 uint16_t *data;
32 };
33
34 struct sixel_image {
35 u_int x;
36 u_int y;
37 u_int xpixel;
38 u_int ypixel;
39
40 u_int *colours;
41 u_int ncolours;
42
43 u_int dx;
44 u_int dy;
45 u_int dc;
46
47 struct sixel_line *lines;
48 };
49
50 static int
sixel_parse_expand_lines(struct sixel_image * si,u_int y)51 sixel_parse_expand_lines(struct sixel_image *si, u_int y)
52 {
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);
60 }
61
62 static int
sixel_parse_expand_line(struct sixel_image * si,struct sixel_line * sl,u_int x)63 sixel_parse_expand_line(struct sixel_image *si, struct sixel_line *sl, u_int x)
64 {
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);
74 }
75
76 static u_int
sixel_get_pixel(struct sixel_image * si,u_int x,u_int y)77 sixel_get_pixel(struct sixel_image *si, u_int x, u_int y)
78 {
79 struct sixel_line *sl;
80
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]);
87 }
88
89 static int
sixel_set_pixel(struct sixel_image * si,u_int x,u_int y,u_int c)90 sixel_set_pixel(struct sixel_image *si, u_int x, u_int y, u_int c)
91 {
92 struct sixel_line *sl;
93
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);
101 }
102
103 static int
sixel_parse_write(struct sixel_image * si,u_int ch)104 sixel_parse_write(struct sixel_image *si, u_int ch)
105 {
106 struct sixel_line *sl;
107 u_int i;
108
109 if (sixel_parse_expand_lines(si, si->dy + 6) != 0)
110 return (1);
111 sl = &si->lines[si->dy];
112
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++;
119 }
120 return (0);
121 }
122
123 static const char *
sixel_parse_attributes(struct sixel_image * si,const char * cp,const char * end)124 sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end)
125 {
126 const char *last;
127 char *endptr;
128 u_int x, y;
129
130 last = cp;
131 while (last != end) {
132 if (*last != ';' && (*last < '0' || *last > '9'))
133 break;
134 last++;
135 }
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);
145 }
146
147 x = strtoul(endptr + 1, &endptr, 10);
148 if (endptr == last || *endptr != ';') {
149 log_debug("%s: missing ;", __func__);
150 return (NULL);
151 }
152 if (x > SIXEL_WIDTH_LIMIT) {
153 log_debug("%s: image is too wide", __func__);
154 return (NULL);
155 }
156 y = strtoul(endptr + 1, &endptr, 10);
157 if (endptr != last) {
158 log_debug("%s: extra ;", __func__);
159 return (NULL);
160 }
161 if (y > SIXEL_HEIGHT_LIMIT) {
162 log_debug("%s: image is too tall", __func__);
163 return (NULL);
164 }
165
166 si->x = x;
167 sixel_parse_expand_lines(si, y);
168
169 return (last);
170 }
171
172 static const char *
sixel_parse_colour(struct sixel_image * si,const char * cp,const char * end)173 sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end)
174 {
175 const char *last;
176 char *endptr;
177 u_int c, type, r, g, b;
178
179 last = cp;
180 while (last != end) {
181 if (*last != ';' && (*last < '0' || *last > '9'))
182 break;
183 last++;
184 }
185
186 c = strtoul(cp, &endptr, 10);
187 if (c > SIXEL_COLOUR_REGISTERS) {
188 log_debug("%s: too many colours", __func__);
189 return (NULL);
190 }
191 si->dc = c + 1;
192 if (endptr == last || *endptr != ';')
193 return (last);
194
195 type = strtoul(endptr + 1, &endptr, 10);
196 if (endptr == last || *endptr != ';') {
197 log_debug("%s: missing ;", __func__);
198 return (NULL);
199 }
200 r = strtoul(endptr + 1, &endptr, 10);
201 if (endptr == last || *endptr != ';') {
202 log_debug("%s: missing ;", __func__);
203 return (NULL);
204 }
205 g = strtoul(endptr + 1, &endptr, 10);
206 if (endptr == last || *endptr != ';') {
207 log_debug("%s: missing ;", __func__);
208 return (NULL);
209 }
210 b = strtoul(endptr + 1, &endptr, 10);
211 if (endptr != last) {
212 log_debug("%s: missing ;", __func__);
213 return (NULL);
214 }
215
216 if (type != 1 && type != 2) {
217 log_debug("%s: invalid type %d", __func__, type);
218 return (NULL);
219 }
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;
224 }
225 si->colours[c] = (type << 24) | (r << 16) | (g << 8) | b;
226 return (last);
227 }
228
229 static const char *
sixel_parse_repeat(struct sixel_image * si,const char * cp,const char * end)230 sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end)
231 {
232 const char *last;
233 char tmp[32], ch;
234 u_int n = 0, i;
235 const char *errstr = NULL;
236
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);
245 }
246 }
247 if (n == 0 || last == end) {
248 log_debug("%s: repeat not terminated", __func__);
249 return (NULL);
250 }
251 tmp[n] = '\0';
252
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);
257 }
258
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);
264 }
265 si->dx++;
266 }
267 return (last);
268 }
269
270 struct sixel_image *
sixel_parse(const char * buf,size_t len,u_int xpixel,u_int ypixel)271 sixel_parse(const char *buf, size_t len, u_int xpixel, u_int ypixel)
272 {
273 struct sixel_image *si;
274 const char *cp = buf, *end = buf + len;
275 char ch;
276
277 if (len == 0 || len == 1 || *cp++ != 'q') {
278 log_debug("%s: empty image", __func__);
279 return (NULL);
280 }
281
282 si = xcalloc (1, sizeof *si);
283 si->xpixel = xpixel;
284 si->ypixel = ypixel;
285
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;
319 }
320 si->dx++;
321 break;
322 }
323 }
324
325 if (si->x == 0 || si->y == 0)
326 goto bad;
327 return (si);
328
329 bad:
330 free(si);
331 return (NULL);
332 }
333
334 void
sixel_free(struct sixel_image * si)335 sixel_free(struct sixel_image *si)
336 {
337 u_int y;
338
339 for (y = 0; y < si->y; y++)
340 free(si->lines[y].data);
341 free(si->lines);
342
343 free(si->colours);
344 free(si);
345 }
346
347 void
sixel_log(struct sixel_image * si)348 sixel_log(struct sixel_image *si)
349 {
350 struct sixel_line *sl;
351 char s[SIXEL_WIDTH_LIMIT + 1];
352 u_int i, x, y, cx, cy;
353
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] = '.';
367 }
368 s[x] = '\0';
369 log_debug("%s: %4u: %s", __func__, y, s);
370 }
371 }
372
373 void
sixel_size_in_cells(struct sixel_image * si,u_int * x,u_int * y)374 sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y)
375 {
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);
384 }
385
386 struct sixel_image *
sixel_scale(struct sixel_image * si,u_int xpixel,u_int ypixel,u_int ox,u_int oy,u_int sx,u_int sy,int colours)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)
389 {
390 struct sixel_image *new;
391 u_int cx, cy, pox, poy, psx, psy, tsx, tsy, px, py;
392 u_int x, y, i;
393
394 /*
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.
398 */
399
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;
409
410 if (xpixel == 0)
411 xpixel = si->xpixel;
412 if (ypixel == 0)
413 ypixel = si->ypixel;
414
415 pox = ox * si->xpixel;
416 poy = oy * si->ypixel;
417 psx = sx * si->xpixel;
418 psy = sy * si->ypixel;
419
420 tsx = sx * xpixel;
421 tsy = ((sy * ypixel) / 6) * 6;
422
423 new = xcalloc (1, sizeof *si);
424 new->xpixel = xpixel;
425 new->ypixel = ypixel;
426
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));
432 }
433 }
434
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;
440 }
441 return (new);
442 }
443
444 static void
sixel_print_add(char ** buf,size_t * len,size_t * used,const char * s,size_t slen)445 sixel_print_add(char **buf, size_t *len, size_t *used, const char *s,
446 size_t slen)
447 {
448 if (*used + slen >= *len + 1) {
449 (*len) *= 2;
450 *buf = xrealloc(*buf, *len);
451 }
452 memcpy(*buf + *used, s, slen);
453 (*used) += slen;
454 }
455
456 static void
sixel_print_repeat(char ** buf,size_t * len,size_t * used,u_int count,char ch)457 sixel_print_repeat(char **buf, size_t *len, size_t *used, u_int count, char ch)
458 {
459 char tmp[16];
460 size_t tmplen;
461
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);
474 }
475 }
476
477 char *
sixel_print(struct sixel_image * si,struct sixel_image * map,size_t * size)478 sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size)
479 {
480 char *buf, tmp[64], *contains, data = 0, last = 0;
481 size_t len, used = 0, tmplen;
482 u_int *colours, ncolours, i, c, x, y, count;
483 struct sixel_line *sl;
484
485 if (map != NULL) {
486 colours = map->colours;
487 ncolours = map->ncolours;
488 } else {
489 colours = si->colours;
490 ncolours = si->ncolours;
491 }
492
493 if (ncolours == 0)
494 return (NULL);
495 contains = xcalloc(1, ncolours);
496
497 len = 8192;
498 buf = xmalloc(len);
499
500 sixel_print_add(&buf, &len, &used, "\033Pq", 3);
501
502 tmplen = xsnprintf(tmp, sizeof tmp, "\"1;1;%u;%u", si->x, si->y);
503 sixel_print_add(&buf, &len, &used, tmp, tmplen);
504
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);
510 }
511
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;
521 }
522 }
523
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);
529
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);
539 }
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++;
548 }
549 sixel_print_repeat(&buf, &len, &used, count, data);
550 sixel_print_add(&buf, &len, &used, "$", 1);
551 }
552
553 if (buf[used - 1] == '$')
554 used--;
555 if (buf[used - 1] != '-')
556 sixel_print_add(&buf, &len, &used, "-", 1);
557 }
558 if (buf[used - 1] == '$' || buf[used - 1] == '-')
559 used--;
560
561 sixel_print_add(&buf, &len, &used, "\033\\", 2);
562
563 buf[used] = '\0';
564 if (size != NULL)
565 *size = used;
566
567 free(contains);
568 return (buf);
569 }
570
571 struct screen *
sixel_to_screen(struct sixel_image * si)572 sixel_to_screen(struct sixel_image *si)
573 {
574 struct screen *s;
575 struct screen_write_ctx ctx;
576 struct grid_cell gc;
577 u_int x, y, sx, sy;
578
579 sixel_size_in_cells(si, &sx, &sy);
580
581 s = xmalloc(sizeof *s);
582 screen_init(s, sx, sy, 0);
583
584 memcpy(&gc, &grid_default_cell, sizeof gc);
585 gc.attr |= (GRID_ATTR_CHARSET|GRID_ATTR_DIM);
586 utf8_set(&gc.data, '~');
587
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);
593 }
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);
599 }
600 }
601 screen_write_stop(&ctx);
602 return (s);
603 }
604