diff options
Diffstat (limited to 'image-sixel.c')
-rw-r--r-- | image-sixel.c | 600 |
1 files changed, 600 insertions, 0 deletions
diff --git a/image-sixel.c b/image-sixel.c new file mode 100644 index 0000000..3396a22 --- /dev/null +++ b/image-sixel.c @@ -0,0 +1,600 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com> + * + * 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 <sys/types.h> + +#include <stdlib.h> +#include <string.h> + +#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 *colours; + u_int ncolours; + + 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); + + 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 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; + + 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; + + 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; + } + contains = xcalloc(1, ncolours); + + len = 8192; + buf = xmalloc(len); + + sixel_print_add(&buf, &len, &used, "\033Pq", 3); + + tmplen = xsnprintf(tmp, sizeof tmp, "\"1;1;%u;%u", si->x, si->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--; + if (buf[used - 1] != '-') + 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); +} |