1 #include <math.h>
2 #include <string.h>
3 #include "builddef.h"
4 #include "visual-details.h"
5 #include "internal.h"
6 
7 // ncvisual core code has a basic implementation in libnotcurses-core, and can
8 // be augmented with a "multimedia engine" -- currently FFmpeg or OpenImageIO,
9 // or the trivial "none" engine. all libnotcurses (built against one of these
10 // engines, selected at compile time) actually does is set this
11 // visual_implementation pointer, and then call libnotcurses_core_init(). the
12 // "none" implementation exists to facilitate linking programs written against
13 // libnotcurses in environments without a true multimedia engine, and does not
14 // set this pointer. all this machination exists to support building notcurses
15 // (and running notcurses programs) without the need of heavy media engines.
16 
17 static ncvisual_implementation null_visual_implementation = {};
18 
19 ncvisual_implementation* visual_implementation = &null_visual_implementation;
20 
21 // to be called at startup -- performs any necessary engine initialization.
ncvisual_init(int logl)22 int ncvisual_init(int logl){
23   if(visual_implementation->visual_init){
24     return visual_implementation->visual_init(logl);
25   }
26   return 0;
27 }
28 
ncvisual_printbanner(fbuf * f)29 void ncvisual_printbanner(fbuf* f){
30   if(visual_implementation->visual_printbanner){
31     visual_implementation->visual_printbanner(f);
32   }
33 }
34 
35 // you need an actual multimedia implementation for functions which work with
36 // codecs, including ncvisual_decode(), ncvisual_decode_loop(),
37 // ncvisual_from_file(), ncvisual_stream(), and ncvisual_subtitle_plane().
ncvisual_decode(ncvisual * nc)38 int ncvisual_decode(ncvisual* nc){
39   if(!visual_implementation->visual_decode){
40     return -1;
41   }
42   return visual_implementation->visual_decode(nc);
43 }
44 
ncvisual_decode_loop(ncvisual * nc)45 int ncvisual_decode_loop(ncvisual* nc){
46   if(!visual_implementation->visual_decode_loop){
47     return -1;
48   }
49   return visual_implementation->visual_decode_loop(nc);
50 }
51 
ncvisual_from_file(const char * filename)52 ncvisual* ncvisual_from_file(const char* filename){
53   if(!visual_implementation->visual_from_file){
54     return NULL;
55   }
56   ncvisual* n = visual_implementation->visual_from_file(filename);
57   if(n == NULL){
58     logerror("error loading %s\n", filename);
59   }
60   return n;
61 }
62 
ncvisual_stream(notcurses * nc,ncvisual * ncv,float timescale,ncstreamcb streamer,const struct ncvisual_options * vopts,void * curry)63 int ncvisual_stream(notcurses* nc, ncvisual* ncv, float timescale,
64                     ncstreamcb streamer, const struct ncvisual_options* vopts,
65                     void* curry){
66   if(!visual_implementation->visual_stream){
67     return -1;
68   }
69   int ret = visual_implementation->visual_stream(nc, ncv, timescale, streamer, vopts, curry);
70   if(ret < 0){
71     logerror("error streaming media\n");
72   }
73   return ret;
74 }
75 
ncvisual_subtitle_plane(ncplane * parent,const ncvisual * ncv)76 ncplane* ncvisual_subtitle_plane(ncplane* parent, const ncvisual* ncv){
77   if(!visual_implementation->visual_subtitle){
78     return NULL;
79   }
80   return visual_implementation->visual_subtitle(parent, ncv);
81 }
82 
ncvisual_blit_internal(ncvisual * ncv,int rows,int cols,ncplane * n,const struct blitset * bset,const blitterargs * barg)83 int ncvisual_blit_internal(ncvisual* ncv, int rows, int cols, ncplane* n,
84                            const struct blitset* bset, const blitterargs* barg){
85   if(!(barg->flags & NCVISUAL_OPTION_NOINTERPOLATE)){
86     if(visual_implementation->visual_blit){
87       if(visual_implementation->visual_blit(ncv, rows, cols, n, bset, barg) < 0){
88         return -1;
89       }
90       return 0;
91     }
92   }
93   // generic implementation
94   int stride = 4 * cols;
95   uint32_t* data = resize_bitmap(ncv->data, ncv->pixy, ncv->pixx,
96                                  ncv->rowstride, rows, cols, stride);
97   if(data == NULL){
98     return -1;
99   }
100   int ret = -1;
101   if(rgba_blit_dispatch(n, bset, stride, data, rows, cols, barg) >= 0){
102     ret = 0;
103   }
104   if(data != ncv->data){
105     free(data);
106   }
107   return ret;
108 }
109 
110 // ncv constructors other than ncvisual_from_file() need to set up the
111 // AVFrame* 'frame' according to their own data, which is assumed to
112 // have been prepared already in 'ncv'.
ncvisual_details_seed(struct ncvisual * ncv)113 void ncvisual_details_seed(struct ncvisual* ncv){
114   if(visual_implementation->visual_details_seed){
115     visual_implementation->visual_details_seed(ncv);
116   }
117 }
118 
ncvisual_create(void)119 ncvisual* ncvisual_create(void){
120   if(visual_implementation->visual_create){
121     return visual_implementation->visual_create();
122   }
123   ncvisual* ret = malloc(sizeof(*ret));
124   memset(ret, 0, sizeof(*ret));
125   return ret;
126 }
127 
128 static inline void
ncvisual_origin(const struct ncvisual_options * vopts,unsigned * restrict begy,unsigned * restrict begx)129 ncvisual_origin(const struct ncvisual_options* vopts, unsigned* restrict begy,
130                 unsigned* restrict begx){
131   *begy = vopts ? vopts->begy : 0;
132   *begx = vopts ? vopts->begx : 0;
133 }
134 
135 // create a plane in which to blit the sprixel. |disppixx| and |disppixy| are
136 // scaled pixel geometry on output, and unused on input. |placey| and |placex|
137 // are used to position the new plane, and reset to 0 on output. |outy| and
138 // |outx| are true output geometry on output, and unused on input (actual input
139 // pixel geometry come from ncv->pixy and ncv->pixx).
140 // |pxoffy| and |pxoffx| are pixel offset within the origin cell. they are not
141 // included within |disppixx| nor |disppixy|, but count towards |outx| and
142 // |outy|. these last two are furthermore clamped to sixel maxima, and |outy|
143 // accounts for sixels being a multiple of six pixels tall.
144 //
145 // cellpxy/cellpxx and dimy/dimx ought describe the cell-pixel and cell
146 // geometry of the target pile or, in Direct Mode, the tcache.
147 static void
shape_sprixel_plane(const tinfo * ti,unsigned cellpxy,unsigned cellpxx,unsigned dimy,unsigned dimx,ncplane * parent,const ncvisual * ncv,ncscale_e scaling,unsigned * disppixy,unsigned * disppixx,uint64_t flags,unsigned * outy,unsigned * outx,int * placey,int * placex,int pxoffy,int pxoffx)148 shape_sprixel_plane(const tinfo* ti, unsigned cellpxy, unsigned cellpxx,
149                     unsigned dimy, unsigned dimx,
150                     ncplane* parent, const ncvisual* ncv,
151                     ncscale_e scaling, unsigned* disppixy, unsigned* disppixx,
152                     uint64_t flags, unsigned* outy, unsigned* outx,
153                     int* placey, int* placex, int pxoffy, int pxoffx){
154   if(scaling != NCSCALE_NONE && scaling != NCSCALE_NONE_HIRES){
155     // disppixy/disppix are treated initially as cells
156     if(parent == NULL){
157       *disppixy = dimy;
158       *disppixx = dimx;
159     }else{
160       ncplane_dim_yx(parent, disppixy, disppixx);
161     }
162     // FIXME why do we clamp only vertical, not horizontal, here?
163     if(*placey + *disppixy >= dimy){
164       *disppixy = dimy - *placey;
165     }
166     if(!(flags & NCVISUAL_OPTION_VERALIGNED)){
167       *disppixy -= *placey;
168     }
169     if(!(flags & NCVISUAL_OPTION_HORALIGNED)){
170       *disppixx -= *placex;
171     }
172     *disppixx *= cellpxx;
173     *disppixy *= cellpxy;
174     *disppixx += pxoffx;
175     *disppixy += pxoffy;
176     *outx = *disppixx;
177     clamp_to_sixelmax(ti, disppixy, disppixx, outy, scaling);
178     if(scaling == NCSCALE_SCALE || scaling == NCSCALE_SCALE_HIRES){
179       scale_visual(ncv, disppixy, disppixx); // can only shrink
180       *outx = *disppixx;
181       clamp_to_sixelmax(ti, disppixy, disppixx, outy, scaling);
182     }
183   }else{
184     *disppixx = ncv->pixx + pxoffx;
185     *disppixy = ncv->pixy + pxoffy;
186     *outx = *disppixx;
187     clamp_to_sixelmax(ti, disppixy, disppixx, outy, scaling);
188   }
189   // pixel offsets ought be counted for clamping purposes, but not returned
190   // as part of the scaled geometry (they are included in outx/outy).
191   *disppixy -= pxoffy;
192   *disppixx -= pxoffx;
193 }
194 
195 // in addition to the fields in 'geom', we pass out:
196 //  * 'disppixx'/'disppixy': scaled output size in pixels
197 //  * 'outy'/'outx': true output size in pixels (ie post-sixel clamping)
198 //  * 'placey'/'placex': offset at which to draw
199 //  * 'bset': blitter that will be used
200 // we take in:
201 //  * 'p': target pile (for cell-pixel and cell geometry)
202 //  * 'ti': used if p is NULL (direct mode only!)
203 //  * 'n': input ncvisual
204 //  * 'vopts': requested ncvisual_options
ncvisual_geom_inner(const tinfo * ti,const ncvisual * n,const struct ncvisual_options * vopts,ncvgeom * geom,const struct blitset ** bset,unsigned * disppixy,unsigned * disppixx,unsigned * outy,unsigned * outx,int * placey,int * placex)205 int ncvisual_geom_inner(const tinfo* ti, const ncvisual* n,
206                         const struct ncvisual_options* vopts, ncvgeom* geom,
207                         const struct blitset** bset,
208                         unsigned* disppixy, unsigned* disppixx,
209                         unsigned* outy, unsigned* outx,
210                         int* placey, int* placex){
211   if(ti == NULL && n == NULL){
212     logerror("got NULL for both sources\n");
213     return -1;
214   }
215   struct ncvisual_options fakevopts;
216   if(vopts == NULL){
217     memset(&fakevopts, 0, sizeof(fakevopts));
218     vopts = &fakevopts;
219   }
220   // check basic vopts preconditions
221   if(vopts->flags >= (NCVISUAL_OPTION_NOINTERPOLATE << 1u)){
222     logwarn("warning: unknown ncvisual options %016" PRIx64 "\n", vopts->flags);
223   }
224   if((vopts->flags & NCVISUAL_OPTION_CHILDPLANE) && !vopts->n){
225     logerror("requested child plane with NULL n\n");
226     return -1;
227   }
228   if(vopts->flags & NCVISUAL_OPTION_HORALIGNED){
229     if(vopts->x < NCALIGN_UNALIGNED || vopts->x > NCALIGN_RIGHT){
230       logerror("bad x %d for horizontal alignment\n", vopts->x);
231       return -1;
232     }
233   }
234   if(vopts->flags & NCVISUAL_OPTION_VERALIGNED){
235     if(vopts->y < NCALIGN_UNALIGNED || vopts->y > NCALIGN_RIGHT){
236       logerror("bad y %d for vertical alignment\n", vopts->y);
237       return -1;
238     }
239   }
240   if(n){
241     geom->pixy = n->pixy;
242     geom->pixx = n->pixx;
243   }
244   // when ti is NULL, we only report properties intrinsic to the ncvisual,
245   // i.e. only its original pixel geometry.
246   if(ti == NULL){
247     return 0;
248   }
249   // determine our blitter
250   *bset = rgba_blitter(ti, vopts);
251   if(!*bset){
252     logerror("couldn't get a blitter for %d\n", vopts ? vopts->blitter : NCBLIT_DEFAULT);
253     return -1;
254   }
255   const ncpile* p = vopts->n ? ncplane_pile_const(vopts->n) : NULL;
256   geom->cdimy = p ? p->cellpxy : ti->cellpxy;
257   geom->cdimx = p ? p->cellpxx : ti->cellpxx;
258   if((geom->blitter = (*bset)->geom) == NCBLIT_PIXEL){
259     geom->maxpixely = ti->sixel_maxy;
260     geom->maxpixelx = ti->sixel_maxx;
261   }
262   geom->scaley = encoding_y_scale(ti, *bset);
263   geom->scalex = encoding_x_scale(ti, *bset);
264   // when n is NULL, we only report properties unrelated to the ncvisual,
265   // i.e. the cell-pixel geometry, max bitmap geometry, blitter, and scaling.
266   if(n == NULL){
267     return 0;
268   }
269   ncscale_e scaling = vopts ? vopts->scaling : NCSCALE_NONE;
270   // determine how much of the original image we're using (leny/lenx)
271   ncvisual_origin(vopts, &geom->begy, &geom->begx);
272   geom->lenx = vopts->lenx;
273   geom->leny = vopts->leny;
274   *placey = vopts->y;
275   *placex = vopts->x;
276   logdebug("vis %ux%u+%ux%u %p\n", geom->begy, geom->begx, geom->leny, geom->lenx, n->data);
277   if(n->data == NULL){
278     logerror("no data in visual\n");
279     return -1;
280   }
281   if(geom->begx >= n->pixx || geom->begy >= n->pixy){
282     logerror("visual too large %u > %d or %u > %d\n", geom->begy, n->pixy, geom->begx, n->pixx);
283     return -1;
284   }
285   if(geom->lenx == 0){ // 0 means "to the end"; use all available source material
286     geom->lenx = n->pixx - geom->begx;
287   }
288   if(geom->leny == 0){
289     geom->leny = n->pixy - geom->begy;
290   }
291   if(geom->lenx <= 0 || geom->leny <= 0){ // no need to draw zero-size object, exit
292     logerror("zero-size object %d %d\n", geom->leny, geom->lenx);
293     return -1;
294   }
295   if(geom->begx + geom->lenx > n->pixx || geom->begy + geom->leny > n->pixy){
296     logerror("geometry too large %d > %d or %d > %d\n", geom->begy + geom->leny, n->pixy, geom->begx + geom->lenx, n->pixx);
297     return -1;
298   }
299   if((*bset)->geom == NCBLIT_PIXEL){
300     if(vopts->n){
301       // FIXME does this work from direct mode?
302       if(vopts->n == notcurses_stdplane_const(ncplane_notcurses_const(vopts->n))){
303         if(!(vopts->flags & NCVISUAL_OPTION_CHILDPLANE)){
304           logerror("won't blit bitmaps to the standard plane\n");
305           return -1;
306         }
307       }
308       if(vopts->y && !(vopts->flags & (NCVISUAL_OPTION_VERALIGNED | NCVISUAL_OPTION_CHILDPLANE))){
309         logerror("non-origin y placement %d for sprixel\n", vopts->y);
310         return -1;
311       }
312       if(vopts->x && !(vopts->flags & (NCVISUAL_OPTION_HORALIGNED | NCVISUAL_OPTION_CHILDPLANE))){
313         logerror("non-origin x placement %d for sprixel\n", vopts->x);
314         return -1;
315       }
316       if(vopts->pxoffy >= geom->cdimy){
317         logerror("pixel y-offset %d too tall for cell %d\n", vopts->pxoffy, geom->cdimy);
318         return -1;
319       }
320       if(vopts->pxoffx >= geom->cdimx){
321         logerror("pixel x-offset %d too wide for cell %d\n", vopts->pxoffx, geom->cdimx);
322         return -1;
323       }
324       if(scaling == NCSCALE_NONE || scaling == NCSCALE_NONE_HIRES){
325         // FIXME clamp to sprixel limits
326         unsigned rows = ((geom->leny + geom->cdimy - 1) / geom->cdimy) + !!vopts->pxoffy;
327         if(rows > ncplane_dim_y(vopts->n)){
328           logerror("sprixel too tall %d for plane %d\n", geom->leny + vopts->pxoffy,
329                    ncplane_dim_y(vopts->n) * geom->cdimy);
330           return -1;
331         }
332         unsigned cols = ((geom->lenx + geom->cdimx - 1) / geom->cdimx) + !!vopts->pxoffx;
333         if(cols > ncplane_dim_x(vopts->n)){
334           logerror("sprixel too wide %d for plane %d\n", geom->lenx + vopts->pxoffx,
335                    ncplane_dim_x(vopts->n) * geom->cdimx);
336           return -1;
337         }
338       }
339     }
340     if(vopts->n == NULL || (vopts->flags & NCVISUAL_OPTION_CHILDPLANE)){
341       // we'll need to create the plane
342       const int dimy = p ? p->dimy : ti->dimy;
343       const int dimx = p ? p->dimx : ti->dimx;
344       shape_sprixel_plane(ti, geom->cdimy, geom->cdimx, dimy, dimx,
345                           vopts->n, n, scaling, disppixy, disppixx,
346                           vopts->flags, outy, outx, placey, placex,
347                           vopts->pxoffy, vopts->pxoffx);
348     }else{
349       if(scaling != NCSCALE_NONE && scaling != NCSCALE_NONE_HIRES){
350         ncplane_dim_yx(vopts->n, disppixy, disppixx);
351         *disppixx *= geom->cdimx;
352         *disppixx += vopts->pxoffx;
353         *disppixy *= geom->cdimy;
354         *disppixy += vopts->pxoffy;
355         clamp_to_sixelmax(ti, disppixy, disppixx, outy, scaling);
356         int absplacex = 0, absplacey = 0;
357         if(!(vopts->flags & NCVISUAL_OPTION_HORALIGNED)){
358           absplacex = *placex;
359         }
360         if(!(vopts->flags & NCVISUAL_OPTION_VERALIGNED)){
361           absplacey = *placey;
362         }
363         *disppixx -= absplacex * geom->cdimx;
364         *disppixy -= absplacey * geom->cdimy;
365       }else{
366         *disppixx = geom->lenx + vopts->pxoffx;
367         *disppixy = geom->leny + vopts->pxoffy;
368       }
369       logdebug("pixel prescale: %d %d %d %d\n", n->pixy, n->pixx, *disppixy, *disppixx);
370       if(scaling == NCSCALE_SCALE || scaling == NCSCALE_SCALE_HIRES){
371         clamp_to_sixelmax(ti, disppixy, disppixx, outy, scaling);
372         scale_visual(n, disppixy, disppixx);
373       }
374       clamp_to_sixelmax(ti, disppixy, disppixx, outy, scaling);
375       // FIXME use a closed form
376       while((*outy + geom->cdimy - 1) / geom->cdimy > ncplane_dim_y(vopts->n)){
377         *outy -= ti->sprixel_scale_height;
378         *disppixy = *outy;
379       }
380       *outx = *disppixx;
381       *disppixx -= vopts->pxoffx;
382       *disppixy -= vopts->pxoffy;
383     }
384     logdebug("pblit: %dx%d ← %dx%d of %d/%d stride %u @%dx%d %p %u\n", *disppixy, *disppixx, geom->begy, geom->begx, n->pixy, n->pixx, n->rowstride, *placey, *placex, n->data, geom->cdimx);
385     geom->rpixy = *disppixy;
386     geom->rpixx = *disppixx;
387     geom->rcellx = *outx / geom->cdimx + !!(*outx % geom->cdimx);
388     geom->rcelly = *outy / geom->cdimy + !!(*outy % geom->cdimy);
389   }else{ // cellblit
390     if(vopts->pxoffx || vopts->pxoffy){
391       logerror("pixel offsets cannot be used with cell blitting\n");
392       return -1;
393     }
394     unsigned dispcols, disprows;
395     if(vopts->n == NULL || (vopts->flags & NCVISUAL_OPTION_CHILDPLANE)){ // create plane
396 //fprintf(stderr, "CPATH1, create beg %dx%d len %dx%d\n", geom->begy, geom->begx, geom->leny, geom->lenx);
397       if(scaling == NCSCALE_NONE || scaling == NCSCALE_NONE_HIRES){
398         dispcols = geom->lenx;
399         disprows = geom->leny;
400       }else{
401         if(vopts->n == NULL){
402           disprows = ti->dimy;
403           dispcols = ti->dimx;
404         }else{
405           ncplane_dim_yx(vopts->n, &disprows, &dispcols);
406         }
407         dispcols *= geom->scalex;
408         disprows *= geom->scaley;
409         if(scaling == NCSCALE_SCALE || scaling == NCSCALE_SCALE_HIRES){
410           scale_visual(n, &disprows, &dispcols);
411         } // else stretch
412       }
413     }else{
414 //fprintf(stderr, "CPATH2, reuse beg %dx%d len %dx%d\n", geom->begy, geom->begx, geom->leny, geom->lenx);
415       if(scaling == NCSCALE_NONE || scaling == NCSCALE_NONE_HIRES){
416         dispcols = geom->lenx;
417         disprows = geom->leny;
418       }else{
419         ncplane_dim_yx(vopts->n, &disprows, &dispcols);
420         dispcols *= geom->scalex;
421         disprows *= geom->scaley;
422         if(!(vopts->flags & NCVISUAL_OPTION_HORALIGNED)){
423           dispcols -= *placex;
424         }
425         if(!(vopts->flags & NCVISUAL_OPTION_VERALIGNED)){
426           disprows -= *placey;
427         }
428         if(scaling == NCSCALE_SCALE || scaling == NCSCALE_SCALE_HIRES){
429           scale_visual(n, &disprows, &dispcols);
430         } // else stretch
431       }
432       if(vopts->flags & NCVISUAL_OPTION_HORALIGNED){
433         *placex = ncplane_halign(vopts->n, *placex, dispcols / geom->scalex);
434       }
435       if(vopts->flags & NCVISUAL_OPTION_VERALIGNED){
436         *placey = ncplane_valign(vopts->n, *placey, disprows / geom->scaley);
437       }
438     }
439     geom->rpixy = disprows;
440     geom->rpixx = dispcols;
441     geom->rcellx = dispcols / geom->scalex + !!(dispcols % geom->scalex);
442     geom->rcelly = disprows / geom->scaley + !!(disprows % geom->scaley);
443   }
444   logdebug("rgeom: %d %d %d %d @ %d/%d (%d on %p)\n", geom->rcelly, geom->rcellx,
445            geom->rpixy, geom->rpixx, *placey, *placex, (*bset)->geom, vopts->n);
446   return 0;
447 }
448 
ncvisual_geom(const notcurses * nc,const ncvisual * n,const struct ncvisual_options * vopts,ncvgeom * geom)449 int ncvisual_geom(const notcurses* nc, const ncvisual* n,
450                   const struct ncvisual_options* vopts, ncvgeom* geom){
451   const struct blitset* bset;
452   unsigned disppxy, disppxx, outy, outx;
453   int placey, placex;
454   return ncvisual_geom_inner(nc ? &nc->tcache : NULL, n, vopts, geom, &bset,
455                              &disppxy, &disppxx, &outy, &outx, &placey, &placex);
456 }
457 
rgb_loose_to_rgba(const void * data,int rows,int * rowstride,int cols,int alpha)458 void* rgb_loose_to_rgba(const void* data, int rows, int* rowstride, int cols, int alpha){
459   if(*rowstride % 4){ // must be a multiple of 4 bytes
460     return NULL;
461   }
462   if(*rowstride < cols * 4){
463     return NULL;
464   }
465   uint32_t* ret = malloc(4 * cols * rows);
466   if(ret){
467     for(int y = 0 ; y < rows ; ++y){
468       for(int x = 0 ; x < cols ; ++x){
469         const uint32_t* src = (const uint32_t*)data + (*rowstride / 4) * y + x;
470         uint32_t* dst = ret + cols * y + x;
471         ncpixel_set_a(dst, alpha);
472         ncpixel_set_r(dst, ncpixel_r(*src));
473         ncpixel_set_g(dst, ncpixel_g(*src));
474         ncpixel_set_b(dst, ncpixel_b(*src));
475       }
476     }
477   }
478   *rowstride = cols * 4;
479   return ret;
480 }
481 
rgb_packed_to_rgba(const void * data,int rows,int * rowstride,int cols,int alpha)482 void* rgb_packed_to_rgba(const void* data, int rows, int* rowstride, int cols, int alpha){
483   if(*rowstride < cols * 3){
484     return NULL;
485   }
486   uint32_t* ret = malloc(4 * cols * rows);
487   if(ret){
488     for(int y = 0 ; y < rows ; ++y){
489       for(int x = 0 ; x < cols ; ++x){
490         const unsigned char* src = (const unsigned char*)data + *rowstride * y + x;
491         uint32_t* dst = ret + cols * y + x;
492         ncpixel_set_a(dst, alpha);
493         ncpixel_set_r(dst, src[0]);
494         ncpixel_set_g(dst, src[1]);
495         ncpixel_set_b(dst, src[2]);
496       }
497     }
498   }
499   *rowstride = cols * 4;
500   return ret;
501 }
502 
bgra_to_rgba(const void * data,int rows,int * rowstride,int cols,int alpha)503 void* bgra_to_rgba(const void* data, int rows, int* rowstride, int cols, int alpha){
504   if(*rowstride % 4){ // must be a multiple of 4 bytes
505     return NULL;
506   }
507   if(*rowstride < cols * 4){
508     return NULL;
509   }
510   uint32_t* ret = malloc(4 * cols * rows);
511   if(ret){
512     for(int y = 0 ; y < rows ; ++y){
513       for(int x = 0 ; x < cols ; ++x){
514         const uint32_t* src = (const uint32_t*)data + (*rowstride / 4) * y + x;
515         uint32_t* dst = ret + cols * y + x;
516         ncpixel_set_a(dst, alpha);
517         ncpixel_set_r(dst, ncpixel_b(*src));
518         ncpixel_set_g(dst, ncpixel_g(*src));
519         ncpixel_set_b(dst, ncpixel_r(*src));
520       }
521     }
522   }
523   *rowstride = cols * 4;
524   return ret;
525 }
526 
527 // Inspects the visual to find the minimum rectangle that can contain all
528 // "real" pixels, where "real" pixels are, by convention, all zeroes.
529 // Placing this box at offyXoffx relative to the visual will encompass all
530 // pixels. Returns the area of the box (0 if there are no pixels).
ncvisual_bounding_box(const ncvisual * ncv,int * leny,int * lenx,int * offy,int * offx)531 int ncvisual_bounding_box(const ncvisual* ncv, int* leny, int* lenx,
532                           int* offy, int* offx){
533   unsigned lcol = 0;
534   unsigned rcol = UINT_MAX;
535   unsigned trow;
536   // first, find the topmost row with a real pixel. if there is no such row,
537   // there are no such pixels. if we find one, we needn't look in this region
538   // for other extrema, so long as we keep the leftmost and rightmost through
539   // this row (from the top). said leftmost and rightmost will be the leftmost
540   // and rightmost pixel of whichever row has the topmost valid pixel. unlike
541   // the topmost, they'll need be further verified.
542   for(trow = 0 ; trow < ncv->pixy ; ++trow){
543     for(unsigned x = 0 ; x < ncv->pixx ; ++x){
544       uint32_t rgba = ncv->data[trow * ncv->rowstride / 4 + x];
545       if(rgba){
546         lcol = x; // leftmost pixel of topmost row
547         // now find rightmost pixel of topmost row
548         unsigned xr;
549         for(xr = ncv->pixx - 1 ; xr > x ; --xr){
550           rgba = ncv->data[trow * ncv->rowstride / 4 + xr];
551           if(rgba){ // rightmost pixel of topmost row
552             break;
553           }
554         }
555         rcol = xr;
556         break;
557       }
558     }
559     if(rcol < INT_MAX){
560       break;
561     }
562   }
563   if(trow == ncv->pixy){ // no real pixels
564     *leny = 0;
565     *lenx = 0;
566     *offy = 0;
567     *offx = 0;
568   }else{
569     assert(rcol < ncv->pixx);
570     // we now know topmost row, and left/rightmost through said row. now we must
571     // find the bottommost row, checking left/rightmost throughout.
572     unsigned brow;
573     for(brow = ncv->pixy - 1 ; brow > trow ; --brow){
574       unsigned x;
575       for(x = 0 ; x < ncv->pixx ; ++x){
576         uint32_t rgba = ncv->data[brow * ncv->rowstride / 4 + x];
577         if(rgba){
578           if(x < lcol){
579             lcol = x;
580           }
581           unsigned xr;
582           for(xr = ncv->pixx - 1 ; xr > x && xr > rcol ; --xr){
583             rgba = ncv->data[brow * ncv->rowstride / 4 + xr];
584             if(rgba){ // rightmost pixel of bottommost row
585               if(xr > rcol){
586                 rcol = xr;
587               }
588               break;
589             }
590           }
591           break;
592         }
593       }
594       if(x < ncv->pixx){
595         break;
596       }
597     }
598     // we now know topmost and bottommost row, and left/rightmost within those
599     // two sections. now check the rest for left and rightmost.
600     for(unsigned y = trow + 1 ; y < brow ; ++y){
601       for(unsigned x = 0 ; x < lcol ; ++x){
602         uint32_t rgba = ncv->data[y * ncv->rowstride / 4 + x];
603         if(rgba){
604           lcol = x;
605           break;
606         }
607       }
608       for(unsigned x = ncv->pixx - 1 ; x > rcol ; --x){
609         uint32_t rgba = ncv->data[y * ncv->rowstride / 4 + x];
610         if(rgba){
611           rcol = x;
612           break;
613         }
614       }
615     }
616     *offy = trow;
617     *leny = brow - trow + 1;
618     *offx = lcol;
619     *lenx = rcol - lcol + 1;
620   }
621   return *leny * *lenx;
622 }
623 
624 // find the "center" cell of a visual. in the case of even rows/columns, we
625 // place the center on the top/left. in such a case there will be one more
626 // cell to the bottom/right of the center.
627 static inline void
ncvisual_center(const ncvisual * n,int * RESTRICT y,int * RESTRICT x)628 ncvisual_center(const ncvisual* n, int* RESTRICT y, int* RESTRICT x){
629   *y = n->pixy;
630   *x = n->pixx;
631   center_box(y, x);
632 }
633 
634 // rotate the 0-indexed (origin-indexed) ['y', 'x'] through 'ctheta' and
635 // 'stheta' around the centerpoint at ['centy', 'centx']. write the results
636 // back to 'y' and 'x'.
637 static void
rotate_point(int * y,int * x,double stheta,double ctheta,int centy,int centx)638 rotate_point(int* y, int* x, double stheta, double ctheta, int centy, int centx){
639   // convert coordinates from origin to left-handed cartesian
640   const int convx = *x - centx;
641   const int convy = *y - centy;
642 //fprintf(stderr, "%d, %d -> conv %d, %d\n", *y, *x, convy, convx);
643   *x = round(convx * ctheta - convy * stheta);
644   *y = round(convx * stheta + convy * ctheta);
645 }
646 
647 // rotate the specified bounding box by the specified sine and cosine of some
648 // theta radians, enlarging or shrinking it as necessary. returns the area.
649 // 'leny', 'lenx', 'offy', and 'offx' describe the bounding box to be rotated,
650 // and might all be updated (in either direction).
651 static int
rotate_bounding_box(double stheta,double ctheta,int * leny,int * lenx,int * offy,int * offx)652 rotate_bounding_box(double stheta, double ctheta, int* leny, int* lenx,
653                     int* offy, int* offx){
654 //fprintf(stderr, "Incoming bounding box: %dx%d @ %dx%d rotate s(%f) c(%f)\n", *leny, *lenx, *offy, *offx, stheta, ctheta);
655   int xs[4], ys[4]; // x and y locations of rotated coordinates
656   int centy = *leny;
657   int centx = *lenx;
658   center_box(&centy, &centx);
659   ys[0] = 0;
660   xs[0] = 0;
661   rotate_point(ys, xs, stheta, ctheta, centy, centx);
662 //fprintf(stderr, "rotated %d, %d -> %d %d\n", 0, 0, ys[0], xs[0]);
663   ys[1] = 0;
664   xs[1] = *lenx - 1;
665   rotate_point(ys + 1, xs + 1, stheta, ctheta, centy, centx);
666 //fprintf(stderr, "rotated %d, %d -> %d %d\n", 0, *lenx - 1, ys[1], xs[1]);
667   ys[2] = *leny - 1;
668   xs[2] = *lenx - 1;
669   rotate_point(ys + 2, xs + 2, stheta, ctheta, centy, centx);
670 //fprintf(stderr, "rotated %d, %d -> %d %d\n", *leny - 1, *lenx - 1, ys[2], xs[2]);
671   ys[3] = *leny - 1;
672   xs[3] = 0;
673   rotate_point(ys + 3, xs + 3, stheta, ctheta, centy, centx);
674 //fprintf(stderr, "rotated %d, %d -> %d %d\n", *leny - 1, 0, ys[3], xs[3]);
675   int trow = ys[0];
676   int brow = ys[0];
677   int lcol = xs[0];
678   int rcol = xs[0];
679   for(size_t i = 1 ; i < sizeof(xs) / sizeof(*xs) ; ++i){
680     if(xs[i] < lcol){
681       lcol = xs[i];
682     }
683     if(xs[i] > rcol){
684       rcol = xs[i];
685     }
686     if(ys[i] < trow){
687       trow = ys[i];
688     }
689     if(ys[i] > brow){
690       brow = ys[i];
691     }
692   }
693   *offy = trow;
694   *leny = brow - trow + 1;
695   *offx = lcol;
696   *lenx = rcol - lcol + 1;
697 //fprintf(stderr, "Rotated bounding box: %dx%d @ %dx%d\n", *leny, *lenx, *offy, *offx);
698   return *leny * *lenx;
699 }
700 
ncvisual_rotate(ncvisual * ncv,double rads)701 int ncvisual_rotate(ncvisual* ncv, double rads){
702   assert(ncv->rowstride / 4 >= ncv->pixx);
703   rads = -rads; // we're a left-handed Cartesian
704   int centy, centx;
705   ncvisual_center(ncv, &centy, &centx); // pixel center (center of 'data')
706   double stheta, ctheta; // sine, cosine
707   stheta = sin(rads);
708   ctheta = cos(rads);
709   // bounding box for real data within the ncvisual. we must only resize to
710   // accommodate real data, lest we grow without band as we rotate.
711   // see https://github.com/dankamongmen/notcurses/issues/599.
712   int bby = ncv->pixy;
713   int bbx = ncv->pixx;
714   int bboffy = 0;
715   int bboffx = 0;
716   if(ncvisual_bounding_box(ncv, &bby, &bbx, &bboffy, &bboffx) <= 0){
717     logerror("Couldn't find a bounding box\n");
718     return -1;
719   }
720   int bbarea;
721   bbarea = rotate_bounding_box(stheta, ctheta, &bby, &bbx, &bboffy, &bboffx);
722   if(bbarea <= 0){
723     logerror("Couldn't rotate the visual (%d, %d, %d, %d)\n", bby, bbx, bboffy, bboffx);
724     return -1;
725   }
726   int bbcentx = bbx, bbcenty = bby;
727   center_box(&bbcenty, &bbcentx);
728 //fprintf(stderr, "stride: %d height: %d width: %d\n", ncv->rowstride, ncv->pixy, ncv->pixx);
729   assert(ncv->rowstride / 4 >= ncv->pixx);
730   uint32_t* data = malloc(bbarea * 4);
731   if(data == NULL){
732     return -1;
733   }
734   memset(data, 0, bbarea * 4);
735 //fprintf(stderr, "bbarea: %d bby: %d bbx: %d centy: %d centx: %d bbcenty: %d bbcentx: %d\n", bbarea, bby, bbx, centy, centx, bbcenty, bbcentx);
736   for(unsigned y = 0 ; y < ncv->pixy ; ++y){
737       for(unsigned x = 0 ; x < ncv->pixx ; ++x){
738       int targx = x, targy = y;
739       rotate_point(&targy, &targx, stheta, ctheta, centy, centx);
740       if(targx > bboffx && targy > bboffy){
741         const int deconvx = targx - bboffx;
742         const int deconvy = targy - bboffy;
743         if(deconvy < bby && deconvx < bbx){
744           data[deconvy * bbx + deconvx] = ncv->data[y * (ncv->rowstride / 4) + x];
745         }
746       }
747 //fprintf(stderr, "CW: %d/%d (%08x) -> %d/%d (stride: %d)\n", y, x, ncv->data[y * (ncv->rowstride / 4) + x], targy, targx, ncv->rowstride);
748 //fprintf(stderr, "wrote %08x to %d (%d)\n", data[targy * ncv->pixy + targx], targy * ncv->pixy + targx, (targy * ncv->pixy + targx) * 4);
749     }
750   }
751   ncvisual_set_data(ncv, data, true);
752   ncv->pixx = bbx;
753   ncv->pixy = bby;
754   ncv->rowstride = bbx * 4;
755   ncvisual_details_seed(ncv);
756   return 0;
757 }
758 
759 static inline size_t
pad_for_image(size_t stride,int cols)760 pad_for_image(size_t stride, int cols){
761   if(visual_implementation->rowalign == 0){
762     return 4 * cols;
763   }else if(stride < cols * 4u){
764     return (4 * cols + visual_implementation->rowalign) /
765             visual_implementation->rowalign * visual_implementation->rowalign;
766   }else if(stride % visual_implementation->rowalign == 0){
767     return stride;
768   }
769   return (stride + visual_implementation->rowalign) /
770           visual_implementation->rowalign * visual_implementation->rowalign;
771 }
772 
ncvisual_from_rgba(const void * rgba,int rows,int rowstride,int cols)773 ncvisual* ncvisual_from_rgba(const void* rgba, int rows, int rowstride, int cols){
774   if(rowstride % 4){
775     logerror("Rowstride %d not a multiple of 4\n", rowstride);
776     return NULL;
777   }
778   ncvisual* ncv = ncvisual_create();
779   if(ncv){
780     // ffmpeg needs inputs with rows aligned on 192-byte boundaries
781     ncv->rowstride = pad_for_image(rowstride, cols);
782     ncv->pixx = cols;
783     ncv->pixy = rows;
784     uint32_t* data = malloc(ncv->rowstride * ncv->pixy);
785     if(data == NULL){
786       ncvisual_destroy(ncv);
787       return NULL;
788     }
789     for(int y = 0 ; y < rows ; ++y){
790 //fprintf(stderr, "ROWS: %d STRIDE: %d (%d) COLS: %d %08x\n", ncv->pixy, ncv->rowstride, rowstride, cols, data[ncv->rowstride * y / 4]);
791       memcpy(data + (ncv->rowstride * y) / 4, (const char*)rgba + rowstride * y, rowstride);
792     }
793     ncvisual_set_data(ncv, data, true);
794     ncvisual_details_seed(ncv);
795   }
796   return ncv;
797 }
798 
ncvisual_from_rgb_packed(const void * rgba,int rows,int rowstride,int cols,int alpha)799 ncvisual* ncvisual_from_rgb_packed(const void* rgba, int rows, int rowstride,
800                                    int cols, int alpha){
801   ncvisual* ncv = ncvisual_create();
802   if(ncv){
803     ncv->rowstride = pad_for_image(cols * 4, cols);
804     ncv->pixx = cols;
805     ncv->pixy = rows;
806     uint32_t* data = malloc(ncv->rowstride * ncv->pixy);
807     if(data == NULL){
808       ncvisual_destroy(ncv);
809       return NULL;
810     }
811     const unsigned char* src = rgba;
812     for(int y = 0 ; y < rows ; ++y){
813 //fprintf(stderr, "ROWS: %d STRIDE: %d (%d) COLS: %d %08x\n", ncv->pixy, ncv->rowstride, ncv->rowstride / 4, cols, data[ncv->rowstride * y / 4]);
814       for(int x = 0 ; x < cols ; ++x){
815         unsigned char r, g, b;
816         memcpy(&r, src + rowstride * y + 3 * x, 1);
817         memcpy(&g, src + rowstride * y + 3 * x + 1, 1);
818         memcpy(&b, src + rowstride * y + 3 * x + 2, 1);
819         ncpixel_set_a(&data[y * ncv->rowstride / 4 + x], alpha);
820         ncpixel_set_r(&data[y * ncv->rowstride / 4 + x], r);
821         ncpixel_set_g(&data[y * ncv->rowstride / 4 + x], g);
822         ncpixel_set_b(&data[y * ncv->rowstride / 4 + x], b);
823 //fprintf(stderr, "RGBA: 0x%02x 0x%02x 0x%02x 0x%02x\n", r, g, b, alpha);
824       }
825     }
826     ncvisual_set_data(ncv, data, true);
827     ncvisual_details_seed(ncv);
828   }
829   return ncv;
830 }
831 
ncvisual_from_rgb_loose(const void * rgba,int rows,int rowstride,int cols,int alpha)832 ncvisual* ncvisual_from_rgb_loose(const void* rgba, int rows, int rowstride,
833                                   int cols, int alpha){
834   if(rowstride % 4){
835     logerror("Rowstride %d not a multiple of 4\n", rowstride);
836     return NULL;
837   }
838   ncvisual* ncv = ncvisual_create();
839   if(ncv){
840     ncv->rowstride = pad_for_image(cols * 4, cols);
841     ncv->pixx = cols;
842     ncv->pixy = rows;
843     uint32_t* data = malloc(ncv->rowstride * ncv->pixy);
844     if(data == NULL){
845       ncvisual_destroy(ncv);
846       return NULL;
847     }
848     for(int y = 0 ; y < rows ; ++y){
849 //fprintf(stderr, "ROWS: %d STRIDE: %d (%d) COLS: %d %08x\n", ncv->pixy, ncv->rowstride, ncv->rowstride / 4, cols, data[ncv->rowstride * y / 4]);
850       memcpy(data + (ncv->rowstride * y) / 4, (const char*)rgba + rowstride * y, rowstride);
851       for(int x = 0 ; x < cols ; ++x){
852         ncpixel_set_a(&data[y * ncv->rowstride / 4 + x], alpha);
853       }
854     }
855     ncvisual_set_data(ncv, data, true);
856     ncvisual_details_seed(ncv);
857   }
858   return ncv;
859 }
860 
ncvisual_from_bgra(const void * bgra,int rows,int rowstride,int cols)861 ncvisual* ncvisual_from_bgra(const void* bgra, int rows, int rowstride, int cols){
862   if(rowstride % 4){
863     return NULL;
864   }
865   ncvisual* ncv = ncvisual_create();
866   if(ncv){
867     ncv->rowstride = pad_for_image(rowstride, cols);
868     ncv->pixx = cols;
869     ncv->pixy = rows;
870     uint32_t* data = malloc(ncv->rowstride * ncv->pixy);
871     if(data == NULL){
872       ncvisual_destroy(ncv);
873       return NULL;
874     }
875     for(int y = 0 ; y < rows ; ++y){
876       for(int x = 0 ; x < cols ; ++x){
877         uint32_t src;
878         memcpy(&src, (const char*)bgra + y * rowstride + x * 4, 4);
879         uint32_t* dst = &data[ncv->rowstride * y / 4 + x];
880         ncpixel_set_a(dst, ncpixel_a(src));
881         ncpixel_set_r(dst, ncpixel_b(src));
882         ncpixel_set_g(dst, ncpixel_g(src));
883         ncpixel_set_b(dst, ncpixel_r(src));
884 //fprintf(stderr, "BGRA PIXEL: %02x%02x%02x%02x RGBA result: %02x%02x%02x%02x\n", ((const char*)&src)[0], ((const char*)&src)[1], ((const char*)&src)[2], ((const char*)&src)[3], ((const char*)dst)[0], ((const char*)dst)[1], ((const char*)dst)[2], ((const char*)dst)[3]);
885       }
886     }
887     ncvisual_set_data(ncv, data, true);
888     ncvisual_details_seed(ncv);
889   }
890   return ncv;
891 }
892 
ncvisual_from_palidx(const void * pdata,int rows,int rowstride,int cols,int palsize,int pstride,const uint32_t * palette)893 ncvisual* ncvisual_from_palidx(const void* pdata, int rows, int rowstride,
894                                int cols, int palsize, int pstride,
895                                const uint32_t* palette){
896   if(rowstride % pstride){
897     logerror("bad pstride (%d) for rowstride (%d)\n", pstride, rowstride);
898     return NULL;
899   }
900   if(palsize > 256 || palsize <= 0){
901     logerror("palettes size (%d) is unsupported\n", palsize);
902     return NULL;
903   }
904   ncvisual* ncv = ncvisual_create();
905   if(ncv){
906     ncv->rowstride = pad_for_image(rowstride, cols);
907     ncv->pixx = cols;
908     ncv->pixy = rows;
909     uint32_t* data = malloc(ncv->rowstride * ncv->pixy);
910     if(data == NULL){
911       ncvisual_destroy(ncv);
912       return NULL;
913     }
914     for(int y = 0 ; y < rows ; ++y){
915       for(int x = 0 ; x < cols ; ++x){
916         int palidx = ((const unsigned char*)pdata)[y * rowstride + x * pstride];
917         if(palidx >= palsize){
918           free(data);
919           ncvisual_destroy(ncv);
920           logerror("invalid palette idx %d >= %d\n", palidx, palsize);
921           return NULL;
922         }
923         uint32_t src = palette[palidx];
924         uint32_t* dst = &data[ncv->rowstride * y / 4 + x];
925         if(ncchannel_default_p(src)){
926           // FIXME use default color as detected, or just 0xffffff
927           ncpixel_set_a(dst, 255 - palidx);
928           ncpixel_set_r(dst, palidx);
929           ncpixel_set_g(dst, 220 - (palidx / 2));
930           ncpixel_set_b(dst, palidx);
931         }else{
932           *dst = 0;
933         }
934 //fprintf(stderr, "BGRA PIXEL: %02x%02x%02x%02x RGBA result: %02x%02x%02x%02x\n", ((const char*)&src)[0], ((const char*)&src)[1], ((const char*)&src)[2], ((const char*)&src)[3], ((const char*)dst)[0], ((const char*)dst)[1], ((const char*)dst)[2], ((const char*)dst)[3]);
935       }
936     }
937     ncvisual_set_data(ncv, data, true);
938     ncvisual_details_seed(ncv);
939   }
940   return ncv;
941 }
942 
ncvisual_resize(ncvisual * n,int rows,int cols)943 int ncvisual_resize(ncvisual* n, int rows, int cols){
944   if(!visual_implementation->visual_resize){
945     return ncvisual_resize_noninterpolative(n, rows, cols);
946   }
947   if(visual_implementation->visual_resize(n, rows, cols)){
948     return -1;
949   }
950   return 0;
951 }
952 
ncvisual_resize_noninterpolative(ncvisual * n,int rows,int cols)953 int ncvisual_resize_noninterpolative(ncvisual* n, int rows, int cols){
954   size_t dstride = pad_for_image(cols * 4, cols);
955   uint32_t* r = resize_bitmap(n->data, n->pixy, n->pixx, n->rowstride,
956                               rows, cols, dstride);
957   if(r == NULL){
958     return -1;
959   }
960   ncvisual_set_data(n, r, true);
961   n->rowstride = dstride;
962   n->pixy = rows;
963   n->pixx = cols;
964   ncvisual_details_seed(n);
965   return 0;
966 }
967 
968 // by the end, disprows/dispcols refer to the number of source rows/cols (in
969 // pixels), which will be mapped to a region of cells scaled by the encodings).
970 // the blit will begin at placey/placex (in terms of cells). begy/begx define
971 // the origin of the source region to draw (in pixels). leny/lenx define the
972 // geometry of the source region to draw, again in pixels. ncv->pixy and
973 // ncv->pixx define the source geometry in pixels.
ncvisual_render_cells(ncvisual * ncv,const struct blitset * bset,int placey,int placex,ncvgeom * geom,ncplane * n,uint64_t flags,uint32_t transcolor)974 ncplane* ncvisual_render_cells(ncvisual* ncv, const struct blitset* bset,
975                                int placey, int placex,
976                                ncvgeom* geom, ncplane* n,
977                                uint64_t flags, uint32_t transcolor){
978   logdebug("cblit: rows/cols: %dx%d plane: %d/%d pix: %d/%d\n", geom->rcelly, geom->rcellx, ncplane_dim_y(n), ncplane_dim_x(n), geom->rpixy, geom->rpixx);
979   blitterargs bargs;
980   bargs.transcolor = transcolor;
981   bargs.begy = geom->begy;
982   bargs.begx = geom->begx;
983   bargs.leny = geom->leny;
984   bargs.lenx = geom->lenx;
985   bargs.flags = flags;
986   bargs.u.cell.placey = placey;
987   bargs.u.cell.placex = placex;
988   if(ncvisual_blit_internal(ncv, geom->rpixy, geom->rpixx, n, bset, &bargs)){
989     return NULL;
990   }
991   return n;
992 }
993 
994 // when a sprixel is blitted to a plane, that plane becomes a sprixel plane. it
995 // must not be used with other output mechanisms unless erased. the plane will
996 // be shrunk to fit the output, and the output is always placed at the origin.
997 // sprixels cannot be blitted to the standard plane.
998 //
999 // the placey/placex arguments thus refer to the position of the *plane*, not
1000 // the sprixel. if creating a new plane, they will be used to place it. if
1001 // using an existing plane, the plane will be moved. they are interpreted
1002 // relative to the parent plane, as they would be in ncplane_create().
1003 //
1004 // by the end, disppixy/disppixx refer to the number of target rows/cols (in
1005 // pixels), aka the scaled geometry. outy refers to the output height, subject
1006 // to Sixel considerations. leny/lenx refer to the number of source rows/cols
1007 // (likewise in pixels). begy/begx refer to the starting offset within the
1008 // source. the sum of begy+leny must not exceed ncv->rows; the sum of begx+lenx
1009 // must not exceed ncv->cols. these sums define the selected geometry. the
1010 // output width is always equal to the scaled width; it has no distinct name.
ncvisual_render_pixels(notcurses * nc,ncvisual * ncv,const struct blitset * bset,int placey,int placex,const ncvgeom * geom,ncplane * n,uint64_t flags,uint32_t transcolor,int pxoffy,int pxoffx)1011 ncplane* ncvisual_render_pixels(notcurses* nc, ncvisual* ncv, const struct blitset* bset,
1012                                 int placey, int placex, const ncvgeom* geom,
1013                                 ncplane* n, uint64_t flags, uint32_t transcolor,
1014                                 int pxoffy, int pxoffx){
1015   logdebug("pblit: rows/cols: %dx%d plane: %d/%d\n", geom->rcelly, geom->rcellx, ncplane_dim_y(n), ncplane_dim_x(n));
1016   const tinfo* ti = &nc->tcache;
1017   blitterargs bargs;
1018   bargs.transcolor = transcolor;
1019   bargs.begy = geom->begy;
1020   bargs.begx = geom->begx;
1021   bargs.leny = geom->leny;
1022   bargs.lenx = geom->lenx;
1023   bargs.flags = flags;
1024   bargs.u.pixel.colorregs = ti->color_registers;
1025   bargs.u.pixel.pxoffy = pxoffy;
1026   bargs.u.pixel.pxoffx = pxoffx;
1027   bargs.u.pixel.cellpxy = geom->cdimy;
1028   bargs.u.pixel.cellpxx = geom->cdimx;
1029   const ncpile* p = ncplane_pile_const(n);
1030   if(n->sprite == NULL){
1031     if((n->sprite = sprixel_alloc(n, geom->rcelly, geom->rcellx)) == NULL){
1032       return NULL;
1033     }
1034     if((n->tam = create_tam(geom->rcelly, geom->rcellx)) == NULL){
1035       return NULL;;
1036     }
1037   }else{
1038     n->sprite = sprixel_recycle(n);
1039     if(n->sprite->dimy != geom->rcelly || n->sprite->dimx != geom->rcellx){
1040       destroy_tam(n);
1041       if((n->tam = create_tam(geom->rcelly, geom->rcellx)) == NULL){
1042         return NULL;
1043       }
1044     }
1045     n->sprite->dimx = geom->rcellx;
1046     n->sprite->dimy = geom->rcelly;
1047   }
1048   bargs.u.pixel.spx = n->sprite;
1049   // FIXME need to pull off the ncpile's sprixellist if anything below fails!
1050   if(ncvisual_blit_internal(ncv, geom->rpixy, geom->rpixx, n, bset, &bargs)){
1051     return NULL;
1052   }
1053   // if we created the plane earlier, placex/placey were taken into account, and
1054   // zeroed out, thus neither of these will have any effect.
1055   if(flags & NCVISUAL_OPTION_HORALIGNED){
1056     if(placex == NCALIGN_CENTER){
1057       placex = (ncplane_dim_x(ncplane_parent_const(n)) * p->cellpxx - geom->rpixx) / 2 / p->cellpxx;
1058     }else if(placex == NCALIGN_RIGHT){
1059       placex = (ncplane_dim_x(ncplane_parent_const(n)) * p->cellpxx - geom->rpixx) / p->cellpxx;
1060     }
1061     if(placex < 0){
1062       return NULL;
1063     }
1064   }
1065   if(flags & NCVISUAL_OPTION_VERALIGNED){
1066     if(placey == NCALIGN_CENTER){
1067       placey = (ncplane_dim_y(ncplane_parent_const(n)) * p->cellpxy - geom->rpixy) / 2 / p->cellpxy;
1068     }else if(placey == NCALIGN_BOTTOM){
1069       placey = (ncplane_dim_y(ncplane_parent_const(n)) * p->cellpxy - geom->rpixy) / p->cellpxy;
1070     }
1071     if(placey < 0){
1072       return NULL;
1073     }
1074   }
1075   // ncplane_resize() hides any attached sprixel, so lift it (the sprixel) out
1076   // for a moment as we shrink the plane to fit. we keep the origin and move to
1077   // the intended location.
1078   sprixel* s = n->sprite;
1079   n->sprite = NULL;
1080 //fprintf(stderr, "ABOUT TO RESIZE: yoff/xoff: %d/%d\n",  placey, placex);
1081   // FIXME might need shrink down the TAM and kill unnecessary auxvecs
1082   if(ncplane_resize(n, 0, 0, s->dimy, s->dimx, placey, placex, s->dimy, s->dimx)){
1083     // if we blow up here, then we've got a TAM sized to the sprixel, rather
1084     // than the plane. running it through destroy_tam() via ncplane_destroy()
1085     // will use incorrect bounds for scrubbing said TAM. do it manually here.
1086     cleanup_tam(n->tam, geom->rcelly, geom->rcellx);
1087     free(n->tam);
1088     n->tam = NULL;
1089     sprixel_hide(bargs.u.pixel.spx);
1090     return NULL;
1091   }
1092   n->sprite = bargs.u.pixel.spx;
1093 //fprintf(stderr, "RESIZED: %d/%d at %d/%d %p\n", ncplane_dim_y(n), ncplane_dim_x(n), ncplane_y(n), ncplane_x(n), n->sprite);
1094   return n;
1095 }
1096 
ncvisual_blit(notcurses * nc,ncvisual * ncv,const struct ncvisual_options * vopts)1097 ncplane* ncvisual_blit(notcurses* nc, ncvisual* ncv, const struct ncvisual_options* vopts){
1098 //fprintf(stderr, "%p tacache: %p\n", n, n->tacache);
1099   struct ncvisual_options fakevopts;
1100   if(vopts == NULL){
1101     memset(&fakevopts, 0, sizeof(fakevopts));
1102     vopts = &fakevopts;
1103   }
1104   loginfo("inblit %dx%d %d@%d %dx%d @ %dx%d %p\n", ncv->pixy, ncv->pixx, vopts->y, vopts->x,
1105           vopts->leny, vopts->lenx, vopts->begy, vopts->begx, vopts->n);
1106   ncvgeom geom;
1107   const struct blitset* bset;
1108   unsigned disppxy, disppxx, outy, outx;
1109   int placey, placex;
1110   if(ncvisual_geom_inner(&nc->tcache, ncv, vopts, &geom, &bset,
1111                          &disppxy, &disppxx, &outy, &outx,
1112                          &placey, &placex)){
1113     // ncvisual_blitset_geom() emits its own diagnostics, no need for an error here
1114     return NULL;
1115   }
1116   ncplane* n = vopts->n;
1117   uint32_t transcolor = 0;
1118   if(vopts->flags & NCVISUAL_OPTION_ADDALPHA){
1119     transcolor = 0x1000000ull | vopts->transcolor;
1120   }
1121   ncplane* createdn = NULL; // to destroy on error
1122   if(n == NULL || (vopts->flags & NCVISUAL_OPTION_CHILDPLANE)){ // create plane
1123     struct ncplane_options nopts = {
1124       .y = placey,
1125       .x = placex,
1126       .rows = geom.rcelly,
1127       .cols = geom.rcellx,
1128       .userptr = NULL,
1129       .name = geom.blitter == NCBLIT_PIXEL ? "bmap" : "cvis",
1130       .resizecb = NULL,
1131       .flags = 0,
1132     };
1133     if(vopts->flags & NCVISUAL_OPTION_HORALIGNED){
1134       nopts.flags |= NCPLANE_OPTION_HORALIGNED;
1135       nopts.x = vopts->x;
1136     }
1137     if(vopts->flags & NCVISUAL_OPTION_VERALIGNED){
1138       nopts.flags |= NCPLANE_OPTION_VERALIGNED;
1139       nopts.y = vopts->y;
1140     }
1141     loginfo("placing new plane: %d/%d @ %d/%d 0x%016" PRIx64 "\n", nopts.rows, nopts.cols, nopts.y, nopts.x, nopts.flags);
1142     if(n == NULL){
1143       n = ncpile_create(nc, &nopts);
1144     }else{
1145       n = ncplane_create(n, &nopts);
1146     }
1147     if((createdn = n) == NULL){
1148       return NULL;
1149     }
1150     placey = 0;
1151     placex = 0;
1152   }
1153   logdebug("blit to plane %p at %d/%d geom %dx%d\n", n, ncplane_abs_y(n), ncplane_abs_x(n), ncplane_dim_y(n), ncplane_dim_x(n));
1154   if(geom.blitter != NCBLIT_PIXEL){
1155     n = ncvisual_render_cells(ncv, bset, placey, placex,
1156                               &geom, n, vopts->flags, transcolor);
1157   }else{
1158     n = ncvisual_render_pixels(nc, ncv, bset, placey, placex,
1159                                &geom, n,
1160                                vopts->flags, transcolor,
1161                                vopts->pxoffy, vopts->pxoffx);
1162   }
1163   if(n == NULL){
1164     ncplane_destroy(createdn);
1165   }
1166   return n;
1167 }
1168 
ncvisual_from_plane(const ncplane * n,ncblitter_e blit,int begy,int begx,unsigned leny,unsigned lenx)1169 ncvisual* ncvisual_from_plane(const ncplane* n, ncblitter_e blit,
1170                               int begy, int begx,
1171                               unsigned leny, unsigned lenx){
1172   unsigned py, px;
1173   uint32_t* rgba = ncplane_as_rgba(n, blit, begy, begx, leny, lenx, &py, &px);
1174 //fprintf(stderr, "snarg: %d/%d @ %d/%d (%p)\n", leny, lenx, begy, begx, rgba);
1175   if(rgba == NULL){
1176     return NULL;
1177   }
1178   unsigned dimy, dimx;
1179   ncplane_dim_yx(n, &dimy, &dimx);
1180   ncvisual* ncv = ncvisual_from_rgba(rgba, py, px * 4, px);
1181   free(rgba);
1182 //fprintf(stderr, "RETURNING %p\n", ncv);
1183   return ncv;
1184 }
1185 
ncvisual_destroy(ncvisual * ncv)1186 void ncvisual_destroy(ncvisual* ncv){
1187   if(ncv){
1188     if(visual_implementation->visual_destroy == NULL){
1189       if(ncv->owndata){
1190         free(ncv->data);
1191       }
1192       free(ncv);
1193     }else{
1194       visual_implementation->visual_destroy(ncv);
1195     }
1196   }
1197 }
1198 
ncvisual_simple_streamer(ncvisual * ncv,struct ncvisual_options * vopts,const struct timespec * tspec,void * curry)1199 int ncvisual_simple_streamer(ncvisual* ncv, struct ncvisual_options* vopts,
1200                              const struct timespec* tspec, void* curry){
1201   struct ncplane* subtitle = NULL;
1202   int ret = 0;
1203   if(curry){
1204     // FIXME improve this hrmmmmm
1205     ncplane* subncp = curry;
1206     if(subncp->blist){
1207       ncplane_destroy(subncp->blist);
1208       subncp->blist = NULL;
1209     }
1210     subtitle = ncvisual_subtitle_plane(subncp, ncv);
1211   }
1212   if(notcurses_render(ncplane_notcurses(vopts->n))){
1213     return -1;
1214   }
1215   clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, tspec, NULL);
1216   ncplane_destroy(subtitle);
1217   return ret;
1218 }
1219 
ncvisual_set_yx(const struct ncvisual * n,unsigned y,unsigned x,uint32_t pixel)1220 int ncvisual_set_yx(const struct ncvisual* n, unsigned y, unsigned x, uint32_t pixel){
1221   if(y >= n->pixy){
1222     logerror("invalid coordinates %u/%u\n", y, x);
1223     return -1;
1224   }
1225   if(x >= n->pixx){
1226     logerror("invalid coordinates %u/%u\n", y, x);
1227     return -1;
1228   }
1229   n->data[y * (n->rowstride / 4) + x] = pixel;
1230   return 0;
1231 }
1232 
ncvisual_at_yx(const ncvisual * n,unsigned y,unsigned x,uint32_t * pixel)1233 int ncvisual_at_yx(const ncvisual* n, unsigned y, unsigned x, uint32_t* pixel){
1234   if(y >= n->pixy){
1235     logerror("invalid coordinates %u/%u\n", y, x);
1236     return -1;
1237   }
1238   if(x >= n->pixx){
1239     logerror("invalid coordinates %u/%u\n", y, x);
1240     return -1;
1241   }
1242   *pixel = n->data[y * (n->rowstride / 4) + x];
1243   return 0;
1244 }
1245 
1246 // originally i wrote this recursively, at which point it promptly began
1247 // exploding once i multithreaded the [yield] demo. hence the clumsy stack
1248 // and hand-rolled iteration. alas, poor yorick!
1249 static int
ncvisual_polyfill_core(ncvisual * n,unsigned y,unsigned x,uint32_t rgba,uint32_t match)1250 ncvisual_polyfill_core(ncvisual* n, unsigned y, unsigned x, uint32_t rgba, uint32_t match){
1251   struct topolyfill* stack = malloc(sizeof(*stack));
1252   if(stack == NULL){
1253     return -1;
1254   }
1255   stack->y = y;
1256   stack->x = x;
1257   stack->next = NULL;
1258   int ret = 0;
1259   struct topolyfill* s;
1260   do{
1261     s = stack;
1262     stack = s->next;
1263     y = s->y;
1264     x = s->x;
1265     uint32_t* pixel = &n->data[y * (n->rowstride / 4) + x];
1266     if(*pixel == match && *pixel != rgba){
1267       ++ret;
1268     // fprintf(stderr, "%d/%d: setting %08x to %08x\n", y, x, *pixel, rgba);
1269       *pixel = rgba;
1270       if(y){
1271         if(create_polyfill_op(y - 1, x, &stack) == NULL){
1272           goto err;
1273         }
1274       }
1275       if(y + 1 < n->pixy){
1276         if(create_polyfill_op(y + 1, x, &stack) == NULL){
1277           goto err;
1278         }
1279       }
1280       if(x){
1281         if(create_polyfill_op(y, x - 1, &stack) == NULL){
1282           goto err;
1283         }
1284       }
1285       if(x + 1 < n->pixx){
1286         if(create_polyfill_op(y, x + 1, &stack) == NULL){
1287           goto err;
1288         }
1289       }
1290     }
1291     free(s);
1292   }while(stack);
1293   return ret;
1294 
1295 err:
1296   free(s);
1297   while(stack){
1298     s = stack->next;
1299     free(stack);
1300     stack = s;
1301   }
1302   return -1;
1303 }
1304 
ncvisual_polyfill_yx(ncvisual * n,unsigned y,unsigned x,uint32_t rgba)1305 int ncvisual_polyfill_yx(ncvisual* n, unsigned y, unsigned x, uint32_t rgba){
1306   if(y >= n->pixy){
1307     logerror("invalid coordinates %u/%u\n", y, x);
1308     return -1;
1309   }
1310   if(x >= n->pixx){
1311     logerror("invalid coordinates %u/%u\n", y, x);
1312     return -1;
1313   }
1314   uint32_t* pixel = &n->data[y * (n->rowstride / 4) + x];
1315   return ncvisual_polyfill_core(n, y, x, rgba, *pixel);
1316 }
1317 
notcurses_canopen_images(const notcurses * nc)1318 bool notcurses_canopen_images(const notcurses* nc __attribute__ ((unused))){
1319   if(!visual_implementation->canopen_images){
1320     return false;
1321   }
1322   return visual_implementation->canopen_images;
1323 }
1324 
notcurses_canopen_videos(const notcurses * nc)1325 bool notcurses_canopen_videos(const notcurses* nc __attribute__ ((unused))){
1326   if(!visual_implementation->canopen_videos){
1327     return false;
1328   }
1329   return visual_implementation->canopen_videos;
1330 }
1331