From 2c417ddb0c8da95a057fbee15879addc68225219 Mon Sep 17 00:00:00 2001 From: nick black Date: Sat, 6 Jun 2020 05:11:45 -0400 Subject: [PATCH] yield demo: punch up with polyfills --- USAGE.md | 11 ++++- doc/man/man3/notcurses_visual.3.md | 6 +++ include/ncpp/Plane.hh | 4 +- include/ncpp/Visual.hh | 4 +- include/notcurses/notcurses.h | 41 +++++++++++-------- python/src/notcurses/build_notcurses.py | 24 ++++++----- src/demo/normal.c | 2 +- src/demo/yield.c | 43 ++++++++++++++++---- src/lib/notcurses.c | 7 +++- src/lib/visual.cpp | 53 ++++++++++++++++++++++--- src/poc/rotator.c | 4 +- tests/Ncpp.cpp | 2 +- tests/rotate.cpp | 4 +- 13 files changed, 152 insertions(+), 53 deletions(-) diff --git a/USAGE.md b/USAGE.md index 119feec2b..9c91a28b6 100644 --- a/USAGE.md +++ b/USAGE.md @@ -759,8 +759,9 @@ int ncplane_at_yx_cell(struct ncplane* n, int y, int x, cell* c); // Start at the plane's 'begy'x'begx' coordinate (which must lie on the // plane), continuing for 'leny'x'lenx' cells. Either or both of 'leny' and // 'lenx' can be specified as -1 to go through the boundary of the plane. -uint32_t* ncplane_rgba(const struct ncplane* nc, int begy, int begx, - int leny, int lenx); +// Only glyphs from the specified blitset may be present. +uint32_t* ncplane_rgba(const struct ncplane* nc, ncblitter_e blit, + int begy, int begx, int leny, int lenx); // return a nul-terminated, heap copy of the current (UTF-8) contents. char* ncplane_contents(const struct ncplane* nc, int begy, int begx, @@ -2507,6 +2508,12 @@ nc_err_e ncvisual_rotate(struct ncvisual* n, double rads); // transformation, unless the size is unchanged. nc_err_e ncvisual_resize(struct ncvisual* n, int rows, int cols); +// Polyfill at the specified location within the ncvisual 'n', using 'rgba'. +int ncvisual_polyfill_yx(struct ncvisual* n, int y, int x, uint32_t rgba); + +// Get the specified pixel from the specified ncvisual. +int ncvisual_at_yx(const struct ncvisual* n, int y, int x, uint32_t* pixel); + // If a subtitle ought be displayed at this time, return a heap-allocated copy // of the UTF8 text. char* ncvisual_subtitle(const struct ncvisual* ncv); diff --git a/doc/man/man3/notcurses_visual.3.md b/doc/man/man3/notcurses_visual.3.md index 46b4a3109..b05832505 100644 --- a/doc/man/man3/notcurses_visual.3.md +++ b/doc/man/man3/notcurses_visual.3.md @@ -72,6 +72,12 @@ typedef int (*streamcb)(struct notcurses*, struct ncvisual*, void*); **int ncvisual_rotate(struct ncvisual* n, double rads);** +**int ncvisual_resize(struct ncvisual* n, int rows, int cols);** + +**int ncvisual_polyfill_yx(struct ncvisual* n, int y, int x, uint32_t rgba);** + +**int ncvisual_at_yx(const struct ncvisual* n, int y, int x, uint32_t* pixel);** + **char* ncvisual_subtitle(const struct ncvisual* ncv);** # DESCRIPTION diff --git a/include/ncpp/Plane.hh b/include/ncpp/Plane.hh index 169c7531c..ad0e30425 100644 --- a/include/ncpp/Plane.hh +++ b/include/ncpp/Plane.hh @@ -621,9 +621,9 @@ namespace ncpp return error_guard (ncplane_polyfill_yx (plane, y, x, c), -1); } - uint32_t* rgba(int begy, int begx, int leny, int lenx) const noexcept + uint32_t* rgba(ncblitter_e blit, int begy, int begx, int leny, int lenx) const noexcept { - return ncplane_rgba (plane, begy, begx, leny, lenx); + return ncplane_rgba (plane, blit, begy, begx, leny, lenx); } char* content(int begy, int begx, int leny, int lenx) const noexcept diff --git a/include/ncpp/Visual.hh b/include/ncpp/Visual.hh index 169db56db..e2d29d0a4 100644 --- a/include/ncpp/Visual.hh +++ b/include/ncpp/Visual.hh @@ -32,10 +32,10 @@ namespace ncpp throw init_error ("Notcurses failed to create a new visual"); } - explicit Visual (const Plane& p, int begy, int begx, int leny, int lenx) + explicit Visual (const Plane& p, ncblitter_e blit, int begy, int begx, int leny, int lenx) : Root(NotCurses::get_instance()) { - visual = ncvisual_from_plane (p, begy, begx, leny, lenx); + visual = ncvisual_from_plane (p, blit, begy, begx, leny, lenx); if (visual == nullptr) throw init_error ("Notcurses failed to create a new visual"); } diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index c02cf2207..f6d3c49cf 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -1213,14 +1213,6 @@ ncplane_at_yx_cell(struct ncplane* n, int y, int x, cell* c){ return r; } -// Create an RGBA flat array from the selected region of the ncplane 'nc'. -// Start at the plane's 'begy'x'begx' coordinate (which must lie on the -// plane), continuing for 'leny'x'lenx' cells. Either or both of 'leny' and -// 'lenx' can be specified as -1 to go through the boundary of the plane. -// Only spaces, half blocks, and full blocks may be present. -API uint32_t* ncplane_rgba(const struct ncplane* nc, int begy, int begx, - int leny, int lenx); - // Create a flat string from the EGCs of the selected region of the ncplane // 'nc'. Start at the plane's 'begy'x'begx' coordinate (which must lie on the // plane), continuing for 'leny'x'lenx' cells. Either or both of 'leny' and @@ -2119,15 +2111,6 @@ API struct ncvisual* ncvisual_from_rgba(const void* rgba, int rows, API struct ncvisual* ncvisual_from_bgra(const void* rgba, int rows, int rowstride, int cols); -// Promote an ncplane 'n' to an ncvisual. The plane may contain only spaces, -// half blocks, and full blocks. The latter will be checked, and any other -// glyph will result in a NULL being returned. This function exists so that -// planes can be subjected to ncvisual transformations. If possible, it's -// better to create the ncvisual from memory using ncvisual_from_rgba(). -API struct ncvisual* ncvisual_from_plane(const struct ncplane* n, - int begy, int begx, - int leny, int lenx); - // each has the empty cell in addition to the product of its dimensions. i.e. // NCBLIT_1x1 has two states: empty and full block. NCBLIT_1x1x4 has five // states: empty, the three shaded blocks, and the full block. @@ -2143,6 +2126,24 @@ typedef enum { NCBLIT_SIXEL, // 6 rows, 1 col (RGB), spotty support among terminals } ncblitter_e; +// Promote an ncplane 'n' to an ncvisual. The plane may contain only spaces, +// half blocks, and full blocks. The latter will be checked, and any other +// glyph will result in a NULL being returned. This function exists so that +// planes can be subjected to ncvisual transformations. If possible, it's +// better to create the ncvisual from memory using ncvisual_from_rgba(). +API struct ncvisual* ncvisual_from_plane(const struct ncplane* n, + ncblitter_e blit, + int begy, int begx, + int leny, int lenx); + +// Create an RGBA flat array from the selected region of the ncplane 'nc'. +// Start at the plane's 'begy'x'begx' coordinate (which must lie on the +// plane), continuing for 'leny'x'lenx' cells. Either or both of 'leny' and +// 'lenx' can be specified as -1 to go through the boundary of the plane. +// Only glyphs from the specified blitset may be present. +API uint32_t* ncplane_rgba(const struct ncplane* nc, ncblitter_e blit, + int begy, int begx, int leny, int lenx); + // Get the size and ratio of ncvisual pixels to output cells along the y // ('toy') and x ('tox') axes. A ncvisual of '*y'X'*x' pixels will require // ('*y' * '*toy')X('x' * 'tox') cells for full output. Returns non-zero @@ -2166,6 +2167,12 @@ API nc_err_e ncvisual_rotate(struct ncvisual* n, double rads); // transformation, unless the size is unchanged. API nc_err_e ncvisual_resize(struct ncvisual* n, int rows, int cols); +// Polyfill at the specified location within the ncvisual 'n', using 'rgba'. +API int ncvisual_polyfill_yx(struct ncvisual* n, int y, int x, uint32_t rgba); + +// Get the specified pixel from the specified ncvisual. +API int ncvisual_at_yx(const struct ncvisual* n, int y, int x, uint32_t* pixel); + #define NCVISUAL_OPTION_NODEGRADE 0x0001 // fail rather than degrading #define NCVISUAL_OPTION_BLEND 0x0002 // use CELL_ALPHA_BLEND with visual diff --git a/python/src/notcurses/build_notcurses.py b/python/src/notcurses/build_notcurses.py index f205ba4d0..960bf4ae5 100644 --- a/python/src/notcurses/build_notcurses.py +++ b/python/src/notcurses/build_notcurses.py @@ -147,7 +147,17 @@ char* ncplane_at_cursor(struct ncplane* n, uint32_t* attrword, uint64_t* channel int ncplane_at_cursor_cell(struct ncplane* n, cell* c); char* ncplane_at_yx(const struct ncplane* n, int y, int x, uint32_t* attrword, uint64_t* channels); int ncplane_at_yx_cell(struct ncplane* n, int y, int x, cell* c); -uint32_t* ncplane_rgba(const struct ncplane* nc, int begy, int begx, int leny, int lenx); +typedef enum { + NCBLIT_1x1, // full block █ + NCBLIT_2x1, // full/(upper|left) blocks ▄█ + NCBLIT_1x1x4, // shaded full blocks ▓▒░█ + NCBLIT_2x2, // quadrants ▗▐ ▖▄▟▌▙█ + NCBLIT_4x1, // four vert/horz levels █▆▄▂ / ▎▌▊█ + NCBLIT_BRAILLE, // 4 rows, 2 cols (braille) ⡀⡄⡆⡇⢀⣀⣄⣆⣇⢠⣠⣤⣦⣧⢰⣰⣴⣶⣷⢸⣸⣼⣾⣿ + NCBLIT_8x1, // eight vert/horz levels █▇▆▅▄▃▂▁ / ▏▎▍▌▋▊▉█ + NCBLIT_SIXEL, // 6 rows, 1 col (RGB) +} ncblitter_e; +uint32_t* ncplane_rgba(const struct ncplane* nc, ncblitter_e blit, int begy, int begx, int leny, int lenx); char* ncplane_contents(const struct ncplane* nc, int begy, int begx, int leny, int lenx); void* ncplane_set_userptr(struct ncplane* n, void* opaque); void* ncplane_userptr(struct ncplane* n); @@ -292,16 +302,6 @@ typedef enum { NCSCALE_SCALE, NCSCALE_STRETCH, } ncscale_e; -typedef enum { - NCBLIT_1x1, // full block █ - NCBLIT_2x1, // full/(upper|left) blocks ▄█ - NCBLIT_1x1x4, // shaded full blocks ▓▒░█ - NCBLIT_2x2, // quadrants ▗▐ ▖▄▟▌▙█ - NCBLIT_4x1, // four vert/horz levels █▆▄▂ / ▎▌▊█ - NCBLIT_BRAILLE, // 4 rows, 2 cols (braille) ⡀⡄⡆⡇⢀⣀⣄⣆⣇⢠⣠⣤⣦⣧⢰⣰⣴⣶⣷⢸⣸⣼⣾⣿ - NCBLIT_8x1, // eight vert/horz levels █▇▆▅▄▃▂▁ / ▏▎▍▌▋▊▉█ - NCBLIT_SIXEL, // 6 rows, 1 col (RGB) -} ncblitter_e; struct ncvisual* ncvisual_from_file(const char* file, nc_err_e* ncerr); struct ncvisual* ncvisual_from_rgba(const void* rgba, int rows, int rowstride, int cols); struct ncvisual* ncvisual_from_bgra(const void* rgba, int rows, int rowstride, int cols); @@ -310,6 +310,8 @@ int ncvisual_geom(const struct notcurses* nc, const struct ncvisual* n, ncblitte void ncvisual_destroy(struct ncvisual* ncv); nc_err_e ncvisual_decode(struct ncvisual* nc); int ncvisual_rotate(struct ncvisual* n, double rads); +int ncvisual_resize(struct ncvisual* n, int rows, int cols); +int ncvisual_polyfill_yx(struct ncvisual* n, int y, int x, uint32_t rgba); struct ncplane* ncvisual_render(struct notcurses* nc, struct ncvisual* ncv, const struct ncvisual_options* vopts); char* ncvisual_subtitle(const struct ncvisual* ncv); typedef int (*streamcb)(struct ncvisual*, struct ncvisual_options*, const struct timespec*, void*); diff --git a/src/demo/normal.c b/src/demo/normal.c index 0461b024a..ed7bf3e68 100644 --- a/src/demo/normal.c +++ b/src/demo/normal.c @@ -62,7 +62,7 @@ rotate_visual(struct notcurses* nc, struct ncplane* n, int dy, int dx){ dx = dy * 2; } //fprintf(stderr, "ASK %d/%d @ %d/%d: %p\n", dy, dx, fromy, fromx); - struct ncvisual* ncv = ncvisual_from_plane(n, fromy, fromx, dy, dx); + struct ncvisual* ncv = ncvisual_from_plane(n, NCBLIT_DEFAULT, fromy, fromx, dy, dx); //fprintf(stderr, "%d/%d @ %d/%d: %p\n", dy, dx, fromy, fromx, ncv); if(!ncv){ ncvisual_destroy(ncv); diff --git a/src/demo/yield.c b/src/demo/yield.c index 8f605da32..6d246c53b 100644 --- a/src/demo/yield.c +++ b/src/demo/yield.c @@ -22,17 +22,46 @@ int yield_demo(struct notcurses* nc){ ncvisual_destroy(wmv); return -1; } + DEMO_RENDER(nc); demo_nanosleep(nc, &demodelay); - cell c = CELL_SIMPLE_INITIALIZER('*'); - cell_set_fg_rgb(&c, 0xff, 0, 0); - cell_set_bg_rgb(&c, 0xff, 0, 0); - for(int i = 0 ; i < 128 ; ++i){ - // FIXME - // don't try to use polyfill; work directly on the ncvisual instead + ncplane_erase(std); + + int vy, vx, vscaley, vscalex; + ncvisual_geom(nc, wmv, vopts.blitter, &vy, &vx, &vscaley, &vscalex); + struct timespec scaled; + int threshold_painted = vy * vx * 10 / 9; + const int ITER = 128; + timespec_div(&demodelay, ITER, &scaled); + int tfilled = 0; + for(int i = 0 ; i < ITER ; ++i){ + int pfilled; + do{ + int x = random() % (vx); + int y = random() % (vy); + uint32_t pixel = 0; + ncvisual_at_yx(wmv, y, x, &pixel); + uint32_t channel = 0; + channel_set_rgb(&channel, 0x80, channel_g(pixel), channel_b(pixel)); +//fprintf(stderr, "POLY: %d/%d\n", y, x); + pfilled = ncvisual_polyfill_yx(wmv, y, x, channel); + if(pfilled < 0){ + ncvisual_destroy(wmv); + return -1; + } + }while(pfilled == 0); + tfilled += pfilled; + if(ncvisual_render(nc, wmv, &vopts) == NULL){ + ncvisual_destroy(wmv); + return -1; + } + DEMO_RENDER(nc); + demo_nanosleep(nc, &scaled); + if(tfilled >= threshold_painted){ + break; + } } - cell_release(std, &c); ncvisual_destroy(wmv); return 0; diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 627d63935..f062b5dc4 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -1938,7 +1938,8 @@ int notcurses_inputready_fd(notcurses* n){ return fileno(n->ttyinfp); } -uint32_t* ncplane_rgba(const ncplane* nc, int begy, int begx, int leny, int lenx){ +uint32_t* ncplane_rgba(const ncplane* nc, ncblitter_e blit, + int begy, int begx, int leny, int lenx){ if(begy < 0 || begx < 0){ return NULL; } @@ -1979,6 +1980,9 @@ uint32_t* ncplane_rgba(const ncplane* nc, int begy, int begx, int leny, int lenx // FIXME how do we deal with transparency? uint32_t frgba = (fr) + (fg << 16u) + (fb << 8u) + 0xff000000; uint32_t brgba = (br) + (bg << 16u) + (bb << 8u) + 0xff000000; + // FIXME integrate 'blit' + (void)blit; + // FIXME need to be able to pick up quadrants! if((strcmp(c, " ") == 0) || (strcmp(c, "") == 0)){ *top = *bot = brgba; }else if(strcmp(c, "▄") == 0){ @@ -1992,6 +1996,7 @@ uint32_t* ncplane_rgba(const ncplane* nc, int begy, int begx, int leny, int lenx }else{ free(c); free(ret); +//fprintf(stderr, "bad rgba character: %s\n", c); return NULL; } free(c); diff --git a/src/lib/visual.cpp b/src/lib/visual.cpp index 7f7951056..c9797c8a2 100644 --- a/src/lib/visual.cpp +++ b/src/lib/visual.cpp @@ -473,10 +473,11 @@ auto ncvisual_render(notcurses* nc, ncvisual* ncv, return n; } -auto ncvisual_from_plane(const ncplane* n, int begy, int begx, +auto ncvisual_from_plane(const ncplane* n, ncblitter_e blit, int begy, int begx, int leny, int lenx) -> ncvisual* { - uint32_t* rgba = ncplane_rgba(n, begy, begx, leny, lenx); + uint32_t* rgba = ncplane_rgba(n, blit, begy, begx, leny, lenx); //fprintf(stderr, "snarg: %d/%d @ %d/%d (%p)\n", leny, lenx, begy, begx, rgba); +//fprintf(stderr, "RGBA %p\n", rgba); if(rgba == nullptr){ return nullptr; } @@ -490,9 +491,7 @@ auto ncvisual_from_plane(const ncplane* n, int begy, int begx, } auto* ncv = ncvisual_from_rgba(rgba, leny * 2, lenx * 4, lenx); free(rgba); - if(ncv == nullptr){ - return nullptr; - } +//fprintf(stderr, "RETURNING %p\n", ncv); return ncv; } @@ -527,6 +526,50 @@ auto ncvisual_simple_streamer(ncvisual* ncv, struct ncvisual_options* vopts, return ret; } +auto ncvisual_polyfill_recurse(ncvisual* n, int y, int x, + uint32_t rgba, uint32_t match) -> int { + if(y < 0 || y >= n->rows){ + return 0; + } + if(x < 0 || x >= n->cols){ + return 0; + } + uint32_t* pixel = &n->data[y * (n->rowstride / 4) + x]; + if(*pixel != match || *pixel == rgba){ + return 0; + } +//fprintf(stderr, "%d/%d: %08x -> %08x\n", y, x, *pixel, rgba); + *pixel = rgba; + int ret = 1; + ret += ncvisual_polyfill_recurse(n, y - 1, x, rgba, match); + ret += ncvisual_polyfill_recurse(n, y + 1, x, rgba, match); + ret += ncvisual_polyfill_recurse(n, y, x - 1, rgba, match); + ret += ncvisual_polyfill_recurse(n, y, x + 1, rgba, match); + return ret; +} + +auto ncvisual_at_yx(const ncvisual* n, int y, int x, uint32_t* pixel) -> int { + if(y >= n->rows || y < 0){ + return -1; + } + if(x >= n->cols || x < 0){ + return -1; + } + *pixel = n->data[y * (n->rowstride / 4) + x]; + return 0; +} + +auto ncvisual_polyfill_yx(ncvisual* n, int y, int x, uint32_t rgba) -> int { + if(y >= n->rows || y < 0){ + return -1; + } + if(x >= n->cols || x < 0){ + return -1; + } + uint32_t* pixel = &n->data[y * (n->rowstride / 4) + x]; + return ncvisual_polyfill_recurse(n, y, x, rgba, *pixel); +} + #ifndef USE_OIIO // built without ffmpeg or oiio #ifndef USE_FFMPEG auto ncvisual_from_file(const char* filename, nc_err_e* err) -> ncvisual* { diff --git a/src/poc/rotator.c b/src/poc/rotator.c index efb68dd21..c710844f1 100644 --- a/src/poc/rotator.c +++ b/src/poc/rotator.c @@ -23,7 +23,7 @@ rotate_grad(struct notcurses* nc){ } notcurses_render(nc); clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); - uint32_t* rgba = ncplane_rgba(n, 0, 0, dimy, dimx); + uint32_t* rgba = ncplane_rgba(n, NCBLIT_DEFAULT, 0, 0, dimy, dimx); if(rgba == NULL){ return -1; } @@ -149,7 +149,7 @@ rotate(struct notcurses* nc){ clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);; // we now have 2 rows of 20 cells each, with gradients. load 'em. - uint32_t* rgba = ncplane_rgba(n, dimy / 2, 0, 2, XSIZE); + uint32_t* rgba = ncplane_rgba(n, NCBLIT_DEFAULT, dimy / 2, 0, 2, XSIZE); if(rgba == NULL){ return -1; } diff --git a/tests/Ncpp.cpp b/tests/Ncpp.cpp index a8f04a9be..a7f4a4665 100644 --- a/tests/Ncpp.cpp +++ b/tests/Ncpp.cpp @@ -59,7 +59,7 @@ TEST_CASE("Ncpp" // FIXME load something onto standard plane, load it into visual, erase // plane, render visual, check for equivalence... { - Visual v = Visual(*n, 0, 0, -1, -1); + Visual v = Visual(*n, NCBLIT_DEFAULT, 0, 0, -1, -1); } } CHECK(nc.stop()); diff --git a/tests/rotate.cpp b/tests/rotate.cpp index 8e7100308..15be8385d 100644 --- a/tests/rotate.cpp +++ b/tests/rotate.cpp @@ -119,7 +119,7 @@ TEST_CASE("Rotate") { ncvisual_options opts{}; auto rendered = ncvisual_render(nc_, ncv, &opts); REQUIRE(rendered); - uint32_t* rgbaret = ncplane_rgba(rendered, 0, 0, -1, -1); + uint32_t* rgbaret = ncplane_rgba(rendered, NCBLIT_DEFAULT, 0, 0, -1, -1); REQUIRE(rgbaret); for(int i = 0 ; i < height * width / 2 ; ++i){ if(rgbaret[i] & CELL_BG_MASK){ @@ -170,7 +170,7 @@ TEST_CASE("Rotate") { ncvisual_options opts{}; auto rendered = ncvisual_render(nc_, ncv, &opts); REQUIRE(rendered); - uint32_t* rgbaret = ncplane_rgba(rendered, 0, 0, -1, -1); + uint32_t* rgbaret = ncplane_rgba(rendered, NCBLIT_DEFAULT, 0, 0, -1, -1); REQUIRE(rgbaret); for(int i = 0 ; i < height * width / 2 ; ++i){ if(rgbaret[i] & CELL_BG_MASK){