summaryrefslogtreecommitdiffstats
path: root/image-sixel.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--image-sixel.c600
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);
+}