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(¢y, ¢x);
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, ¢y, ¢x); // 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