Deepclean sixel on move (#1567)

Normally, we don't bother to edit Sixels in-place upon annihilation, since you can just print the character over them and be done with it. If we move one, however, we need to cut the annihilated pixels out of the Sixel. Failure to do so was leading to flicker around the base of the orca's Segway in the intro demo. This was a bit of a struggle, but it seems to work beautifully (though a bit slower than I would care for -- we'll do something about that). Closes #1555.
This commit is contained in:
Nick Black 2021-04-20 05:23:56 -04:00 committed by GitHub
parent c05b0683ec
commit 05662e54c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 210 additions and 4 deletions

View File

@ -19,6 +19,7 @@ relies on the font. Patches to correct/complete this table are very welcome!
| --------------- | ------------------ | ----- | ------ | ----------------------- | ----- |
| [Alacritty](https://github.com/alacritty/alacritty) | ✅ | ✅ |❌ |`TERM=alacritty` `COLORTERM=24bit` | [Sixel support WIP](https://github.com/ayosec/alacritty/tree/graphics) |
| [Contour](https://github.com/christianparpart/contour) | ❌ | ✅ |? |`TERM=contour-latest` ? | Claims Sixel support |
| [ETerm](https://github.com/mej/Eterm) | | | | TERM="Eterm" | Doesn't reply to Send Device Attributes |
| [FBterm](https://github.com/zhangyuanwei/fbterm) | ❌ | ? |? |`TERM=fbterm` | 256 colors, no RGB color. |
| [foot](https://codeberg.org/dnkl/foot) | ✅ | ✅ |✅ |`TERM=foot` | Sixel support. |
| [Gnome Terminal](https://gitlab.gnome.org/GNOME/gnome-terminal) | | ❌ |✅ |`TERM=gnome` `COLORTERM=24bit` | `ccc` support *is* available when run with `vte-256color`. |

View File

@ -514,10 +514,16 @@ ncdirect_render_visual(ncdirect* n, ncvisual* ncv, ncblitter_e blitfxn,
}
if(scale == NCSCALE_SCALE || scale == NCSCALE_SCALE_HIRES){
scale_visual(ncv, &disprows, &dispcols);
if(bset->geom == NCBLIT_PIXEL){
clamp_to_sixelmax(&n->tcache, &disprows, &dispcols);
}
}
}else{
disprows = ncv->rows;
dispcols = ncv->cols;
if(bset->geom == NCBLIT_PIXEL){
clamp_to_sixelmax(&n->tcache, &disprows, &dispcols);
}
}
leny = (leny / (double)ncv->rows) * ((double)disprows);
lenx = (lenx / (double)ncv->cols) * ((double)dispcols);

View File

@ -158,11 +158,13 @@ typedef struct sprixel {
int y, x;
int dimy, dimx; // cell geometry
int pixy, pixx; // pixel geometry (might be smaller than cell geo)
int cellpxy, cellpxx; // cell-pixel geometry at time of creation
// each tacache entry is one of 0 (standard opaque cell), 1 (cell with
// some transparency), 2 (annihilated, excised)
int parse_start; // where to start parsing for cell wipes
int movedfromy; // for SPRIXEL_MOVED, the starting absolute position,
int movedfromx; // so that we can damage old cells when redrawn
bool wipes_outstanding; // do we need execute wipes on move?
} sprixel;
// A plane is memory for some rectilinear virtual window, plus current cursor

View File

@ -445,7 +445,10 @@ sixel_blit_inner(int leny, int lenx, const sixeltable* stab, int rows, int cols,
int sixel_blit(ncplane* n, int linesize, const void* data,
int leny, int lenx, const blitterargs* bargs){
int sixelcount = (lenx - bargs->begx) * ((leny - bargs->begy + 5) / 6);
if((leny - bargs->begy) % 6){
return -1;
}
int sixelcount = (lenx - bargs->begx) * (leny - bargs->begy) / 6;
int colorregs = bargs->u.pixel.colorregs;
if(colorregs <= 0){
return -1;
@ -524,6 +527,181 @@ int sixel_delete(const notcurses* nc, const ncpile* p, FILE* out, sprixel* s){
return 0;
}
// offered 'rle' instances of 'c', up through 'y'x'x' (pixel coordinates)
// within 's', determine if any of these bytes need be elided due to recent
// annihilation. note that annihilation could have taken place anywhere along
// the continuous 'rle' instances. see deepclean_stream() regarding trusting
// our input, and keep your CVEs to yourselves. remember that this covers six
// rows at a time, [y..y + 5].
static int
deepclean_output(FILE* fp, const sprixel* s, int y, int *x, int* rle,
int* printed, int color, int* needclosure, char c){
c -= 63;
int rlei = 0;
// xi loops over the section we cover, a minimum of 1 pixel and a maximum
// of one line. FIXME can skip (celldimx - 1) / celldimx checks, do so!
for(int xi = *x ; xi < *x + *rle ; ++xi){
unsigned char mask = 0x3f; // assume all six bits are valid
for(int yi = y ; yi < y + 6 ; ++yi){
const int tidx = (yi / s->cellpxy) * s->dimx + (xi / s->cellpxx);
const bool nihil = (s->n->tacache[tidx] == SPRIXCELL_ANNIHILATED);
if(nihil){
mask &= ~(1u << (yi - y));
}
}
if((c & mask) != c){
if(rlei){
if(write_rle(printed, color, fp, rlei, c & mask, needclosure)){
return -1;
}
rlei = 0;
}
}else{
++rlei;
}
}
*x += *rle;
if(rlei){
if(write_rle(printed, color, fp, rlei, c, needclosure)){
return -1;
}
rlei = 0;
}
*rle = 1;
return 0;
}
// we should have already copied everything up through parse_start. we now
// read from the old sixel, copying through whatever we find, unless it's been
// obliterated by a SPRIXCELL_ANNIHILATED. this is *not* suitable as a general
// sixel lexer, but it works for all sixels we generate. we explicitly do not
// protect against e.g. overflow of the color/RLE specs, or an RLE that takes
// us past data boundaries, because we're not taking random external data. i'm
// sure this will one day bite me in my ass and lead to president celine dion.
static int
deepclean_stream(sprixel* s, FILE* fp){
int idx = s->parse_start;
enum {
SIXEL_WANT_HASH, // we ought get a '#' or '-'
SIXEL_EAT_COLOR, // we're reading the color until we hit data
SIXEL_EAT_COLOR_EPSILON, // actually process color
SIXEL_EAT_RLE, // we're reading the repetition count
SIXEL_EAT_RLE_EPSILON, // actually process rle
SIXEL_EAT_DATA // we're reading data until we hit EOL
} state = SIXEL_WANT_HASH;
int color = 0;
int rle = 1;
int y = 0;
int x = 0;
int printed;
int needclosure = 0;
while(idx + 2 < s->glyphlen){
const char c = s->glyph[idx];
//fprintf(stderr, "%d] %c (%d) (%d/%d)\n", state, c, c, idx, s->glyphlen);
if(state == SIXEL_WANT_HASH){
if(c == '#'){
state = SIXEL_EAT_COLOR;
color = 0;
printed = 0;
}else if(c == '-'){
y += 6;
x = 0;
}else{
return -1;
}
}
// we require an actual digit where a color or an RLE is expected,
// so verify that the first char in the state is indeed a digit,
// and fall through to epsilon states below to actually process it.
else if(state == SIXEL_EAT_COLOR){
if(isdigit(c)){
state = SIXEL_EAT_COLOR_EPSILON;
}else{
return -1;
}
}else if(state == SIXEL_EAT_RLE){
if(isdigit(c)){
state = SIXEL_EAT_RLE_EPSILON;
}else{
return -1;
}
}
// handle the color/rle digits, with implicit fallthrough from
// the EAT_COLOR/EAT_RLE states above.
if(state == SIXEL_EAT_COLOR_EPSILON){
if(isdigit(c)){
color *= 10;
color += c - '0';
}else{
state = SIXEL_EAT_DATA;
rle = 1;
}
}else if(state == SIXEL_EAT_RLE_EPSILON){
if(isdigit(c)){
rle *= 10;
rle += c - '0';
}else{
state = SIXEL_EAT_DATA;
}
}
if(state == SIXEL_EAT_DATA){
if(c == '!'){
state = SIXEL_EAT_RLE;
rle = 0;
}else if(c == '-'){
y += 6;
x = 0;
state = SIXEL_WANT_HASH;
needclosure = needclosure | printed;
fputc('-', fp);
}else if(c == '$'){
x = 0;
state = SIXEL_WANT_HASH;
needclosure = needclosure | printed;
}else if(c < 63 || c > 126){
return -1;
}else{ // data byte
if(deepclean_output(fp, s, y, &x, &rle, &printed, color,
&needclosure, c)){
return -1;
}
}
}
++idx;
}
fprintf(fp, "\e\\");
return 0;
}
static int
sixel_deepclean(sprixel* s){
char* buf = NULL;
size_t size = 0;
FILE* fp = open_memstream(&buf, &size);
if(fwrite(s->glyph, 1, s->parse_start, fp) != (size_t)s->parse_start){
goto err;
}
if(deepclean_stream(s, fp)){
goto err;
}
if(fclose(fp) == EOF){
free(buf);
return -1;
}
free(s->glyph);
s->glyph = buf;
//fprintf(stderr, "Deepclean! %d -> %zu\n", s->glyphlen, size);
s->glyphlen = size;
return 0;
err:
fclose(fp);
free(buf);
return -1;
}
int sixel_draw(const notcurses* n, const ncpile* p, sprixel* s, FILE* out){
(void)n;
if(s->invalidated == SPRIXEL_MOVED){
@ -535,6 +713,14 @@ int sixel_draw(const notcurses* n, const ncpile* p, sprixel* s, FILE* out){
}
}
}
// if we've wiped any cells, we need actually wipe them out now, or else
// we'll get flicker when we move to the new location
if(s->wipes_outstanding){
if(sixel_deepclean(s)){
return -1;
}
s->wipes_outstanding = false;
}
s->invalidated = SPRIXEL_INVALIDATED;
}else{
if(fwrite(s->glyph, s->glyphlen, 1, out) != 1){
@ -561,6 +747,7 @@ int sixel_wipe(const notcurses* nc, sprixel* s, int ycell, int xcell){
//fprintf(stderr, "CACHED WIPE %d %d/%d\n", s->id, ycell, xcell);
return 1; // already annihilated FIXME but 0 breaks things
}
s->wipes_outstanding = true;
change_p2(s->glyph, SIXEL_P2_TRANS);
return -1;
}

View File

@ -109,12 +109,18 @@ sprixel* sprixel_alloc(ncplane* n, int dimy, int dimx, int placey, int placex){
ret->y = placey;
ret->x = placex;
ret->id = ++sprixelid_nonce;
ret->wipes_outstanding = false;
//fprintf(stderr, "LOOKING AT %p (p->n = %p)\n", ret, ret->n);
if(ncplane_pile(ret->n)){
notcurses* nc = ncplane_notcurses(ret->n);
ret->next = nc->sprixelcache;
nc->sprixelcache = ret;
ret->cellpxy = nc->tcache.cellpixy;
ret->cellpxx = nc->tcache.cellpixx;
//fprintf(stderr, "%p %p %p\n", nc->sprixelcache, ret, nc->sprixelcache->next);
}else{
ret->next = NULL;
ret->cellpxy = ret->cellpxx = -1;
}
}
return ret;

View File

@ -351,6 +351,7 @@ setup_sixel(tinfo* ti){
// query for Sixel support
static int
query_sixel(tinfo* ti, int fd){
// Send Device Attributes (see decTerminalID resource)
if(writen(fd, "\x1b[c", 3) != 3){
return -1;
}

View File

@ -633,6 +633,8 @@ ncplane* ncvisual_render_pixels(notcurses* nc, ncvisual* ncv, const struct blits
clamp_to_sixelmax(&nc->tcache, &disprows, &dispcols);
if(scaling == NCSCALE_SCALE || scaling == NCSCALE_SCALE_HIRES){
scale_visual(ncv, &disprows, &dispcols); // can only shrink
// FIXME can't we keep this limited to one clamp-to-6 max?
clamp_to_sixelmax(&nc->tcache, &disprows, &dispcols);
}
}
struct ncplane_options nopts = {
@ -671,9 +673,10 @@ ncplane* ncvisual_render_pixels(notcurses* nc, ncvisual* ncv, const struct blits
disprows -= placey * nc->tcache.cellpixy;
}
}
}
if(scaling == NCSCALE_SCALE || scaling == NCSCALE_SCALE_HIRES){
scale_visual(ncv, &disprows, &dispcols);
if(scaling == NCSCALE_SCALE || scaling == NCSCALE_SCALE_HIRES){
scale_visual(ncv, &disprows, &dispcols);
clamp_to_sixelmax(&nc->tcache, &disprows, &dispcols);
}
}
if(flags & NCVISUAL_OPTION_HORALIGNED){
if(placex == NCALIGN_CENTER){