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