mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-08 10:06:22 -05:00
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
This commit is contained in:
parent
e8197d22f7
commit
9e3bfb1eab
6 changed files with 350 additions and 3 deletions
23
dcs.c
23
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;
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
21
render.c
21
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 */
|
||||
|
|
|
|||
284
sixel.c
Normal file
284
sixel.c
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
#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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
7
sixel.h
Normal file
7
sixel.h
Normal file
|
|
@ -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);
|
||||
17
terminal.h
17
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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue