| /* $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_COLOUR_REGISTERS 1024 |
| #define SIXEL_WIDTH_LIMIT 2016 |
| #define SIXEL_HEIGHT_LIMIT 2016 |
| |
| 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 || *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); |
| } |