/* $OpenBSD$ */ /* * Copyright (c) 2019 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" #define SIXEL_WIDTH_LIMIT 10000 #define SIXEL_HEIGHT_LIMIT 10000 struct sixel_line { u_int x; uint16_t *data; }; struct sixel_image { u_int x; u_int y; u_int xpixel; u_int ypixel; u_int set_ra; u_int ra_x; u_int ra_y; u_int *colours; u_int ncolours; u_int p2; u_int dx; u_int dy; u_int dc; struct sixel_line *lines; }; static int sixel_parse_expand_lines(struct sixel_image *si, u_int y) { if (y <= si->y) return (0); if (y > SIXEL_HEIGHT_LIMIT) return (1); si->lines = xrecallocarray(si->lines, si->y, y, sizeof *si->lines); si->y = y; return (0); } static int sixel_parse_expand_line(struct sixel_image *si, struct sixel_line *sl, u_int x) { if (x <= sl->x) return (0); if (x > SIXEL_WIDTH_LIMIT) return (1); if (x > si->x) si->x = x; sl->data = xrecallocarray(sl->data, sl->x, si->x, sizeof *sl->data); sl->x = si->x; return (0); } static u_int sixel_get_pixel(struct sixel_image *si, u_int x, u_int y) { struct sixel_line *sl; if (y >= si->y) return (0); sl = &si->lines[y]; if (x >= sl->x) return (0); return (sl->data[x]); } static int sixel_set_pixel(struct sixel_image *si, u_int x, u_int y, u_int c) { struct sixel_line *sl; if (sixel_parse_expand_lines(si, y + 1) != 0) return (1); sl = &si->lines[y]; if (sixel_parse_expand_line(si, sl, x + 1) != 0) return (1); sl->data[x] = c; return (0); } static int sixel_parse_write(struct sixel_image *si, u_int ch) { struct sixel_line *sl; u_int i; if (sixel_parse_expand_lines(si, si->dy + 6) != 0) return (1); sl = &si->lines[si->dy]; for (i = 0; i < 6; i++) { if (sixel_parse_expand_line(si, sl, si->dx + 1) != 0) return (1); if (ch & (1 << i)) sl->data[si->dx] = si->dc; sl++; } return (0); } static const char * sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end) { const char *last; char *endptr; u_int x, y; last = cp; while (last != end) { if (*last != ';' && (*last < '0' || *last > '9')) break; last++; } strtoul(cp, &endptr, 10); if (endptr == last || *endptr != ';') return (last); strtoul(endptr + 1, &endptr, 10); if (endptr == last) return (last); if (*endptr != ';') { log_debug("%s: missing ;", __func__); return (NULL); } x = strtoul(endptr + 1, &endptr, 10); if (endptr == last || *endptr != ';') { log_debug("%s: missing ;", __func__); return (NULL); } if (x > SIXEL_WIDTH_LIMIT) { log_debug("%s: image is too wide", __func__); return (NULL); } y = strtoul(endptr + 1, &endptr, 10); if (endptr != last) { log_debug("%s: extra ;", __func__); return (NULL); } if (y > SIXEL_HEIGHT_LIMIT) { log_debug("%s: image is too tall", __func__); return (NULL); } si->x = x; sixel_parse_expand_lines(si, y); si->set_ra = 1; si->ra_x = x; si->ra_y = y; return (last); } static const char * sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end) { const char *last; char *endptr; u_int c, type, r, g, b; last = cp; while (last != end) { if (*last != ';' && (*last < '0' || *last > '9')) break; last++; } c = strtoul(cp, &endptr, 10); if (c > SIXEL_COLOUR_REGISTERS) { log_debug("%s: too many colours", __func__); return (NULL); } si->dc = c + 1; if (endptr == last || *endptr != ';') return (last); type = strtoul(endptr + 1, &endptr, 10); if (endptr == last || *endptr != ';') { log_debug("%s: missing ;", __func__); return (NULL); } r = strtoul(endptr + 1, &endptr, 10); if (endptr == last || *endptr != ';') { log_debug("%s: missing ;", __func__); return (NULL); } g = strtoul(endptr + 1, &endptr, 10); if (endptr == last || *endptr != ';') { log_debug("%s: missing ;", __func__); return (NULL); } b = strtoul(endptr + 1, &endptr, 10); if (endptr != last) { log_debug("%s: missing ;", __func__); return (NULL); } if (type != 1 && type != 2) { log_debug("%s: invalid type %d", __func__, type); return (NULL); } if (c + 1 > si->ncolours) { si->colours = xrecallocarray(si->colours, si->ncolours, c + 1, sizeof *si->colours); si->ncolours = c + 1; } si->colours[c] = (type << 24) | (r << 16) | (g << 8) | b; return (last); } static const char * sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end) { const char *last; char tmp[32], ch; u_int n = 0, i; const char *errstr = NULL; last = cp; while (last != end) { if (*last < '0' || *last > '9') break; tmp[n++] = *last++; if (n == (sizeof tmp) - 1) { log_debug("%s: repeat not terminated", __func__); return (NULL); } } if (n == 0 || last == end) { log_debug("%s: repeat not terminated", __func__); return (NULL); } tmp[n] = '\0'; n = strtonum(tmp, 1, SIXEL_WIDTH_LIMIT, &errstr); if (n == 0 || errstr != NULL) { log_debug("%s: repeat too wide", __func__); return (NULL); } ch = (*last++) - 0x3f; for (i = 0; i < n; i++) { if (sixel_parse_write(si, ch) != 0) { log_debug("%s: width limit reached", __func__); return (NULL); } si->dx++; } return (last); } struct sixel_image * sixel_parse(const char *buf, size_t len, u_int p2, u_int xpixel, u_int ypixel) { struct sixel_image *si; const char *cp = buf, *end = buf + len; char ch; if (len == 0 || len == 1 || *cp++ != 'q') { log_debug("%s: empty image", __func__); return (NULL); } si = xcalloc (1, sizeof *si); si->xpixel = xpixel; si->ypixel = ypixel; si->p2 = p2; while (cp != end) { ch = *cp++; switch (ch) { case '"': cp = sixel_parse_attributes(si, cp, end); if (cp == NULL) goto bad; break; case '#': cp = sixel_parse_colour(si, cp, end); if (cp == NULL) goto bad; break; case '!': cp = sixel_parse_repeat(si, cp, end); if (cp == NULL) goto bad; break; case '-': si->dx = 0; si->dy += 6; break; case '$': si->dx = 0; break; default: if (ch < 0x20) break; if (ch < 0x3f || ch > 0x7e) goto bad; if (sixel_parse_write(si, ch - 0x3f) != 0) { log_debug("%s: width limit reached", __func__); goto bad; } si->dx++; break; } } if (si->x == 0 || si->y == 0) goto bad; return (si); bad: free(si); return (NULL); } void sixel_free(struct sixel_image *si) { u_int y; for (y = 0; y < si->y; y++) free(si->lines[y].data); free(si->lines); free(si->colours); free(si); } void sixel_log(struct sixel_image *si) { struct sixel_line *sl; char s[SIXEL_WIDTH_LIMIT + 1]; u_int i, x, y, cx, cy; sixel_size_in_cells(si, &cx, &cy); log_debug("%s: image %ux%u (%ux%u)", __func__, si->x, si->y, cx, cy); for (i = 0; i < si->ncolours; i++) log_debug("%s: colour %u is %07x", __func__, i, si->colours[i]); for (y = 0; y < si->y; y++) { sl = &si->lines[y]; for (x = 0; x < si->x; x++) { if (x >= sl->x) s[x] = '_'; else if (sl->data[x] != 0) s[x] = '0' + (sl->data[x] - 1) % 10; else s[x] = '.'; } s[x] = '\0'; log_debug("%s: %4u: %s", __func__, y, s); } } void sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y) { if ((si->x % si->xpixel) == 0) *x = (si->x / si->xpixel); else *x = 1 + (si->x / si->xpixel); if ((si->y % si->ypixel) == 0) *y = (si->y / si->ypixel); else *y = 1 + (si->y / si->ypixel); } 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) { struct sixel_image *new; u_int cx, cy, pox, poy, psx, psy, tsx, tsy, px, py; u_int x, y, i; /* * We want to get the section of the image at ox,oy in image cells and * map it onto the same size in terminal cells, remembering that we * can only draw vertical sections of six pixels. */ sixel_size_in_cells(si, &cx, &cy); if (ox >= cx) return (NULL); if (oy >= cy) return (NULL); if (ox + sx >= cx) sx = cx - ox; if (oy + sy >= cy) sy = cy - oy; if (xpixel == 0) xpixel = si->xpixel; if (ypixel == 0) ypixel = si->ypixel; pox = ox * si->xpixel; poy = oy * si->ypixel; psx = sx * si->xpixel; psy = sy * si->ypixel; tsx = sx * xpixel; tsy = ((sy * ypixel) / 6) * 6; new = xcalloc (1, sizeof *si); new->xpixel = xpixel; new->ypixel = ypixel; new->p2 = si->p2; new->set_ra = si->set_ra; /* clamp to slice end */ new->ra_x = si->ra_x < psx ? si->ra_x : psx; new->ra_y = si->ra_y < psy ? si->ra_y : psy; /* subtract slice origin */ new->ra_x = new->ra_x > pox ? new->ra_x - pox : 0; new->ra_y = new->ra_y > poy ? new->ra_y - poy : 0; /* resize */ new->ra_x = new->ra_x * xpixel / si->xpixel; new->ra_y = new->ra_y * ypixel / si->ypixel; for (y = 0; y < tsy; y++) { py = poy + ((double)y * psy / tsy); for (x = 0; x < tsx; x++) { px = pox + ((double)x * psx / tsx); sixel_set_pixel(new, x, y, sixel_get_pixel(si, px, py)); } } if (colours) { new->colours = xmalloc(si->ncolours * sizeof *new->colours); for (i = 0; i < si->ncolours; i++) new->colours[i] = si->colours[i]; new->ncolours = si->ncolours; } return (new); } static void sixel_print_add(char **buf, size_t *len, size_t *used, const char *s, size_t slen) { if (*used + slen >= *len + 1) { (*len) *= 2; *buf = xrealloc(*buf, *len); } memcpy(*buf + *used, s, slen); (*used) += slen; } static void sixel_print_repeat(char **buf, size_t *len, size_t *used, u_int count, char ch) { char tmp[16]; size_t tmplen; if (count == 1) sixel_print_add(buf, len, used, &ch, 1); else if (count == 2) { sixel_print_add(buf, len, used, &ch, 1); sixel_print_add(buf, len, used, &ch, 1); } else if (count == 3) { sixel_print_add(buf, len, used, &ch, 1); sixel_print_add(buf, len, used, &ch, 1); sixel_print_add(buf, len, used, &ch, 1); } else if (count != 0) { tmplen = xsnprintf(tmp, sizeof tmp, "!%u%c", count, ch); sixel_print_add(buf, len, used, tmp, tmplen); } } char * sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size) { char *buf, tmp[64], *contains, data, last = 0; size_t len, used = 0, tmplen; u_int *colours, ncolours, i, c, x, y, count; struct sixel_line *sl; if (map != NULL) { colours = map->colours; ncolours = map->ncolours; } else { colours = si->colours; ncolours = si->ncolours; } if (ncolours == 0) return (NULL); contains = xcalloc(1, ncolours); len = 8192; buf = xmalloc(len); tmplen = xsnprintf(tmp, sizeof tmp, "\033P0;%uq", si->p2); sixel_print_add(&buf, &len, &used, tmp, tmplen); if (si->set_ra) { tmplen = xsnprintf(tmp, sizeof tmp, "\"1;1;%u;%u", si->ra_x, si->ra_y); sixel_print_add(&buf, &len, &used, tmp, tmplen); } for (i = 0; i < ncolours; i++) { c = colours[i]; tmplen = xsnprintf(tmp, sizeof tmp, "#%u;%u;%u;%u;%u", i, c >> 24, (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff); sixel_print_add(&buf, &len, &used, tmp, tmplen); } for (y = 0; y < si->y; y += 6) { memset(contains, 0, ncolours); for (x = 0; x < si->x; x++) { for (i = 0; i < 6; i++) { if (y + i >= si->y) break; sl = &si->lines[y + i]; if (x < sl->x && sl->data[x] != 0) contains[sl->data[x] - 1] = 1; } } for (c = 0; c < ncolours; c++) { if (!contains[c]) continue; tmplen = xsnprintf(tmp, sizeof tmp, "#%u", c); sixel_print_add(&buf, &len, &used, tmp, tmplen); count = 0; for (x = 0; x < si->x; x++) { data = 0; for (i = 0; i < 6; i++) { if (y + i >= si->y) break; sl = &si->lines[y + i]; if (x < sl->x && sl->data[x] == c + 1) data |= (1 << i); } data += 0x3f; if (data != last) { sixel_print_repeat(&buf, &len, &used, count, last); last = data; count = 1; } else count++; } sixel_print_repeat(&buf, &len, &used, count, data); sixel_print_add(&buf, &len, &used, "$", 1); } if (buf[used - 1] == '$') used--; sixel_print_add(&buf, &len, &used, "-", 1); } if (buf[used - 1] == '$' || buf[used - 1] == '-') used--; sixel_print_add(&buf, &len, &used, "\033\\", 2); buf[used] = '\0'; if (size != NULL) *size = used; free(contains); return (buf); } struct screen * sixel_to_screen(struct sixel_image *si) { struct screen *s; struct screen_write_ctx ctx; struct grid_cell gc; u_int x, y, sx, sy; sixel_size_in_cells(si, &sx, &sy); s = xmalloc(sizeof *s); screen_init(s, sx, sy, 0); memcpy(&gc, &grid_default_cell, sizeof gc); gc.attr |= (GRID_ATTR_CHARSET|GRID_ATTR_DIM); utf8_set(&gc.data, '~'); screen_write_start(&ctx, s); if (sx == 1 || sy == 1) { for (y = 0; y < sy; y++) { for (x = 0; x < sx; x++) grid_view_set_cell(s->grid, x, y, &gc); } } else { screen_write_box(&ctx, sx, sy, BOX_LINES_DEFAULT, NULL, NULL); for (y = 1; y < sy - 1; y++) { for (x = 1; x < sx - 1; x++) grid_view_set_cell(s->grid, x, y, &gc); } } screen_write_stop(&ctx); return (s); }