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