From 9e3bfb1eab2c7b1c7a1ec6021331fddc57e8a266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 21 Feb 2020 21:53:23 +0100 Subject: [PATCH] sixel: initial support This implements basic parsing of sixel data. Lots of limitations and temporary solutions as this is still work-in-progress: * Maximum image size hardcoded to 800x800 * No HLS color format support * Image is always rendered at 0x0 in the terminal --- dcs.c | 23 ++++- meson.build | 1 + render.c | 21 ++++ sixel.c | 284 ++++++++++++++++++++++++++++++++++++++++++++++++++++ sixel.h | 7 ++ terminal.h | 17 ++++ 6 files changed, 350 insertions(+), 3 deletions(-) create mode 100644 sixel.c create mode 100644 sixel.h diff --git a/dcs.c b/dcs.c index 048cd62e..f9e3c10c 100644 --- a/dcs.c +++ b/dcs.c @@ -3,6 +3,7 @@ #define LOG_MODULE "dcs" #define LOG_ENABLE_DBG 0 #include "log.h" +#include "sixel.h" #include "vt.h" static void @@ -35,9 +36,20 @@ dcs_hook(struct terminal *term, uint8_t final) assert(term->vt.dcs.data == NULL); assert(term->vt.dcs.size == 0); + assert(term->vt.dcs.put_handler == NULL); assert(term->vt.dcs.unhook_handler == NULL); switch (term->vt.private[0]) { + case 0: + switch (final) { + case 'q': + sixel_init(term); + term->vt.dcs.put_handler = &sixel_put; + term->vt.dcs.unhook_handler = &sixel_unhook; + break; + } + break; + case '=': switch (final) { case 's': @@ -75,9 +87,13 @@ void dcs_put(struct terminal *term, uint8_t c) { LOG_DBG("PUT: %c", c); - if (!ensure_size(term, term->vt.dcs.idx + 1)) - return; - term->vt.dcs.data[term->vt.dcs.idx++] = c; + if (term->vt.dcs.put_handler != NULL) + term->vt.dcs.put_handler(term, c); + else { + if (!ensure_size(term, term->vt.dcs.idx + 1)) + return; + term->vt.dcs.data[term->vt.dcs.idx++] = c; + } } void @@ -87,6 +103,7 @@ dcs_unhook(struct terminal *term) term->vt.dcs.unhook_handler(term); term->vt.dcs.unhook_handler = NULL; + term->vt.dcs.put_handler = NULL; free(term->vt.dcs.data); term->vt.dcs.data = NULL; diff --git a/meson.build b/meson.build index 2da5c65f..16483f51 100644 --- a/meson.build +++ b/meson.build @@ -117,6 +117,7 @@ executable( 'selection.c', 'selection.h', 'server.c', 'server.h', 'shm.c', 'shm.h', + 'sixel.c', 'sixel.h', 'slave.c', 'slave.h', 'terminal.c', 'terminal.h', 'tokenize.c', 'tokenize.h', diff --git a/render.c b/render.c index bb229288..a88aba17 100644 --- a/render.c +++ b/render.c @@ -816,6 +816,27 @@ grid_render(struct terminal *term) cols_updated * term->cell_width, term->cell_height); } + if (term->sixel.pix != NULL) { + pixman_image_composite( + PIXMAN_OP_SRC, + term->sixel.pix, + NULL, + pix, + 0, 0, + 0, 0, + 0, 0, + term->sixel.max_col, + term->sixel.row * 6); + wl_surface_damage_buffer( + term->window->surface, + 0, 0, term->sixel.max_col, term->sixel.row * 6); + + pixman_image_unref(term->sixel.pix); + free(term->sixel.image); + term->sixel.pix = NULL; + term->sixel.image = NULL; + } + if (term->flash.active) { /* Note: alpha is pre-computed in each color component */ /* TODO: dim while searching */ diff --git a/sixel.c b/sixel.c new file mode 100644 index 00000000..2ca783e5 --- /dev/null +++ b/sixel.c @@ -0,0 +1,284 @@ +#include "sixel.h" + +#include + +#define LOG_MODULE "sixel" +#define LOG_ENABLE_DBG 0 +#include "log.h" +#include "render.h" + +#define max(x, y) ((x) > (y) ? (x) : (y)) + +static const size_t COLOR_COUNT = 1024; +static const size_t IMAGE_WIDTH = 800; +static const size_t IMAGE_HEIGHT = 800; + +static size_t count; + +void +sixel_init(struct terminal *term) +{ + if (term->sixel.pix != NULL) { + pixman_image_unref(term->sixel.pix); + free(term->sixel.image); + term->sixel.pix = NULL; + term->sixel.image = NULL; + } + + assert(term->sixel.palette == NULL); + assert(term->sixel.image == NULL); + + if (term->sixel.image != NULL) { + if (term->sixel.pix != NULL) { + pixman_image_unref(term->sixel.pix); + term->sixel.pix = NULL; + } + free(term->sixel.image); + } + + term->sixel.state = SIXEL_SIXEL; + term->sixel.row = 0; + term->sixel.col = 0; + term->sixel.color_idx = 0; + term->sixel.max_col = 0; + term->sixel.param = 0; + term->sixel.param_idx = 0; + memset(term->sixel.params, 0, sizeof(term->sixel.params)); + term->sixel.palette = calloc(COLOR_COUNT, sizeof(term->sixel.palette[0])); + term->sixel.image = calloc(IMAGE_WIDTH * IMAGE_HEIGHT, sizeof(term->sixel.image[0])); + + count = 0; + + /* TODO: default palette */ +} + +void +sixel_unhook(struct terminal *term) +{ + free(term->sixel.palette); + term->sixel.palette = NULL; + + if (term->sixel.pix != NULL) + pixman_image_unref(term->sixel.pix); + + LOG_DBG("generating %dx%d pixman image", term->sixel.row * 6, term->sixel.max_col); + + if (term->sixel.col > 0) { + if (term->sixel.col > term->sixel.max_col) + term->sixel.max_col = term->sixel.col; + term->sixel.row++; + term->sixel.col = 0; + } + + term->sixel.pix = pixman_image_create_bits_no_clear( + PIXMAN_a8r8g8b8, + term->sixel.max_col, + term->sixel.row * 6, + term->sixel.image, + IMAGE_WIDTH * sizeof(uint32_t)); + + size_t lines = max(1, term->sixel.row * 6 / term->cell_height); + for (size_t i = 0; i < lines; i++) + term_linefeed(term); + render_refresh(term); +} + +static void +sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) +{ + LOG_DBG("adding sixel %02hhx using color 0x%06x", sixel, color); + if (term->sixel.col >= IMAGE_WIDTH) { + LOG_WARN("column outside image width"); + return; + } + if (term->sixel.row >= IMAGE_HEIGHT) { + LOG_WARN("row outside image height"); + return; + } + + for (int i = 0; i < 6; i++) { + int bit = sixel & 1; + sixel >>= 1; + if (bit) { + size_t idx = (term->sixel.row * 6 + i) * IMAGE_WIDTH + term->sixel.col; + term->sixel.image[idx] = 0x00 << 24 | color; + } + } + + assert(sixel == 0); + + term->sixel.col++; +} + +static void +sixel_sixel(struct terminal *term, uint8_t c) +{ + switch (c) { + case '"': + term->sixel.state = SIXEL_RASTER; + term->sixel.param = 0; + term->sixel.param_idx = 0; + break; + + case '#': + term->sixel.state = SIXEL_COLOR; + term->sixel.color_idx = 0; + term->sixel.param = 0; + term->sixel.param_idx = 0; + break; + + case '$': + if (term->sixel.col > term->sixel.max_col) + term->sixel.max_col = term->sixel.col; + term->sixel.col = 0; + break; + + case '-': + if (term->sixel.col > term->sixel.max_col) + term->sixel.max_col = term->sixel.col; + term->sixel.row++; + term->sixel.col = 0; + break; + + case '!': + term->sixel.state = SIXEL_REPEAT; + term->sixel.param = 0; + term->sixel.param_idx = 0; + break; + + default: + if (c < '?' || c > '~') { + LOG_ERR("invalid sixel charactwer: '%c' at idx=%zu", c, count); + return; + } + + sixel_add(term, term->sixel.palette[term->sixel.color_idx], c - 63); + break; + } +} + +static void +sixel_repeat(struct terminal *term, uint8_t c) +{ + if (c >= '0' && c <= '9') { + term->sixel.param *= 10; + term->sixel.param += c - '0'; + } else { + LOG_DBG("repeating '%c' %u times", c, term->sixel.param); + term->sixel.state = SIXEL_SIXEL; + for (unsigned i = 0; i < term->sixel.param; i++) + sixel_sixel(term, c); + } +} + +static void +sixel_raster(struct terminal *term, uint8_t c) +{ + if (c >= '0' && c <= '9') { + term->sixel.param *= 10; + term->sixel.param += c - '0'; + } else { + if (term->sixel.param_idx < sizeof(term->sixel.params) / sizeof(term->sixel.params[0])) + term->sixel.params[term->sixel.param_idx++] = term->sixel.param; + + term->sixel.param = 0; + + if (c != ';') { + unsigned pan __attribute__((unused)) = term->sixel.params[0]; + unsigned pad __attribute__((unused)) = term->sixel.params[1]; + unsigned ph __attribute__((unused)) = term->sixel.params[2]; + unsigned pv __attribute__((unused)) = term->sixel.params[3]; + + LOG_DBG("pan=%u, pad=%u (aspect ratio = %u), size=%ux%u", + pan, pad, pan / pad, ph, pv); + } + + switch (c) { + case '#': term->sixel.state = SIXEL_COLOR; break; + case ';': term->sixel.state = SIXEL_RASTER; break; + default: term->sixel.state = SIXEL_SIXEL; sixel_sixel(term, c); break; + } + } +} + +static void +sixel_color(struct terminal *term, uint8_t c) +{ + if (c >= '0' && c <= '9') { + term->sixel.param *= 10; + term->sixel.param += c - '0'; + } else { + if (term->sixel.param < COLOR_COUNT) + term->sixel.color_idx = term->sixel.param; + else + term->sixel.color_idx = 0; + + term->sixel.param_idx = 0; + term->sixel.param = 0; + + switch (c) { + case '#': term->sixel.state = SIXEL_COLOR; break; + case ';': term->sixel.state = SIXEL_COLOR_SPEC; break; + default: term->sixel.state = SIXEL_SIXEL; sixel_sixel(term, c); break; + } + } +} + +static void +sixel_color_spec(struct terminal *term, uint8_t c) +{ + if (c >= '0' && c <= '9') { + term->sixel.param *= 10; + term->sixel.param += c - '0'; + } else { + if (term->sixel.param_idx < sizeof(term->sixel.params) / sizeof(term->sixel.params[0])) + term->sixel.params[term->sixel.param_idx++] = term->sixel.param; + + term->sixel.param = 0; + + if (c != ';') { + unsigned format = term->sixel.params[0]; + unsigned c1 = term->sixel.params[1]; + unsigned c2 = term->sixel.params[2]; + unsigned c3 = term->sixel.params[3]; + + if (format == 1) { + assert(false && "HLS color format not implemented"); + } else { + uint8_t r = 255 * c1 / 100; + uint8_t g = 255 * c2 / 100; + uint8_t b = 255 * c3 / 100; + + LOG_DBG("setting palette #%d = RGB %hhu/%hhu/%hhu", + term->sixel.color_idx, r, g, b); + + term->sixel.palette[term->sixel.color_idx] = r << 16 | g << 8 | b; + } + } + + switch (c) { + case '#': term->sixel.state = SIXEL_COLOR; break; + case ';': term->sixel.state = SIXEL_COLOR_SPEC; break; + default: term->sixel.state = SIXEL_SIXEL; sixel_sixel(term, c); break; + } + } +} + +void +sixel_put(struct terminal *term, uint8_t c) +{ + count++; + switch (c) { + case ' ': return; + case '\n': return; + case '\r': return; + } + + switch (term->sixel.state) { + case SIXEL_SIXEL: sixel_sixel(term, c); break; + case SIXEL_REPEAT: sixel_repeat(term, c); break; + case SIXEL_RASTER: sixel_raster(term, c); break; + case SIXEL_COLOR: sixel_color(term, c); break; + case SIXEL_COLOR_SPEC: sixel_color_spec(term, c); break; + } +} diff --git a/sixel.h b/sixel.h new file mode 100644 index 00000000..0cb24bfe --- /dev/null +++ b/sixel.h @@ -0,0 +1,7 @@ +#pragma once + +#include "terminal.h" + +void sixel_init(struct terminal *term); +void sixel_put(struct terminal *term, uint8_t c); +void sixel_unhook(struct terminal *term); diff --git a/terminal.h b/terminal.h index d9727b32..756b0554 100644 --- a/terminal.h +++ b/terminal.h @@ -131,6 +131,7 @@ struct vt { uint8_t *data; size_t size; size_t idx; + void (*put_handler)(struct terminal *term, uint8_t c); void (*unhook_handler)(struct terminal *term); } dcs; struct attributes attrs; @@ -344,6 +345,22 @@ struct terminal { int upper_fd; } delayed_render_timer; + struct { + enum { SIXEL_SIXEL, SIXEL_REPEAT, SIXEL_RASTER, SIXEL_COLOR, SIXEL_COLOR_SPEC} state; + int row; + int col; + int color_idx; + int max_col; + unsigned params[4]; + uint32_t *palette; + uint32_t *image; + + unsigned int param; + unsigned param_idx; + + pixman_image_t *pix; + } sixel; + bool hold_at_exit; bool is_shutting_down; void (*shutdown_cb)(void *data, int exit_code);