diff --git a/NEWS.md b/NEWS.md index 10f0fdb4d..2719703ae 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,12 +9,18 @@ rearrangements of Notcurses. `ncblitter_e` field, allowing visuals to be mapped to various plotting paradigms including Sixel, Braille and quadrants. Not all backends have been implemented, and not all implementations are in their final form. + `CELL_ALPHA_BLEND` can now be used for translucent visuals. * Added `ncvisual_geom()`, providing access to an `ncvisual` size and its pixel-to-cell blitting ratios. * Deprecated functions `ncvisual_open_plane()` and `ncplane_visual_open()` have been removed. Their functionality is present in `ncvisual_from_file()`. The function `ncvisual_plane()` no longer has any meaning, and has been removed. + * The `fadecb` typedef now accepts as its third argument a `const struct + timespec`. This is the absolute deadline through which the frame ought + be displayed. Like the changes to `ncvisual_stream()`, this gives more + flexibility to the callback, and allows more precise timing. There will + be further changes to the Fade API before API freeze (see #659). * 1.4.3 (2020-05-22) * Plot: make 8x1 the default, instead of 1x1. diff --git a/USAGE.md b/USAGE.md index bcc7cfa83..5139aa8d4 100644 --- a/USAGE.md +++ b/USAGE.md @@ -1200,8 +1200,7 @@ typedef int (*fadecb)(struct notcurses* nc, struct ncplane* ncp, void* curry); // Fade the ncplane out over the provided time, calling the specified function // when done. Requires a terminal which supports truecolor, or at least palette // modification (if the terminal uses a palette, our ability to fade planes is -// limited, and affected by the complexity of the rest of the screen). It is -// not safe to resize or destroy the plane during the fadeout FIXME. +// limited, and affected by the complexity of the rest of the screen). int ncplane_fadeout(struct ncplane* n, const struct timespec* ts, fadecb fader, void* curry); // Fade the ncplane in over the specified time. Load the ncplane with the diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index 926288f92..20ec4a0f0 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -1974,8 +1974,7 @@ typedef int (*fadecb)(struct notcurses* nc, struct ncplane* ncp, // Fade the ncplane out over the provided time, calling the specified function // when done. Requires a terminal which supports truecolor, or at least palette // modification (if the terminal uses a palette, our ability to fade planes is -// limited, and affected by the complexity of the rest of the screen). It is -// not safe to resize or destroy the plane during the fadeout FIXME. +// limited, and affected by the complexity of the rest of the screen). API int ncplane_fadeout(struct ncplane* n, const struct timespec* ts, fadecb fader, void* curry); diff --git a/src/lib/fade.c b/src/lib/fade.c index 9eb9b0605..ffdb1d0bb 100644 --- a/src/lib/fade.c +++ b/src/lib/fade.c @@ -6,6 +6,8 @@ typedef struct ncfadectx { int maxsteps; // maximum number of iterations unsigned maxr, maxg, maxb; // maxima across foreground channels unsigned maxbr, maxbg, maxbb; // maxima across background channels + uint64_t nanosecs_step; // nanoseconds per iteration + uint64_t startns; // time fade started uint64_t* channels; // all channels from the framebuffer } ncfadectx; @@ -13,7 +15,7 @@ typedef struct ncfadectx { // snapshot of all channels on the plane. While copying the snapshot, determine // the maxima across each of the six components. static int -alloc_ncplane_palette(ncplane* n, ncfadectx* pp){ +alloc_ncplane_palette(ncplane* n, ncfadectx* pp, const struct timespec* ts){ ncplane_dim_yx(n, &pp->rows, &pp->cols); // add an additional element for the background cell int size = pp->rows * pp->cols + 1; @@ -82,28 +84,28 @@ alloc_ncplane_palette(ncplane* n, ncfadectx* pp){ if(pp->maxsteps == 0){ pp->maxsteps = 1; } - return 0; -} - -static int -ncplane_fadein_internal(ncplane* n, const struct timespec* ts, - fadecb fader, ncfadectx* pp, void* curry){ - uint64_t nanosecs_total = ts->tv_sec * NANOSECS_IN_SEC + ts->tv_nsec; - uint64_t nanosecs_step = nanosecs_total / pp->maxsteps; - if(nanosecs_step == 0){ - nanosecs_step = 1; + uint64_t nanosecs_total = timespec_to_ns(ts); + pp->nanosecs_step = nanosecs_total / pp->maxsteps; + if(pp->nanosecs_step == 0){ + pp->nanosecs_step = 1; } struct timespec times; clock_gettime(CLOCK_MONOTONIC, ×); // Start time in absolute nanoseconds - uint64_t startns = times.tv_sec * NANOSECS_IN_SEC + times.tv_nsec; + pp->startns = timespec_to_ns(×); + return 0; +} + +static int +ncplane_fadein_internal(ncplane* n, fadecb fader, ncfadectx* pp, void* curry){ // Current time, sampled each iteration uint64_t curns; int ret = 0; do{ + struct timespec times; clock_gettime(CLOCK_MONOTONIC, ×); curns = times.tv_sec * NANOSECS_IN_SEC + times.tv_nsec; - int iter = (curns - startns) / nanosecs_step + 1; + int iter = (curns - pp->startns) / pp->nanosecs_step + 1; if(iter > pp->maxsteps){ break; } @@ -133,7 +135,7 @@ ncplane_fadein_internal(ncplane* n, const struct timespec* ts, } } } - uint64_t nextwake = (iter + 1) * nanosecs_step + startns; + uint64_t nextwake = (iter + 1) * pp->nanosecs_step + pp->startns; struct timespec sleepspec; sleepspec.tv_sec = nextwake / NANOSECS_IN_SEC; sleepspec.tv_nsec = nextwake % NANOSECS_IN_SEC; @@ -152,89 +154,85 @@ ncplane_fadein_internal(ncplane* n, const struct timespec* ts, return ret; } +int ncplane_fadeout_iteration(ncplane* n, ncfadectx* nctx, int iter, + fadecb fader, void* curry){ + unsigned br, bg, bb; + unsigned r, g, b; + int y, x; + // each time through, we need look each cell back up, due to the + // possibility of a resize event :/ + int dimy, dimx; + ncplane_dim_yx(n, &dimy, &dimx); + for(y = 0 ; y < nctx->rows && y < dimy ; ++y){ + for(x = 0 ; x < nctx->cols && x < dimx; ++x){ + cell* c = &n->fb[dimx * y + x]; + if(!cell_fg_default_p(c)){ + channels_fg_rgb(nctx->channels[nctx->cols * y + x], &r, &g, &b); + r = r * (nctx->maxsteps - iter) / nctx->maxsteps; + g = g * (nctx->maxsteps - iter) / nctx->maxsteps; + b = b * (nctx->maxsteps - iter) / nctx->maxsteps; + cell_set_fg_rgb(c, r, g, b); + } + if(!cell_bg_default_p(c)){ + channels_bg_rgb(nctx->channels[nctx->cols * y + x], &br, &bg, &bb); + br = br * (nctx->maxsteps - iter) / nctx->maxsteps; + bg = bg * (nctx->maxsteps - iter) / nctx->maxsteps; + bb = bb * (nctx->maxsteps - iter) / nctx->maxsteps; + cell_set_bg_rgb(c, br, bg, bb); + } + } + } + cell* c = &n->basecell; + if(!cell_fg_default_p(c)){ + channels_fg_rgb(nctx->channels[nctx->cols * y], &r, &g, &b); + r = r * (nctx->maxsteps - iter) / nctx->maxsteps; + g = g * (nctx->maxsteps - iter) / nctx->maxsteps; + b = b * (nctx->maxsteps - iter) / nctx->maxsteps; + cell_set_fg_rgb(&n->basecell, r, g, b); + } + if(!cell_bg_default_p(c)){ + channels_bg_rgb(nctx->channels[nctx->cols * y], &br, &bg, &bb); + br = br * (nctx->maxsteps - iter) / nctx->maxsteps; + bg = bg * (nctx->maxsteps - iter) / nctx->maxsteps; + bb = bb * (nctx->maxsteps - iter) / nctx->maxsteps; + cell_set_bg_rgb(&n->basecell, br, bg, bb); + } + uint64_t nextwake = (iter + 1) * nctx->nanosecs_step + nctx->startns; + struct timespec sleepspec; + sleepspec.tv_sec = nextwake / NANOSECS_IN_SEC; + sleepspec.tv_nsec = nextwake % NANOSECS_IN_SEC; + int ret; + if(fader){ + ret = fader(n->nc, n, &sleepspec, curry); + }else{ + ret = notcurses_render(n->nc); + // clock_nanosleep() has no love for CLOCK_MONOTONIC_RAW, at least as + // of Glibc 2.29 + Linux 5.3 (or FreeBSD 12) :/. + clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &sleepspec, NULL); + } + return ret; +} + int ncplane_fadeout(ncplane* n, const struct timespec* ts, fadecb fader, void* curry){ ncfadectx pp; if(!n->nc->tcache.RGBflag && !n->nc->tcache.CCCflag){ // terminal can't fade return -1; } - if(alloc_ncplane_palette(n, &pp)){ + if(alloc_ncplane_palette(n, &pp, ts)){ return -1; } - uint64_t nanosecs_total = timespec_to_ns(ts); - uint64_t nanosecs_step = nanosecs_total / pp.maxsteps; - if(nanosecs_step == 0){ - nanosecs_step = 1; - } - struct timespec times; - clock_gettime(CLOCK_MONOTONIC, ×); - // Start time in absolute nanoseconds - uint64_t startns = timespec_to_ns(×); int ret = 0; + struct timespec times; + ns_to_timespec(pp.startns, ×); do{ - unsigned br, bg, bb; - unsigned r, g, b; uint64_t curns = times.tv_sec * NANOSECS_IN_SEC + times.tv_nsec; - int iter = (curns - startns) / nanosecs_step + 1; + int iter = (curns - pp.startns) / pp.nanosecs_step + 1; if(iter > pp.maxsteps){ break; } - int y, x; - // each time through, we need look each cell back up, due to the - // possibility of a resize event :/ - int dimy, dimx; - ncplane_dim_yx(n, &dimy, &dimx); - for(y = 0 ; y < pp.rows && y < dimy ; ++y){ - for(x = 0 ; x < pp.cols && x < dimx; ++x){ - cell* c = &n->fb[dimx * y + x]; - if(!cell_fg_default_p(c)){ - channels_fg_rgb(pp.channels[pp.cols * y + x], &r, &g, &b); - r = r * (pp.maxsteps - iter) / pp.maxsteps; - g = g * (pp.maxsteps - iter) / pp.maxsteps; - b = b * (pp.maxsteps - iter) / pp.maxsteps; - cell_set_fg_rgb(c, r, g, b); - } - if(!cell_bg_default_p(c)){ - channels_bg_rgb(pp.channels[pp.cols * y + x], &br, &bg, &bb); - br = br * (pp.maxsteps - iter) / pp.maxsteps; - bg = bg * (pp.maxsteps - iter) / pp.maxsteps; - bb = bb * (pp.maxsteps - iter) / pp.maxsteps; - cell_set_bg_rgb(c, br, bg, bb); - } - } - } - cell* c = &n->basecell; - if(!cell_fg_default_p(c)){ - channels_fg_rgb(pp.channels[pp.cols * y], &r, &g, &b); - r = r * (pp.maxsteps - iter) / pp.maxsteps; - g = g * (pp.maxsteps - iter) / pp.maxsteps; - b = b * (pp.maxsteps - iter) / pp.maxsteps; - cell_set_fg_rgb(&n->basecell, r, g, b); - } - if(!cell_bg_default_p(c)){ - channels_bg_rgb(pp.channels[pp.cols * y], &br, &bg, &bb); - br = br * (pp.maxsteps - iter) / pp.maxsteps; - bg = bg * (pp.maxsteps - iter) / pp.maxsteps; - bb = bb * (pp.maxsteps - iter) / pp.maxsteps; - cell_set_bg_rgb(&n->basecell, br, bg, bb); - } - uint64_t nextwake = (iter + 1) * nanosecs_step + startns; - struct timespec sleepspec; - sleepspec.tv_sec = nextwake / NANOSECS_IN_SEC; - sleepspec.tv_nsec = nextwake % NANOSECS_IN_SEC; - int rsleep; - if(fader){ - ret = fader(n->nc, n, &sleepspec, curry); - }else{ - ret = notcurses_render(n->nc); - } - if(ret){ - break; - } - // clock_nanosleep() has no love for CLOCK_MONOTONIC_RAW, at least as - // of Glibc 2.29 + Linux 5.3 (or FreeBSD 12) :/. - rsleep = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &sleepspec, NULL); - if(rsleep){ - break; + int r = ncplane_fadeout_iteration(n, &pp, iter, fader, curry); + if(r){ + return r; } clock_gettime(CLOCK_MONOTONIC, ×); }while(true); @@ -254,10 +252,10 @@ int ncplane_fadein(ncplane* n, const struct timespec* ts, fadecb fader, void* cu } return -1; } - if(alloc_ncplane_palette(n, &pp)){ + if(alloc_ncplane_palette(n, &pp, ts)){ return -1; } - int ret = ncplane_fadein_internal(n, ts, fader, &pp, curry); + int ret = ncplane_fadein_internal(n, fader, &pp, curry); free(pp.channels); return ret; } @@ -268,11 +266,11 @@ int ncplane_pulse(ncplane* n, const struct timespec* ts, fadecb fader, void* cur if(!n->nc->tcache.RGBflag && !n->nc->tcache.CCCflag){ // terminal can't fade return -1; } - if(alloc_ncplane_palette(n, &pp)){ + if(alloc_ncplane_palette(n, &pp, ts)){ return -1; } for(;;){ - ret = ncplane_fadein_internal(n, ts, fader, &pp, curry); + ret = ncplane_fadein_internal(n, fader, &pp, curry); if(ret){ break; }