xref: /netbsd-src/external/bsd/tmux/dist/image-sixel.c (revision 890b6d91a44b7fcb2dfbcbc1e93463086e462d2d)
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
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
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
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
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
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 *
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 *
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 *
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 *
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
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
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
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 *
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
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
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 *
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 *
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