From ebc405b165fbdafcf5a55b672bcd8669a8c6bdeb Mon Sep 17 00:00:00 2001 From: nick black Date: Tue, 6 Jul 2021 22:53:57 -0400 Subject: [PATCH] [kitty] get most of animation working #1895 --- src/lib/internal.h | 4 ++- src/lib/kitty.c | 65 ++++++++++++++++++++++++++++++++++------------ src/lib/render.c | 24 +++++++++-------- src/lib/sprite.h | 6 +++++ src/lib/termdesc.c | 14 +++++++--- 5 files changed, 81 insertions(+), 32 deletions(-) diff --git a/src/lib/internal.h b/src/lib/internal.h index 010ebb4c1..4fbf00450 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -646,8 +646,8 @@ uint8_t* sprixel_auxiliary_vector(const sprixel* s); // asking whether it was sixel and there were no errors). static inline int sprite_scrub(const notcurses* n, const ncpile* p, sprixel* s){ -//fprintf(stderr, "Destroying sprite %u\n", s->id); //sprixel_debug(s, stderr); + logdebug("Sprixel %u state %d\n", s->id, s->invalidated); return n->tcache.pixel_scrub(p, s); } @@ -656,6 +656,7 @@ sprite_scrub(const notcurses* n, const ncpile* p, sprixel* s){ static inline int sprite_draw(const notcurses* n, const ncpile* p, sprixel* s, FILE* out){ //sprixel_debug(s, stderr); + logdebug("Sprixel %u state %d\n", s->id, s->invalidated); return n->tcache.pixel_draw(p, s, out); } @@ -664,6 +665,7 @@ sprite_draw(const notcurses* n, const ncpile* p, sprixel* s, FILE* out){ static inline int sprite_redraw(const notcurses* n, const ncpile* p, sprixel* s, FILE* out){ //sprixel_debug(s, stderr); + logdebug("Sprixel %u state %d\n", s->id, s->invalidated); if(s->invalidated == SPRIXEL_MOVED && n->tcache.pixel_move){ // if we are kitty prior to 0.20.0, C=1 isn't available to us, and we must // not emit it. we use sixel_maxy_pristine as a side channel to encode diff --git a/src/lib/kitty.c b/src/lib/kitty.c index cd484e2ad..9f3c37ca3 100644 --- a/src/lib/kitty.c +++ b/src/lib/kitty.c @@ -354,15 +354,18 @@ int kitty_rebuild(sprixel* s, int ycell, int xcell, uint8_t* auxvec){ } // an animation auxvec requires storing all the pixel data for the cell, -// instead of just the alpha channel. +// instead of just the alpha channel. pass the start of the RGBA to be +// copied, and the rowstride. static inline uint8_t* -kitty_transanim_auxvec(const sprixel* s){ - const size_t slen = 4 * s->cellpxy * s->cellpxx; +kitty_transanim_auxvec(int cellpxy, int cellpxx, const uint32_t* data, int rowstride){ + const size_t slen = 4 * cellpxy * cellpxx; uint8_t* a = malloc(slen); if(a){ - memset(a, 0, slen); + for(int y = 0 ; y < cellpxy ; ++y){ +fprintf(stderr, "COPYING %d from %p to %p\n", cellpxx * 4, data + y * (rowstride / 4), a + y * (cellpxx * 4)); + memcpy(a + y * (cellpxx * 4), data + y * (rowstride / 4), cellpxx * 4); + } } - // FIXME decode glyph into auxvec -- need to keep original glyph! return a; } @@ -374,10 +377,6 @@ int kitty_wipe_animation(sprixel* s, int ycell, int xcell){ return -1; } logdebug("Wiping sprixel %u at %d/%d\n", s->id, ycell, xcell); - uint8_t* auxvec = kitty_transanim_auxvec(s); - if(auxvec == NULL){ - return -1; - } FILE* fp = s->mstreamfp; fprintf(fp, "\e_Ga=f,x=%d,y=%d,s=%d,v=%d,i=%d,X=1,r=1,q=2;", xcell * s->cellpxx, @@ -397,7 +396,6 @@ int kitty_wipe_animation(sprixel* s, int ycell, int xcell){ } // FIXME need chunking for cells of 768+ pixels fprintf(fp, "\e\\"); - s->n->tam[s->dimx * ycell + xcell].auxvector = auxvec; s->invalidated = SPRIXEL_INVALIDATED; return 1; } @@ -511,6 +509,7 @@ write_kitty_data(FILE* fp, int linesize, int leny, int lenx, int cols, tament* tam, int* parse_start){ //fprintf(stderr, "drawing kitty %p\n", tam); if(linesize % sizeof(*data)){ + logerror("Stride (%d) badly aligned\n", linesize); fclose(fp); return -1; } @@ -558,6 +557,11 @@ write_kitty_data(FILE* fp, int linesize, int leny, int lenx, int cols, int xcell = x / cdimx; int ycell = y / cdimy; int tyx = xcell + ycell * cols; + if(tam[tyx].auxvector == NULL){ + if((tam[tyx].auxvector = kitty_transanim_auxvec(cdimy, cdimx, line + x, linesize)) == NULL){ + return -1; // FIXME need clean up auxvecs, fp + } + } //fprintf(stderr, "Tyx: %d y: %d (%d) * %d x: %d (%d) state %d %p\n", tyx, y, y / cdimy, cols, x, x / cdimx, tam[tyx].state, tam[tyx].auxvector); if(tam[tyx].state == SPRIXCELL_ANNIHILATED || tam[tyx].state == SPRIXCELL_ANNIHILATED_TRANS){ // this pixel is part of a cell which is currently wiped (alpha-nulled @@ -592,7 +596,7 @@ write_kitty_data(FILE* fp, int linesize, int leny, int lenx, int cols, fprintf(fp, "\e\\"); } if(fclose(fp) == EOF){ - return -1; + return -1; // FIXME clean up auxvecs! } scrub_tam_boundaries(tam, leny, lenx, cdimy, cdimx); return 0; @@ -601,8 +605,8 @@ write_kitty_data(FILE* fp, int linesize, int leny, int lenx, int cols, // Kitty graphics blitter. Kitty can take in up to 4KiB at a time of (optionally // deflate-compressed) 24bit RGB. Returns -1 on error, 1 on success. -int kitty_blit(ncplane* n, int linesize, const void* data, int leny, int lenx, - const blitterargs* bargs, int bpp __attribute__ ((unused))){ +int kitty_blit_core(ncplane* n, int linesize, const void* data, int leny, int lenx, + const blitterargs* bargs, int bpp __attribute__ ((unused))){ int cols = bargs->u.pixel.spx->dimx; int rows = bargs->u.pixel.spx->dimy; char* buf = NULL; @@ -651,6 +655,17 @@ int kitty_blit(ncplane* n, int linesize, const void* data, int leny, int lenx, return 1; } +int kitty_blit(ncplane* n, int linesize, const void* data, int leny, int lenx, + const blitterargs* bargs, int bpp __attribute__ ((unused))){ + return kitty_blit_core(n, linesize, data, leny, lenx, bargs, false); +} + +int kitty_blit_animated(ncplane* n, int linesize, const void* data, + int leny, int lenx, const blitterargs* bargs, + int bpp __attribute__ ((unused))){ + return kitty_blit_core(n, linesize, data, leny, lenx, bargs, true); +} + int kitty_remove(int id, FILE* out){ loginfo("Removing graphic %u\n", id); if(fprintf(out, "\e_Ga=d,d=i,i=%d\e\\", id) < 0){ @@ -693,11 +708,29 @@ int kitty_scrub(const ncpile* p, sprixel* s){ // returns the number of bytes written int kitty_draw(const ncpile* p, sprixel* s, FILE* out){ (void)p; + bool animated = false; + if(s->mstreamfp){ // active animation + int fret = fclose(s->mstreamfp); + s->mstreamfp = NULL; + if(fret == EOF){ + return -1; + } + animated = true; + } int ret = s->glyphlen; - if(fwrite(s->glyph, s->glyphlen, 1, out) != 1){ - ret = -1; + if(ret){ + if(fwrite(s->glyph, s->glyphlen, 1, out) != 1){ + ret = -1; + } + } + if(animated){ + free(s->glyph); + s->glyph = NULL; + s->glyphlen = 0; + s->invalidated = SPRIXEL_QUIESCENT; + }else{ + s->invalidated = SPRIXEL_LOADED; } - s->invalidated = SPRIXEL_LOADED; return ret; } diff --git a/src/lib/render.c b/src/lib/render.c index a7b7c2434..f255f058c 100644 --- a/src/lib/render.c +++ b/src/lib/render.c @@ -881,14 +881,16 @@ clean_sprixels(notcurses* nc, ncpile* p, FILE* out){ } // otherwise it's a new pile, so we couldn't have been on-screen } - if(goto_location(nc, out, y + nc->margin_t, x + nc->margin_l) == 0){ - int r = sprite_redraw(nc, p, s, out); - if(r < 0){ - return -1; - } - bytesemitted += r; - nc->rstate.hardcursorpos = true; + if(goto_location(nc, out, y + nc->margin_t, x + nc->margin_l)){ + return -1; } + int r = sprite_redraw(nc, p, s, out); + if(r < 0){ + return -1; + } + bytesemitted += r; + // FIXME might not need this if it was only an upload + nc->rstate.hardcursorpos = true; parent = &s->next; ++nc->stats.sprixelemissions; }else{ @@ -1111,7 +1113,7 @@ notcurses_rasterize_inner(notcurses* nc, ncpile* p, FILE* out, unsigned* asu){ // don't write a clearscreen. we only update things that have been changed. // we explicitly move the cursor at the beginning of each output line, so no // need to home it expliticly. -//fprintf(stderr, "RASTERIZE SPRIXELS\n"); + logdebug("Starting sprixel phase 1\n"); int64_t sprixelbytes = clean_sprixels(nc, p, out); if(sprixelbytes < 0){ return -1; @@ -1120,11 +1122,11 @@ notcurses_rasterize_inner(notcurses* nc, ncpile* p, FILE* out, unsigned* asu){ if(rasterize_scrolls(p, out)){ return -1; } -//fprintf(stderr, "RASTERIZE CORE PASS 1\n"); + logdebug("Starting glyph phase 1\n"); if(rasterize_core(nc, p, out, 0)){ return -1; } -//fprintf(stderr, "FINALIZE SPRIXELS\n"); + logdebug("Starting sprixel phase 2\n"); int64_t rasprixelbytes = rasterize_sprixels(nc, p, out); if(rasprixelbytes < 0){ return -1; @@ -1133,7 +1135,7 @@ notcurses_rasterize_inner(notcurses* nc, ncpile* p, FILE* out, unsigned* asu){ pthread_mutex_lock(&nc->statlock); nc->stats.sprixelbytes += sprixelbytes; pthread_mutex_unlock(&nc->statlock); -//fprintf(stderr, "RASTERIZE CORE PASS 2\n"); + logdebug("Starting glyph phase 2\n"); if(rasterize_core(nc, p, out, 1)){ return -1; } diff --git a/src/lib/sprite.h b/src/lib/sprite.h index d43646d4c..1651cf375 100644 --- a/src/lib/sprite.h +++ b/src/lib/sprite.h @@ -161,6 +161,9 @@ int sixel_wipe(sprixel* s, int ycell, int xcell); // throughout to 0. the same trick doesn't work on sixel, but there we // can just print directly over the bitmap. int kitty_wipe(sprixel* s, int ycell, int xcell); +// wipes out a cell by animating an all-transparent cell, and integrating +// it with the original image using the animation protocol of 0.20.0+. +int kitty_wipe_animation(sprixel* s, int ycell, int xcell); int sixel_rebuild(sprixel* s, int ycell, int xcell, uint8_t* auxvec); int kitty_rebuild(sprixel* s, int ycell, int xcell, uint8_t* auxvec); int kitty_draw(const struct ncpile *p, sprixel* s, FILE* out); @@ -181,6 +184,9 @@ int sixel_blit(struct ncplane* nc, int linesize, const void* data, int leny, int lenx, const struct blitterargs* bargs, int bpp); int kitty_blit(struct ncplane* nc, int linesize, const void* data, int leny, int lenx, const struct blitterargs* bargs, int bpp); +int kitty_blit_animated(struct ncplane* n, int linesize, const void* data, + int leny, int lenx, const struct blitterargs* bargs, + int bpp); #ifdef __cplusplus } diff --git a/src/lib/termdesc.c b/src/lib/termdesc.c index 78f47f517..29074f56f 100644 --- a/src/lib/termdesc.c +++ b/src/lib/termdesc.c @@ -62,10 +62,10 @@ setup_sixel_bitmaps(tinfo* ti, int fd, bool invert80){ sprite_init(ti, fd); } -// kitty 0.19.3 didn't have C=1, and thus needs sixel_maxy_pristine +// kitty 0.19.3 didn't have C=1, and thus needs sixel_maxy_pristine. it also +// lacked animation, and thus requires the older interface. static inline void setup_kitty_bitmaps(tinfo* ti, int fd, int sixel_maxy_pristine){ - ti->pixel_wipe = kitty_wipe; ti->pixel_scrub = kitty_scrub; ti->pixel_remove = kitty_remove; ti->pixel_draw = kitty_draw; @@ -75,9 +75,15 @@ setup_kitty_bitmaps(tinfo* ti, int fd, int sixel_maxy_pristine){ ti->sprixel_scale_height = 1; ti->pixel_rebuild = kitty_rebuild; ti->pixel_clear_all = kitty_clear_all; - ti->pixel_trans_auxvec = kitty_trans_auxvec; ti->sixel_maxy_pristine = sixel_maxy_pristine; - set_pixel_blitter(kitty_blit); + if(sixel_maxy_pristine){ + ti->pixel_wipe = kitty_wipe; + ti->pixel_trans_auxvec = kitty_trans_auxvec; + set_pixel_blitter(kitty_blit); + }else{ + ti->pixel_wipe = kitty_wipe_animation; + set_pixel_blitter(kitty_blit_animated); + } sprite_init(ti, fd); }