foot/sixel.c
Daniel Eklöf f0fc82f098
sixel: wip: maintain a list of finished, and "active" sixel images
In unhook, add the generated image to a list of finished sixel images,
along with positioning and size information.

When rendering, loop this list of images, and render the images (or
parts of) that are visible.

When scrolling, check if any part of the images cover the re-cycled
lines, and if so, remove the *entire* image from the list.

This means we have the following limitations:

* The renderer always renders the whole (visible area of) the
  image(s). There are times when this isn't necessary - for example,
  when the image is scrolled inside the visible area.
* It would be nice if we could crop the image when parts of it is
  scrolled out.
2020-02-21 23:40:35 +01:00

280 lines
7.7 KiB
C

#include "sixel.h"
#include <string.h>
#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)
{
assert(term->sixel.palette == NULL);
assert(term->sixel.image == NULL);
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;
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;
}
struct sixel image = {
.data = term->sixel.image,
.width = term->sixel.max_col,
.height = term->sixel.row * 6,
.pos = (struct coord){term->cursor.point.col, term->grid->offset + term->cursor.point.row},
};
image.pix = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8,
image.width, image.height,
term->sixel.image,
IMAGE_WIDTH * sizeof(uint32_t));
tll_push_back(term->sixel_images, image);
term->sixel.image = NULL;
term->sixel.max_col = 0;
term->sixel.col = 0;
term->sixel.row = 0;
const size_t lines = (image.height + term->cell_height - 1) / term->cell_height;
for (size_t i = 0; i < lines; i++)
term_linefeed(term);
term_formfeed(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] = term->colors.alpha / 256 << 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;
}
}