From a0b34f70628fcf7433655c76e483c6c41bdd038f Mon Sep 17 00:00:00 2001 From: nick black Date: Fri, 29 Oct 2021 16:28:43 -0400 Subject: [PATCH] [core] introduce ncvisual_geom(), replacing ncvisual_blitter_geom() #1684 --- NEWS.md | 6 +++++ USAGE.md | 43 ++++++++++++++++++++++-------- doc/man/man3/notcurses_visual.3.md | 28 ++++++++++++++----- include/ncpp/Visual.hh | 4 +-- include/notcurses/notcurses.h | 19 +++++++++---- src/demo/eagle.c | 11 +++++--- src/demo/intro.c | 17 +++--------- src/demo/keller.c | 8 +++--- src/demo/luigi.c | 8 +++--- src/demo/normal.c | 14 +++++----- src/demo/yield.c | 5 ++-- src/lib/visual.c | 11 +++++++- src/media/ffmpeg.c | 8 +++--- src/poc/rotate.c | 6 ++--- src/tests/media.cpp | 10 ++++--- 15 files changed, 130 insertions(+), 68 deletions(-) diff --git a/NEWS.md b/NEWS.md index d9f933369..f51978edd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,6 +12,12 @@ rearrangements of Notcurses. deprecated functionality, ABI3 ought require small changes, if any. * 2.4.9 (not yet released) + * `ncvisual_geom()` has been introduced, using the `ncvgeom` struct + introduced for direct mode. This allows complete statement of geometry + for an `ncvisual`. It replaces `ncvisual_blitter_geom()`, which has been + deprecated, and will be removed in ABI3. It furthermore exposes some of + the information previously available only from `ncplane_pixelgeom()`, + though that function continues to be supported. * On transition between `ncplane`s (on terminals implementing complex wide glyphs), Notcurses now always issues an `hpa` sequence to force horizontal positioning. This fixes a number of longstanding bugs in e.g. the diff --git a/USAGE.md b/USAGE.md index 9c9d3eb37..76b492f70 100644 --- a/USAGE.md +++ b/USAGE.md @@ -3299,17 +3299,38 @@ Various transformations can be applied to an `ncvisual`, regardless of how it was built up: ```c -// Get the size and ratio of ncvisual pixels to output cells along the y -// and x axes. The input size (in pixels) will be written to 'y' and 'x'. -// The scaling will be written to 'scaley' and 'scalex'. With these: -// rows = (y / scaley) + !!(y % scaley) or (y + scaley - 1) / scaley -// cols = (x / scalex) + !!(x % scalex) or (x + scalex - 1) / scalex -// Returns non-zero for an invalid 'vopts'. The blitter that will be used -// is returned in '*blitter'. -// These results are invalidated upon a terminal resize. -int ncvisual_blitter_geom(const struct notcurses* nc, const struct ncvisual* n, - const struct ncvisual_options* vopts, int* y, int* x, - int* scaley, int* scalex, ncblitter_e* blitter); +// describes all geometries of an ncvisual: those which are inherent, and those +// dependent upon a given rendering regime. pixy and pixx are the true internal +// pixel geometry, taken directly from the load (and updated by +// ncvisual_resize()). cdimy/cdimx are the cell-pixel geometry *at the time +// of this call* (it can change with a font change, in which case all values +// other than pixy/pixx are invalidated). rpixy/rpixx are the pixel geometry as +// handed to the blitter, following any scaling. scaley/scalex are the number +// of input pixels drawn to a single cell; when using NCBLIT_PIXEL, they are +// equivalent to cdimy/cdimx. rcelly/rcellx are the cell geometry as written by +// the blitter, following any padding (there is padding whenever rpix{y, x} is +// not evenly divided by scale{y, x}, and also sometimes for Sixel). +// maxpixely/maxpixelx are defined only when NCBLIT_PIXEL is used, and specify +// the largest bitmap that the terminal is willing to accept. blitter is the +// blitter which will be used, a function of the requested blitter and the +// blitters actually supported by this environment. if no ncvisual was +// supplied, only cdimy/cdimx are filled in. +typedef struct ncvgeom { + int pixy, pixx; // true pixel geometry of ncvisual data + int cdimy, cdimx; // terminal cell geometry when this was calculated + int rpixy, rpixx; // rendered pixel geometry (per visual_options) + int rcelly, rcellx; // rendered cell geometry (per visual_options) + int scaley, scalex; // pixels per filled cell (scale == c for bitmaps) + int maxpixely, maxpixelx; // only defined for NCBLIT_PIXEL + ncblitter_e blitter;// blitter that will be used +} ncvgeom; + +// all-purpose ncvisual geometry solver. one or both of 'nc' and 'n' must be +// non-NULL. if 'nc' is NULL, only pixy/pixx will be filled in, with the true +// pixel geometry of 'n'. if 'n' is NULL, only cdimy/cdimx, blitter, and (if +// applicable) maxpixely/maxpixelx are filled in. +int ncvisual_geom(const struct notcurses* nc, const struct ncvisual* n, + const struct ncvisual_options* vopts, ncvgeom* geom); // Scale the visual to 'rows' X 'columns' pixels, using the best scheme // available. This is a lossy transformation, unless the size is unchanged. diff --git a/doc/man/man3/notcurses_visual.3.md b/doc/man/man3/notcurses_visual.3.md index ba579ddd5..4d5abbbde 100644 --- a/doc/man/man3/notcurses_visual.3.md +++ b/doc/man/man3/notcurses_visual.3.md @@ -51,6 +51,16 @@ struct ncvisual_options { }; typedef int (*streamcb)(struct notcurses*, struct ncvisual*, void*); + +typedef struct ncvgeom { + int pixy, pixx; // true pixel geometry of ncvisual data + int cdimy, cdimx; // terminal cell geometry when this was calculated + int rpixy, rpixx; // rendered pixel geometry (per visual_options) + int rcelly, rcellx; // rendered cell geometry (per visual_options) + int scaley, scalex; // pixels per filled cell (scale == c for bitmaps) + int maxpixely, maxpixelx; // only defined for NCBLIT_PIXEL + ncblitter_e blitter;// blitter that will be used +} ncvgeom; ``` **struct ncvisual* ncvisual_from_file(const char* ***file***);** @@ -67,7 +77,7 @@ typedef int (*streamcb)(struct notcurses*, struct ncvisual*, void*); **struct ncvisual* ncvisual_from_plane(struct ncplane* ***n***, ncblitter_e ***blit***, int ***begy***, int ***begx***, int ***leny***, int ***lenx***);** -**int ncvisual_blitter_geom(const struct notcurses* ***nc***, const struct ncvisual* ***n***, const struct ncvisual_options* ***vopts***, int* ***y***, int* ***x***, int* ***scaley***, int* ***scalex***, ncblitter_e* ***blitter***);** +**int ncvisual_geom(const struct notcurses* ***nc***, const struct ncvisual* ***n***, const struct ncvisual_options* ***vopts***, ncvgeom* ***geom***);** **void ncvisual_destroy(struct ncvisual* ***ncv***);** @@ -177,9 +187,14 @@ geometry of same. ***flags*** is a bitfield over: as a transparent color. * **NCVISUAL_OPTION_CHILDPLANE**: Make a new plane, as a child of ***n***. -**ncvisual_blitter_geom** allows the caller to determine any or all of the -visual's pixel geometry, the blitter to be used, and that blitter's scaling -in both dimensions. Any but the first argument may be **NULL**. +**ncvisual_geom** allows the caller to determine any or all of the visual's +pixel geometry, the blitter to be used, and that blitter's scaling in both +dimensions. Any but the final argument may be **NULL**, though at least one +of ***nc*** and ***n*** must be non-**NULL**. If ***nc*** is **NULL**, +only properties intrinsic to the visual are returned (i.e. its original +pixel geometry). If ***n*** is **NULL**, only properties independent of the +visual are returned (i.e. cell-pixel geometry and maximum bitmap geometry). +If both are supplied, all fields of the **ncvgeom** structure are filled in. **ncplane_qrcode** draws an ISO/IEC 18004:2015 QR Code for the **len** bytes of **data** using **NCBLIT_2x1** (this is the only blitter that will work with QR @@ -317,7 +332,8 @@ which the visual was rendered. If **opts->n** is provided, this will be **opts->n**. Otherwise, a plane will be created, perfectly sized for the visual and the specified blitter. -**ncvisual_blitter_geom** returns non-zero if the specified blitter is invalid. +**ncvisual_geom** returns non-zero if the specified configuration is invalid, +or if both ***nc*** and ***n*** are **NULL**. **ncvisual_media_defblitter** returns the blitter selected by **NCBLIT_DEFAULT** in the specified configuration. If UTF8 is not enabled, this will always be @@ -358,7 +374,7 @@ When using non-interpolative blitting together with scaling, unless your goal includes minimizing the total area required, lower-resolution blitters will generally look just as good as higher resolution blitters, and be faster. -The results of **ncvisual_blitter_geom** are invalidated by a terminal resize. +The results of **ncvisual_geom** are invalidated by a terminal resize. # BUGS diff --git a/include/ncpp/Visual.hh b/include/ncpp/Visual.hh index e22c8e139..2f72fd87f 100644 --- a/include/ncpp/Visual.hh +++ b/include/ncpp/Visual.hh @@ -96,9 +96,9 @@ namespace ncpp return error_guard (ncvisual_polyfill_yx (visual, y, x, rgba), -1); } - bool geom (const struct ncvisual_options *vopts, int *y, int *x, int *toy, int *tox, ncblitter_e* blitter) const NOEXCEPT_MAYBE + bool geom (const struct ncvisual_options *vopts, ncvgeom* geom) const NOEXCEPT_MAYBE { - return error_guard (ncvisual_blitter_geom (get_notcurses (), visual, vopts, y, x, toy, tox, blitter), -1); + return error_guard (ncvisual_geom (get_notcurses (), visual, vopts, geom), -1); } bool at (int y, int x, uint32_t* pixel) const diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index 38ee6918e..a311399a8 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -2895,13 +2895,14 @@ struct ncvisual_options { // maxpixely/maxpixelx are defined only when NCBLIT_PIXEL is used, and specify // the largest bitmap that the terminal is willing to accept. blitter is the // blitter which will be used, a function of the requested blitter and the -// blitters actually supported by this environment. +// blitters actually supported by this environment. if no ncvisual was +// supplied, only cdimy/cdimx are filled in. typedef struct ncvgeom { int pixy, pixx; // true pixel geometry of ncvisual data int cdimy, cdimx; // terminal cell geometry when this was calculated - int rpixy, rpixx; // rendered pixel geometry - int rcelly, rcellx; // rendered cell geometry - int scaley, scalex; // pixels per filled cell + int rpixy, rpixx; // rendered pixel geometry (per visual_options) + int rcelly, rcellx; // rendered cell geometry (per visual_options) + int scaley, scalex; // pixels per filled cell (scale == c for bitmaps) int maxpixely, maxpixelx; // only defined for NCBLIT_PIXEL ncblitter_e blitter;// blitter that will be used } ncvgeom; @@ -2916,7 +2917,15 @@ typedef struct ncvgeom { API int ncvisual_blitter_geom(const struct notcurses* nc, const struct ncvisual* n, const struct ncvisual_options* vopts, int* y, int* x, int* scaley, int* scalex, ncblitter_e* blitter) - __attribute__ ((nonnull (1))); + __attribute__ ((nonnull (1))) __attribute__ ((deprecated)); + +// all-purpose ncvisual geometry solver. one or both of 'nc' and 'n' must be +// non-NULL. if 'nc' is NULL, only pixy/pixx will be filled in, with the true +// pixel geometry of 'n'. if 'n' is NULL, only cdimy/cdimx, blitter, and (if +// applicable) maxpixely/maxpixelx are filled in. +API int ncvisual_geom(const struct notcurses* nc, const struct ncvisual* n, + const struct ncvisual_options* vopts, ncvgeom* geom) + __attribute__ ((nonnull (4))); // Destroy an ncvisual. Rendered elements will not be disrupted, but the visual // can be neither decoded nor rendered any further. diff --git a/src/demo/eagle.c b/src/demo/eagle.c index c38ae1d27..181ed8e03 100644 --- a/src/demo/eagle.c +++ b/src/demo/eagle.c @@ -36,8 +36,6 @@ zoom_map(struct notcurses* nc, const char* map, int* ret){ if(ncv == NULL){ return NULL; } - // true height and width of visual, and blitter scaling parameters - int vheight, yscale, vwidth, xscale; // first we want to get the true size, so don't supply NCSSCALE_STRETCH yet, // but *do* explicitly supply NCBLIT_2x2 since we're not scaling. struct ncvisual_options vopts = { @@ -45,11 +43,16 @@ zoom_map(struct notcurses* nc, const char* map, int* ret){ .blitter = NCBLIT_2x2, .flags = NCVISUAL_OPTION_NOINTERPOLATE, }; - if(ncvisual_blitter_geom(nc, ncv, &vopts, &vheight, &vwidth, - &yscale, &xscale, NULL)){ + ncvgeom geom; + if(ncvisual_geom(nc, ncv, &vopts, &geom)){ ncvisual_destroy(ncv); return NULL; } + // true height and width of visual, and blitter scaling parameters + int vheight = geom.pixy; + int vwidth = geom.pixx; + int yscale = geom.scaley; + int xscale = geom.scalex; //fprintf(stderr, "VHEIGHT: %d VWIDTH: %d scale: %d/%d\n", vheight, vwidth, yscale, xscale); // we start at the lower left corner of the outzoomed map int placey, placex; // dimensions of true display diff --git a/src/demo/intro.c b/src/demo/intro.c index 0a8138385..c8f2b5589 100644 --- a/src/demo/intro.c +++ b/src/demo/intro.c @@ -78,20 +78,11 @@ orcashow(struct notcurses* nc, int dimy, int dimx){ .blitter = NCBLIT_PIXEL, .scaling = NCSCALE_STRETCH, }; - int cellpxy, cellpxx; - ncplane_pixelgeom(notcurses_stdplane_const(nc), NULL, NULL, - &cellpxy, &cellpxx, NULL, NULL); - int odimy, odimx, scaley, scalex; - ncvisual_blitter_geom(nc, ncv, &vopts, &odimy, &odimx, &scaley, &scalex, NULL); - // even if we can't do pixel output, we want her the same size as if weu - // *could* do pixel output. if we have no idea as to the geom, use scale. - if(cellpxy == 0){ - cellpxy = scaley; - cellpxx = scalex; - } + ncvgeom geom; + ncvisual_geom(nc, ncv, &vopts, &geom); struct ncplane_options nopts = { - .rows = (odimy / cellpxy) + !!(odimy % cellpxy), - .cols = (odimx / cellpxx) + !!(odimx % cellpxx), + .rows = geom.rcelly, + .cols = geom.rcellx, .name = "orca", }; if(nopts.cols > dimx - 1){ diff --git a/src/demo/keller.c b/src/demo/keller.c index 3ff143778..a5e00441b 100644 --- a/src/demo/keller.c +++ b/src/demo/keller.c @@ -36,18 +36,18 @@ visualize(struct notcurses* nc, struct ncvisual* ncv){ | NCVISUAL_OPTION_VERALIGNED | NCVISUAL_OPTION_CHILDPLANE, }; - int scalex, scaley, truey, truex; vopts.x = NCALIGN_CENTER; vopts.y = NCALIGN_CENTER; ncplane_erase(stdn); // to clear out old text struct ncplane* n = NULL; - if(ncvisual_blitter_geom(nc, ncv, &vopts, &truey, &truex, &scaley, &scalex, NULL) == 0){ + ncvgeom geom; + if(ncvisual_geom(nc, ncv, &vopts, &geom) == 0){ if( (n = ncvisual_blit(nc, ncv, &vopts)) ){ ncplane_move_below(n, stdn); ncplane_printf_aligned(stdn, ncplane_dim_y(stdn) / 2 - 1, NCALIGN_CENTER, - "%03dx%03d", truex, truey); + "%03dx%03d", geom.pixx, geom.pixy); ncplane_printf_aligned(stdn, ncplane_dim_y(stdn) / 2 + 1, NCALIGN_CENTER, - "%d:%d pixels -> cell", scalex, scaley); + "%d:%d pixels -> cell", geom.scalex, geom.scaley); } } //fprintf(stderr, "X: %d truex: %d scalex: %d\n", vopts.x, truex, scalex); diff --git a/src/demo/luigi.c b/src/demo/luigi.c index e1f9d7fa0..8fedf0022 100644 --- a/src/demo/luigi.c +++ b/src/demo/luigi.c @@ -219,10 +219,10 @@ int luigi_demo(struct notcurses* nc){ ncplane_move_top(lastseen); } ncplane_move_yx(lastseen, yoff, i); - int dimy, scaley; - ncvisual_blitter_geom(nc, wmncv, NULL, &dimy, NULL, &scaley, NULL, NULL); - dimy /= scaley; - ncplane_move_yx(wmplane, rows * 4 / 5 - dimy + 1 + (i % 2), i - 60); + ncvgeom geom; + ncvisual_geom(nc, wmncv, NULL, &geom); + geom.pixy /= geom.scaley; + ncplane_move_yx(wmplane, rows * 4 / 5 - geom.pixy + 1 + (i % 2), i - 60); DEMO_RENDER(nc); demo_nanosleep(nc, &stepdelay); } diff --git a/src/demo/normal.c b/src/demo/normal.c index 571c5c818..e2e045073 100644 --- a/src/demo/normal.c +++ b/src/demo/normal.c @@ -167,10 +167,10 @@ int normal_demo(struct notcurses* nc){ struct ncvisual_options vopts = { .n = nstd, }; - int yscale, xscale; - ncvisual_blitter_geom(nc, NULL, &vopts, NULL, NULL, &yscale, &xscale, NULL); - dy *= yscale; - dx *= xscale; + ncvgeom geom; + ncvisual_geom(nc, NULL, &vopts, &geom); + dy *= geom.scaley; + dx *= geom.scalex; uint32_t* rgba = malloc(sizeof(*rgba) * dy * dx); if(!rgba){ goto err; @@ -179,10 +179,10 @@ int normal_demo(struct notcurses* nc){ rgba[off] = 0xff000000; } int y; - if(dy / yscale % 2){ - y = dy / yscale + 1; + if(dy / geom.scaley % 2){ + y = dy / geom.scaley + 1; for(int x = 0 ; x < dx ; ++x){ - if(mcell(offset(rgba, y, x, dx), y, x, dy / yscale, dx)){ + if(mcell(offset(rgba, y, x, dx), y, x, dy / geom.scaley, dx)){ goto err; } } diff --git a/src/demo/yield.c b/src/demo/yield.c index c73ac6b2a..20aa28e9a 100644 --- a/src/demo/yield.c +++ b/src/demo/yield.c @@ -237,7 +237,8 @@ int yield_demo(struct notcurses* nc){ ncplane_set_fg_rgb8(label, 0xff, 0xff, 0xff); ncplane_set_styles(label, NCSTYLE_BOLD); ncplane_printf_aligned(label, 0, NCALIGN_CENTER, "Yield: %03.1f%%", 0.0); - if(ncvisual_blitter_geom(nc, v1, &m1.vopts, &maxy, &maxx, NULL, NULL, NULL)){ + ncvgeom geom; + if(ncvisual_geom(nc, v1, &m1.vopts, &geom)){ ncplane_destroy(label); ncvisual_destroy(v1); ncvisual_destroy(v2); @@ -253,7 +254,7 @@ int yield_demo(struct notcurses* nc){ ncplane_destroy(m1.vopts.n); return -1; } - total = maxx * maxy; + total = geom.pixy * geom.pixx; DEMO_RENDER(nc); diff --git a/src/lib/visual.c b/src/lib/visual.c index 523b3af17..c6af6d65f 100644 --- a/src/lib/visual.c +++ b/src/lib/visual.c @@ -132,7 +132,6 @@ ncvisual_origin(const struct ncvisual_options* vopts, int* restrict begy, int* r // FIXME we ought also do the output calculations here (how many rows x cols, // given the input plane vopts->n and scaling vopts->scaling)--but do not // perform any actual scaling, nor create any planes! -// takes tcache distinctly; nc is used only for logging, and can be NULL int ncvisual_blitset_geom(const notcurses* nc, const tinfo* tcache, const ncvisual* n, const struct ncvisual_options* vopts, int* y, int* x, int* scaley, int* scalex, @@ -287,6 +286,16 @@ int ncvisual_blitter_geom(const notcurses* nc, const ncvisual* n, return ret; } +int ncvisual_geom(const notcurses* nc, const ncvisual* n, + const struct ncvisual_options* vopts, ncvgeom* geom){ + if(nc == NULL && n == NULL){ + logerror("got NULL for both sources\n"); + return -1; + } + // FIXME + return 0; +} + void* rgb_loose_to_rgba(const void* data, int rows, int* rowstride, int cols, int alpha){ if(*rowstride % 4){ // must be a multiple of 4 bytes return NULL; diff --git a/src/media/ffmpeg.c b/src/media/ffmpeg.c index dba857949..1062ecf35 100644 --- a/src/media/ffmpeg.c +++ b/src/media/ffmpeg.c @@ -498,9 +498,11 @@ int ffmpeg_stream(notcurses* nc, ncvisual* ncv, float timescale, ncplane_erase(activevopts.n); // new frame could be partially transparent } // decay the blitter explicitly, so that the callback knows the blitter it - // was actually rendered with - ncvisual_blitter_geom(nc, ncv, &activevopts, NULL, NULL, NULL, NULL, - &activevopts.blitter); + // was actually rendered with. basically just need rgba_blitter(), but + // that's not exported. + ncvgeom geom; + ncvisual_geom(nc, ncv, &activevopts, &geom); + activevopts.blitter = geom.blitter; if((newn = ncvisual_blit(nc, ncv, &activevopts)) == NULL){ if(activevopts.n != vopts->n){ ncplane_destroy(activevopts.n); diff --git a/src/poc/rotate.c b/src/poc/rotate.c index 3cb89c020..1ca5d6537 100644 --- a/src/poc/rotate.c +++ b/src/poc/rotate.c @@ -41,7 +41,6 @@ int main(int argc, char** argv){ if(!ncv){ goto err; } - int scaley, scalex; vopts.n = n; if((ncvisual_blit(nc, ncv, &vopts)) == NULL){ goto err; @@ -52,8 +51,9 @@ int main(int argc, char** argv){ clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); ncplane_erase(n); - ncvisual_blitter_geom(nc, ncv, &vopts, NULL, NULL, &scaley, &scalex, NULL); - if(ncvisual_resize(ncv, dimy * scaley, dimx * scalex)){ + ncvgeom geom; + ncvisual_geom(nc, ncv, &vopts, &geom); + if(ncvisual_resize(ncv, dimy * geom.scaley, dimx * geom.scalex)){ goto err; } if(ncvisual_blit(nc, ncv, &vopts) == NULL){ diff --git a/src/tests/media.cpp b/src/tests/media.cpp index fce2f4c1e..1253f15d8 100644 --- a/src/tests/media.cpp +++ b/src/tests/media.cpp @@ -92,14 +92,18 @@ TEST_CASE("Media") { ncplane_dim_yx(ncp_, &dimy, &dimx); auto ncv = ncvisual_from_file(find_data("changes.jpg").get()); REQUIRE(ncv); - int odimy, odimx, ndimy, ndimx; struct ncvisual_options opts{}; opts.n = ncp_; - CHECK(0 == ncvisual_blitter_geom(nc_, ncv, &opts, &odimy, &odimx, nullptr, nullptr, nullptr)); + ncvgeom geom{}; + CHECK(0 == ncvisual_geom(nc_, ncv, &opts, &geom)); + int odimy = geom.pixy; + int odimx = geom.pixx; CHECK(ncvisual_blit(nc_, ncv, &opts)); CHECK(0 == notcurses_render(nc_)); CHECK(0 == ncvisual_resize_noninterpolative(ncv, ncv->pixy * 2, ncv->pixx * 2)); - CHECK(0 == ncvisual_blitter_geom(nc_, ncv, &opts, &ndimy, &ndimx, nullptr, nullptr, nullptr)); + CHECK(0 == ncvisual_geom(nc_, ncv, &opts, &geom)); + int ndimy = geom.pixy; + int ndimx = geom.pixx; CHECK(ndimy == odimy * 2); CHECK(ndimx == odimx * 2); CHECK(ncvisual_blit(nc_, ncv, &opts));