1 #include <time.h>
2 #include <sys/time.h>
3 #include "internal.h"
4 
5 typedef struct ncfadectx {
6   unsigned rows;                // number of rows when allocated
7   unsigned cols;                // number of columns when allocated
8   int maxsteps;                 // maximum number of iterations
9   unsigned maxr, maxg, maxb;    // maxima across foreground channels
10   unsigned maxbr, maxbg, maxbb; // maxima across background channels
11   uint64_t nanosecs_step;       // nanoseconds per iteration
12   uint64_t startns;             // time fade started
13   uint64_t* channels;           // all channels from the framebuffer
14 } ncfadectx;
15 
ncfadectx_iterations(const ncfadectx * nctx)16 int ncfadectx_iterations(const ncfadectx* nctx){
17   return nctx->maxsteps;
18 }
19 
20 // These arrays are too large to be safely placed on the stack. Get an atomic
21 // snapshot of all channels on the plane. While copying the snapshot, determine
22 // the maxima across each of the six components.
23 static int
alloc_ncplane_palette(ncplane * n,ncfadectx * pp,const struct timespec * ts)24 alloc_ncplane_palette(ncplane* n, ncfadectx* pp, const struct timespec* ts){
25   ncplane_dim_yx(n, &pp->rows, &pp->cols);
26   // add an additional element for the background cell
27   int size = pp->rows * pp->cols + 1;
28   if((pp->channels = malloc(sizeof(*pp->channels) * size)) == NULL){
29     return -1;
30   }
31   pp->maxr = pp->maxg = pp->maxb = 0;
32   pp->maxbr = pp->maxbg = pp->maxbb = 0;
33   unsigned r, g, b, br, bg, bb;
34   uint64_t channels;
35   unsigned y;
36   for(y = 0 ; y < pp->rows ; ++y){
37     for(unsigned x = 0 ; x < pp->cols ; ++x){
38       channels = n->fb[nfbcellidx(n, y, x)].channels;
39       pp->channels[y * pp->cols + x] = channels;
40       ncchannels_fg_rgb8(channels, &r, &g, &b);
41       if(r > pp->maxr){
42         pp->maxr = r;
43       }
44       if(g > pp->maxg){
45         pp->maxg = g;
46       }
47       if(b > pp->maxb){
48         pp->maxb = b;
49       }
50       ncchannels_bg_rgb8(channels, &br, &bg, &bb);
51       if(br > pp->maxbr){
52         pp->maxbr = br;
53       }
54       if(bg > pp->maxbg){
55         pp->maxbg = bg;
56       }
57       if(bb > pp->maxbb){
58         pp->maxbb = bb;
59       }
60     }
61   }
62   channels = n->basecell.channels;
63   pp->channels[y * pp->cols] = channels;
64   ncchannels_fg_rgb8(channels, &r, &g, &b);
65   if(r > pp->maxr){
66     pp->maxr = r;
67   }
68   if(g > pp->maxg){
69     pp->maxg = g;
70   }
71   if(b > pp->maxb){
72     pp->maxb = b;
73   }
74   ncchannels_bg_rgb8(channels, &br, &bg, &bb);
75   if(br > pp->maxbr){
76     pp->maxbr = br;
77   }
78   if(bg > pp->maxbg){
79     pp->maxbg = bg;
80   }
81   if(bb > pp->maxbb){
82     pp->maxbb = bb;
83   }
84   int maxfsteps = pp->maxg > pp->maxr ? (pp->maxb > pp->maxg ? pp->maxb : pp->maxg) :
85                   (pp->maxb > pp->maxr ? pp->maxb : pp->maxr);
86   int maxbsteps = pp->maxbg > pp->maxbr ? (pp->maxbb > pp->maxbg ? pp->maxbb : pp->maxbg) :
87                   (pp->maxbb > pp->maxbr ? pp->maxbb : pp->maxbr);
88   pp->maxsteps = maxfsteps > maxbsteps ? maxfsteps : maxbsteps;
89   if(pp->maxsteps == 0){
90     pp->maxsteps = 1;
91   }
92   uint64_t nanosecs_total;
93   if(ts){
94     nanosecs_total = timespec_to_ns(ts);
95     pp->nanosecs_step = nanosecs_total / pp->maxsteps;
96     if(pp->nanosecs_step == 0){
97       pp->nanosecs_step = 1;
98     }
99   }else{
100     pp->nanosecs_step = 1;
101   }
102   struct timespec times;
103   clock_gettime(CLOCK_MONOTONIC, &times);
104   // Start time in absolute nanoseconds
105   pp->startns = timespec_to_ns(&times);
106   return 0;
107 }
108 
ncplane_fadein_iteration(ncplane * n,ncfadectx * nctx,int iter,fadecb fader,void * curry)109 int ncplane_fadein_iteration(ncplane* n, ncfadectx* nctx, int iter,
110                              fadecb fader, void* curry){
111   // each time through, we need look each cell back up, due to the
112   // possibility of a resize event :/
113   unsigned dimy, dimx;
114   ncplane_dim_yx(n, &dimy, &dimx);
115   unsigned y;
116   for(y = 0 ; y < nctx->rows && y < dimy ; ++y){
117     for(unsigned x = 0 ; x < nctx->cols && x < dimx; ++x){
118       unsigned r, g, b;
119       ncchannels_fg_rgb8(nctx->channels[nctx->cols * y + x], &r, &g, &b);
120       unsigned br, bg, bb;
121       ncchannels_bg_rgb8(nctx->channels[nctx->cols * y + x], &br, &bg, &bb);
122       nccell* c = &n->fb[dimx * y + x];
123       if(!nccell_fg_default_p(c)){
124         r = r * iter / nctx->maxsteps;
125         g = g * iter / nctx->maxsteps;
126         b = b * iter / nctx->maxsteps;
127         nccell_set_fg_rgb8(c, r, g, b);
128       }
129       if(!nccell_bg_default_p(c)){
130         br = br * iter / nctx->maxsteps;
131         bg = bg * iter / nctx->maxsteps;
132         bb = bb * iter / nctx->maxsteps;
133         nccell_set_bg_rgb8(c, br, bg, bb);
134       }
135     }
136   }
137   uint64_t nextwake = (iter + 1) * nctx->nanosecs_step + nctx->startns;
138   struct timespec sleepspec;
139   sleepspec.tv_sec = nextwake / NANOSECS_IN_SEC;
140   sleepspec.tv_nsec = nextwake % NANOSECS_IN_SEC;
141   int ret = 0;
142   if(fader){
143     ret |= fader(ncplane_notcurses(n), n, &sleepspec, curry);
144   }else{
145     ret |= notcurses_render(ncplane_notcurses(n));
146     // clock_nanosleep() has no love for CLOCK_MONOTONIC_RAW, at least as
147     // of Glibc 2.29 + Linux 5.3 (or FreeBSD 12) :/.
148     clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &sleepspec, NULL);
149   }
150   return ret;
151 }
152 
153 static int
ncplane_fadein_internal(ncplane * n,fadecb fader,ncfadectx * pp,void * curry)154 ncplane_fadein_internal(ncplane* n, fadecb fader, ncfadectx* pp, void* curry){
155   // Current time, sampled each iteration
156   uint64_t curns;
157   do{
158     struct timespec times;
159     clock_gettime(CLOCK_MONOTONIC, &times);
160     curns = times.tv_sec * NANOSECS_IN_SEC + times.tv_nsec;
161     int iter = (curns - pp->startns) / pp->nanosecs_step + 1;
162     if(iter > pp->maxsteps){
163       break;
164     }
165     int r = ncplane_fadein_iteration(n, pp, iter, fader, curry);
166     if(r){
167       return r;
168     }
169     clock_gettime(CLOCK_MONOTONIC, &times);
170   }while(true);
171   return 0;
172 }
173 
ncplane_fadeout_iteration(ncplane * n,ncfadectx * nctx,int iter,fadecb fader,void * curry)174 int ncplane_fadeout_iteration(ncplane* n, ncfadectx* nctx, int iter,
175                               fadecb fader, void* curry){
176   unsigned br, bg, bb;
177   unsigned r, g, b;
178   // each time through, we need look each cell back up, due to the
179   // possibility of a resize event :/
180   unsigned dimy, dimx;
181   ncplane_dim_yx(n, &dimy, &dimx);
182   unsigned y;
183   for(y = 0 ; y < nctx->rows && y < dimy ; ++y){
184     for(unsigned x = 0 ; x < nctx->cols && x < dimx; ++x){
185       nccell* c = &n->fb[dimx * y + x];
186       if(!nccell_fg_default_p(c)){
187         ncchannels_fg_rgb8(nctx->channels[nctx->cols * y + x], &r, &g, &b);
188         r = r * (nctx->maxsteps - iter) / nctx->maxsteps;
189         g = g * (nctx->maxsteps - iter) / nctx->maxsteps;
190         b = b * (nctx->maxsteps - iter) / nctx->maxsteps;
191         nccell_set_fg_rgb8(c, r, g, b);
192       }
193       if(!nccell_bg_default_p(c)){
194         ncchannels_bg_rgb8(nctx->channels[nctx->cols * y + x], &br, &bg, &bb);
195         br = br * (nctx->maxsteps - iter) / nctx->maxsteps;
196         bg = bg * (nctx->maxsteps - iter) / nctx->maxsteps;
197         bb = bb * (nctx->maxsteps - iter) / nctx->maxsteps;
198         nccell_set_bg_rgb8(c, br, bg, bb);
199       }
200     }
201   }
202   nccell* c = &n->basecell;
203   if(!nccell_fg_default_p(c)){
204     ncchannels_fg_rgb8(nctx->channels[nctx->cols * y], &r, &g, &b);
205     r = r * (nctx->maxsteps - iter) / nctx->maxsteps;
206     g = g * (nctx->maxsteps - iter) / nctx->maxsteps;
207     b = b * (nctx->maxsteps - iter) / nctx->maxsteps;
208     nccell_set_fg_rgb8(&n->basecell, r, g, b);
209   }
210   if(!nccell_bg_default_p(c)){
211     ncchannels_bg_rgb8(nctx->channels[nctx->cols * y], &br, &bg, &bb);
212     br = br * (nctx->maxsteps - iter) / nctx->maxsteps;
213     bg = bg * (nctx->maxsteps - iter) / nctx->maxsteps;
214     bb = bb * (nctx->maxsteps - iter) / nctx->maxsteps;
215     nccell_set_bg_rgb8(&n->basecell, br, bg, bb);
216   }
217   uint64_t nextwake = (iter + 1) * nctx->nanosecs_step + nctx->startns;
218   struct timespec sleepspec;
219   sleepspec.tv_sec = nextwake / NANOSECS_IN_SEC;
220   sleepspec.tv_nsec = nextwake % NANOSECS_IN_SEC;
221   int ret;
222   if(fader){
223     ret = fader(ncplane_notcurses(n), n, &sleepspec, curry);
224   }else{
225     ret = notcurses_render(ncplane_notcurses(n));
226     // clock_nanosleep() has no love for CLOCK_MONOTONIC_RAW, at least as
227     // of Glibc 2.29 + Linux 5.3 (or FreeBSD 12) :/.
228     clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &sleepspec, NULL);
229   }
230   return ret;
231 }
232 
233 static ncfadectx*
ncfadectx_setup_internal(ncplane * n,const struct timespec * ts)234 ncfadectx_setup_internal(ncplane* n, const struct timespec* ts){
235   if(!ncplane_notcurses(n)->tcache.caps.rgb &&
236      !ncplane_notcurses(n)->tcache.caps.can_change_colors){ // terminal can't fade
237     return NULL;
238   }
239   ncfadectx* nctx = malloc(sizeof(*nctx));
240   if(nctx){
241     if(alloc_ncplane_palette(n, nctx, ts) == 0){
242       return nctx;
243     }
244     free(nctx);
245   }
246   return NULL;
247 }
248 
ncfadectx_setup(ncplane * n)249 ncfadectx* ncfadectx_setup(ncplane* n){
250   return ncfadectx_setup_internal(n, NULL);
251 }
252 
ncfadectx_free(ncfadectx * nctx)253 void ncfadectx_free(ncfadectx* nctx){
254   if(nctx){
255     free(nctx->channels);
256     free(nctx);
257   }
258 }
259 
ncplane_fadeout(ncplane * n,const struct timespec * ts,fadecb fader,void * curry)260 int ncplane_fadeout(ncplane* n, const struct timespec* ts, fadecb fader, void* curry){
261   ncfadectx* pp = ncfadectx_setup_internal(n, ts);
262   if(!pp){
263     return -1;
264   }
265   struct timespec times;
266   ns_to_timespec(pp->startns, &times);
267   do{
268     uint64_t curns = times.tv_sec * NANOSECS_IN_SEC + times.tv_nsec;
269     int iter = (curns - pp->startns) / pp->nanosecs_step + 1;
270     if(iter > pp->maxsteps){
271       break;
272     }
273     int r = ncplane_fadeout_iteration(n, pp, iter, fader, curry);
274     if(r){
275       ncfadectx_free(pp);
276       return r;
277     }
278     clock_gettime(CLOCK_MONOTONIC, &times);
279   }while(true);
280   ncfadectx_free(pp);
281   return 0;
282 }
283 
ncplane_fadein(ncplane * n,const struct timespec * ts,fadecb fader,void * curry)284 int ncplane_fadein(ncplane* n, const struct timespec* ts, fadecb fader, void* curry){
285   ncfadectx* nctx = ncfadectx_setup_internal(n, ts);
286   if(nctx == NULL){
287     struct timespec now;
288     clock_gettime(CLOCK_MONOTONIC, &now);
289     if(fader){
290       fader(ncplane_notcurses(n), n, &now, curry);
291     }else{
292       notcurses_render(ncplane_notcurses(n));
293     }
294     return -1;
295   }
296   int ret = ncplane_fadein_internal(n, fader, nctx, curry);
297   ncfadectx_free(nctx);
298   return ret;
299 }
300 
ncplane_pulse(ncplane * n,const struct timespec * ts,fadecb fader,void * curry)301 int ncplane_pulse(ncplane* n, const struct timespec* ts, fadecb fader, void* curry){
302   ncfadectx pp;
303   int ret;
304   if(!notcurses_canfade(ncplane_notcurses(n))){
305     return -1;
306   }
307   if(alloc_ncplane_palette(n, &pp, ts)){
308     return -1;
309   }
310   for(;;){
311     ret = ncplane_fadein_internal(n, fader, &pp, curry);
312     if(ret){
313       break;
314     }
315     ret = ncplane_fadeout(n, ts, fader, curry);
316     if(ret){
317       break;
318     }
319   }
320   free(pp.channels);
321   return ret;
322 }
323