You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
324 lines
9.8 KiB
C
324 lines
9.8 KiB
C
#include <time.h>
|
|
#include <sys/time.h>
|
|
#include "internal.h"
|
|
|
|
typedef struct ncfadectx {
|
|
int rows; // number of rows when allocated
|
|
int cols; // number of columns when allocated
|
|
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;
|
|
|
|
int ncfadectx_iterations(const ncfadectx* nctx){
|
|
return nctx->maxsteps;
|
|
}
|
|
|
|
// These arrays are too large to be safely placed on the stack. Get an atomic
|
|
// 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, 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;
|
|
if((pp->channels = malloc(sizeof(*pp->channels) * size)) == NULL){
|
|
return -1;
|
|
}
|
|
pp->maxr = pp->maxg = pp->maxb = 0;
|
|
pp->maxbr = pp->maxbg = pp->maxbb = 0;
|
|
unsigned r, g, b, br, bg, bb;
|
|
uint64_t channels;
|
|
int y, x;
|
|
for(y = 0 ; y < pp->rows ; ++y){
|
|
for(x = 0 ; x < pp->cols ; ++x){
|
|
channels = n->fb[nfbcellidx(n, y, x)].channels;
|
|
pp->channels[y * pp->cols + x] = channels;
|
|
channels_fg_rgb8(channels, &r, &g, &b);
|
|
if(r > pp->maxr){
|
|
pp->maxr = r;
|
|
}
|
|
if(g > pp->maxg){
|
|
pp->maxg = g;
|
|
}
|
|
if(b > pp->maxb){
|
|
pp->maxb = b;
|
|
}
|
|
channels_bg_rgb8(channels, &br, &bg, &bb);
|
|
if(br > pp->maxbr){
|
|
pp->maxbr = br;
|
|
}
|
|
if(bg > pp->maxbg){
|
|
pp->maxbg = bg;
|
|
}
|
|
if(bb > pp->maxbb){
|
|
pp->maxbb = bb;
|
|
}
|
|
}
|
|
}
|
|
channels = n->basecell.channels;
|
|
pp->channels[y * pp->cols] = channels;
|
|
channels_fg_rgb8(channels, &r, &g, &b);
|
|
if(r > pp->maxr){
|
|
pp->maxr = r;
|
|
}
|
|
if(g > pp->maxg){
|
|
pp->maxg = g;
|
|
}
|
|
if(b > pp->maxb){
|
|
pp->maxb = b;
|
|
}
|
|
channels_bg_rgb8(channels, &br, &bg, &bb);
|
|
if(br > pp->maxbr){
|
|
pp->maxbr = br;
|
|
}
|
|
if(bg > pp->maxbg){
|
|
pp->maxbg = bg;
|
|
}
|
|
if(bb > pp->maxbb){
|
|
pp->maxbb = bb;
|
|
}
|
|
int maxfsteps = pp->maxg > pp->maxr ? (pp->maxb > pp->maxg ? pp->maxb : pp->maxg) :
|
|
(pp->maxb > pp->maxr ? pp->maxb : pp->maxr);
|
|
int maxbsteps = pp->maxbg > pp->maxbr ? (pp->maxbb > pp->maxbg ? pp->maxbb : pp->maxbg) :
|
|
(pp->maxbb > pp->maxbr ? pp->maxbb : pp->maxbr);
|
|
pp->maxsteps = maxfsteps > maxbsteps ? maxfsteps : maxbsteps;
|
|
if(pp->maxsteps == 0){
|
|
pp->maxsteps = 1;
|
|
}
|
|
uint64_t nanosecs_total;
|
|
if(ts){
|
|
nanosecs_total = timespec_to_ns(ts);
|
|
pp->nanosecs_step = nanosecs_total / pp->maxsteps;
|
|
if(pp->nanosecs_step == 0){
|
|
pp->nanosecs_step = 1;
|
|
}
|
|
}else{
|
|
pp->nanosecs_step = 1;
|
|
}
|
|
struct timespec times;
|
|
clock_gettime(CLOCK_MONOTONIC, ×);
|
|
// Start time in absolute nanoseconds
|
|
pp->startns = timespec_to_ns(×);
|
|
return 0;
|
|
}
|
|
|
|
int ncplane_fadein_iteration(ncplane* n, ncfadectx* nctx, int iter,
|
|
fadecb fader, void* curry){
|
|
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){
|
|
unsigned r, g, b;
|
|
channels_fg_rgb8(nctx->channels[nctx->cols * y + x], &r, &g, &b);
|
|
unsigned br, bg, bb;
|
|
channels_bg_rgb8(nctx->channels[nctx->cols * y + x], &br, &bg, &bb);
|
|
nccell* c = &n->fb[dimx * y + x];
|
|
if(!cell_fg_default_p(c)){
|
|
r = r * iter / nctx->maxsteps;
|
|
g = g * iter / nctx->maxsteps;
|
|
b = b * iter / nctx->maxsteps;
|
|
cell_set_fg_rgb8(c, r, g, b);
|
|
}
|
|
if(!cell_bg_default_p(c)){
|
|
br = br * iter / nctx->maxsteps;
|
|
bg = bg * iter / nctx->maxsteps;
|
|
bb = bb * iter / nctx->maxsteps;
|
|
cell_set_bg_rgb8(c, 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 = 0;
|
|
if(fader){
|
|
ret |= fader(ncplane_notcurses(n), n, &sleepspec, curry);
|
|
}else{
|
|
ret |= notcurses_render(ncplane_notcurses(n));
|
|
// 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;
|
|
}
|
|
|
|
static int
|
|
ncplane_fadein_internal(ncplane* n, fadecb fader, ncfadectx* pp, void* curry){
|
|
// Current time, sampled each iteration
|
|
uint64_t curns;
|
|
do{
|
|
struct timespec times;
|
|
clock_gettime(CLOCK_MONOTONIC, ×);
|
|
curns = times.tv_sec * NANOSECS_IN_SEC + times.tv_nsec;
|
|
int iter = (curns - pp->startns) / pp->nanosecs_step + 1;
|
|
if(iter > pp->maxsteps){
|
|
break;
|
|
}
|
|
int r = ncplane_fadein_iteration(n, pp, iter, fader, curry);
|
|
if(r){
|
|
return r;
|
|
}
|
|
clock_gettime(CLOCK_MONOTONIC, ×);
|
|
}while(true);
|
|
return 0;
|
|
}
|
|
|
|
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){
|
|
nccell* c = &n->fb[dimx * y + x];
|
|
if(!cell_fg_default_p(c)){
|
|
channels_fg_rgb8(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_rgb8(c, r, g, b);
|
|
}
|
|
if(!cell_bg_default_p(c)){
|
|
channels_bg_rgb8(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_rgb8(c, br, bg, bb);
|
|
}
|
|
}
|
|
}
|
|
nccell* c = &n->basecell;
|
|
if(!cell_fg_default_p(c)){
|
|
channels_fg_rgb8(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_rgb8(&n->basecell, r, g, b);
|
|
}
|
|
if(!cell_bg_default_p(c)){
|
|
channels_bg_rgb8(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_rgb8(&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(ncplane_notcurses(n), n, &sleepspec, curry);
|
|
}else{
|
|
ret = notcurses_render(ncplane_notcurses(n));
|
|
// 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;
|
|
}
|
|
|
|
static ncfadectx*
|
|
ncfadectx_setup_internal(ncplane* n, const struct timespec* ts){
|
|
if(!ncplane_notcurses(n)->tcache.RGBflag &&
|
|
!ncplane_notcurses(n)->tcache.CCCflag){ // terminal can't fade
|
|
return NULL;
|
|
}
|
|
ncfadectx* nctx = malloc(sizeof(*nctx));
|
|
if(nctx){
|
|
if(alloc_ncplane_palette(n, nctx, ts) == 0){
|
|
return nctx;
|
|
}
|
|
free(nctx);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
ncfadectx* ncfadectx_setup(ncplane* n){
|
|
return ncfadectx_setup_internal(n, NULL);
|
|
}
|
|
|
|
void ncfadectx_free(ncfadectx* nctx){
|
|
if(nctx){
|
|
free(nctx->channels);
|
|
free(nctx);
|
|
}
|
|
}
|
|
|
|
int ncplane_fadeout(ncplane* n, const struct timespec* ts, fadecb fader, void* curry){
|
|
ncfadectx* pp = ncfadectx_setup_internal(n, ts);
|
|
if(!pp){
|
|
return -1;
|
|
}
|
|
struct timespec times;
|
|
ns_to_timespec(pp->startns, ×);
|
|
do{
|
|
uint64_t curns = times.tv_sec * NANOSECS_IN_SEC + times.tv_nsec;
|
|
int iter = (curns - pp->startns) / pp->nanosecs_step + 1;
|
|
if(iter > pp->maxsteps){
|
|
break;
|
|
}
|
|
int r = ncplane_fadeout_iteration(n, pp, iter, fader, curry);
|
|
if(r){
|
|
ncfadectx_free(pp);
|
|
return r;
|
|
}
|
|
clock_gettime(CLOCK_MONOTONIC, ×);
|
|
}while(true);
|
|
ncfadectx_free(pp);
|
|
return 0;
|
|
}
|
|
|
|
int ncplane_fadein(ncplane* n, const struct timespec* ts, fadecb fader, void* curry){
|
|
ncfadectx* nctx = ncfadectx_setup_internal(n, ts);
|
|
if(nctx == NULL){
|
|
struct timespec now;
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
if(fader){
|
|
fader(ncplane_notcurses(n), n, &now, curry);
|
|
}else{
|
|
notcurses_render(ncplane_notcurses(n));
|
|
}
|
|
return -1;
|
|
}
|
|
int ret = ncplane_fadein_internal(n, fader, nctx, curry);
|
|
ncfadectx_free(nctx);
|
|
return ret;
|
|
}
|
|
|
|
int ncplane_pulse(ncplane* n, const struct timespec* ts, fadecb fader, void* curry){
|
|
ncfadectx pp;
|
|
int ret;
|
|
if(!ncplane_notcurses(n)->tcache.RGBflag &&
|
|
!ncplane_notcurses(n)->tcache.CCCflag){ // terminal can't fade
|
|
return -1;
|
|
}
|
|
if(alloc_ncplane_palette(n, &pp, ts)){
|
|
return -1;
|
|
}
|
|
for(;;){
|
|
ret = ncplane_fadein_internal(n, fader, &pp, curry);
|
|
if(ret){
|
|
break;
|
|
}
|
|
ret = ncplane_fadeout(n, ts, fader, curry);
|
|
if(ret){
|
|
break;
|
|
}
|
|
}
|
|
free(pp.channels);
|
|
return ret;
|
|
}
|