From 8bd8055f727496ed69e4a4fabf7b81a8b27c4f78 Mon Sep 17 00:00:00 2001 From: Nick Black Date: Sat, 14 Dec 2019 17:34:10 -0500 Subject: [PATCH] Distinct fg/bg alpha channels #139 (#141) * improved alpha macros * demo: use new alpha macros * add ncplane_set_*_alpha() * explicitly set fg for uniblock * outro: background is a space #139 * distinct alpha channels for fg/bg #139 * rename 'background' cell to 'default' #142 * doc palette fades --- README.md | 207 ++++++++++++++++++++++++++++++--------- include/notcurses.h | 83 ++++++++++++---- src/demo/luigi.c | 4 +- src/demo/outro.c | 12 ++- src/demo/panelreel.c | 2 +- src/demo/sliding.c | 2 +- src/demo/unicodeblocks.c | 14 ++- src/demo/widecolor.c | 5 +- src/lib/internal.h | 2 +- src/lib/notcurses.c | 141 +++++++++++++++++--------- src/lib/panelreel.c | 2 +- tests/cell.cpp | 6 +- 12 files changed, 351 insertions(+), 129 deletions(-) diff --git a/README.md b/README.md index 0c13cf69f..76d1dd3f3 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Why use this non-standard library? * Thread safety, and efficient use in parallel programs, has been a design consideration from the beginning. -* A svelter design than that codified by X/Open. +* A svelter design than that codified by X/Open: * Exported identifiers are prefixed to avoid common namespace collisions. * The library object exports a minimal set of symbols. Where reasonable, `static inline` header-only code is used. This facilitates compiler @@ -202,6 +202,13 @@ Utility functions operating on the toplevel `notcurses` object include: // following a resize operation, but the cursor might have changed position. int notcurses_resize(struct notcurses* n, int* RESTRICT y, int* RESTRICT x); +// Return our current idea of the terminal dimensions in rows and cols. +static inline void +notcurses_term_dim_yx(const struct notcurses* n, int* RESTRICT rows, + int* RESTRICT cols){ + ncplane_dim_yx(notcurses_stdplane_const(n), rows, cols); +} + // Refresh the physical screen to match what was last rendered (i.e., without // reflecting any changes since the last call to notcurses_render()). This is // primarily useful if the screen is externally corrupted. @@ -376,14 +383,6 @@ int ncplane_resize(struct ncplane* n, int keepy, int keepx, int keepleny, // the standard plane. int ncplane_destroy(struct ncplane* ncp); -// Set the ncplane's background cell to this cell. It will be rendered anywhere -// that the ncplane's gcluster is 0. The default background is all zeroes. -// Erasing the ncplane does not eliminate the background. -int ncplane_set_background(struct ncplane* ncp, const cell* c); - -// Extract the ncplane's background cell into 'c'. -int ncplane_background(struct ncplane* ncp, cell* c); - // Move this plane relative to the standard plane. It is an error to attempt to // move the standard plane. int ncplane_move_yx(struct ncplane* n, int y, int x); @@ -391,6 +390,52 @@ int ncplane_move_yx(struct ncplane* n, int y, int x); // Get the origin of this plane relative to the standard plane. void ncplane_yx(const struct ncplane* n, int* RESTRICT y, int* RESTRICT x); +// Returns the dimensions of this ncplane. +void ncplane_dim_yx(const struct ncplane* n, int* RESTRICT rows, + int* RESTRICT cols); + +// Erase every cell in the ncplane, resetting all attributes to normal, all +// colors to the default color, and all cells to undrawn. All cells associated +// with this ncplane are invalidated, and must not be used after the call, +// excluding the default cell. +void ncplane_erase(struct ncplane* n); + +// Set the specified style bits for the ncplane 'n', whether they're actively +// supported or not. +void ncplane_styles_set(struct ncplane* n, unsigned stylebits); + +// Add the specified styles to the ncplane's existing spec. +void ncplane_styles_on(struct ncplane* n, unsigned stylebits); + +// Remove the specified styles from the ncplane's existing spec. +void ncplane_styles_off(struct ncplane* n, unsigned stylebits); + +// Return the current styling for this ncplane. +unsigned ncplane_styles(const struct ncplane* n); + +``` + +If a given cell's glyph is zero, or its foreground channel is fully transparent, +it is considered to have no foreground. A _default_ cell can be chosen for the +`ncplane`, to be consulted in this case. If the default cell's glyph is likewise +zero (or its foreground channel fully transparent), the plane's foreground is +not rendered. Note that the default cell, like every other cell, has its own +foreground and background channels. + +```c +// Set the ncplane's default cell to this cell. If defined, it will be rendered +// anywhere that the ncplane's gcluster is 0. Erasing the ncplane does not +// reset the default cell; this function must instead be called with a zero c. +int ncplane_set_default(struct ncplane* ncp, const cell* c); + +// Extract the ncplane's default cell into 'c'. +int ncplane_default(struct ncplane* ncp, cell* c); +``` + +`ncplane`s are completely ordered along an imaginary z-axis. Newly-created +`ncplane`s are on the top of the stack. They can be freely reordered. + +```c // Splice ncplane 'n' out of the z-buffer, and reinsert it at the top or bottom. int ncplane_move_top(struct ncplane* n); int ncplane_move_bottom(struct ncplane* n); @@ -400,7 +445,13 @@ int ncplane_move_below(struct ncplane* RESTRICT n, struct ncplane* RESTRICT belo // Splice ncplane 'n' out of the z-buffer, and reinsert it above 'above'. int ncplane_move_above(struct ncplane* RESTRICT n, struct ncplane* RESTRICT above); +``` +Each plane holds a user pointer which can be retrieved and set (or ignored). In +addition, the plane's virtual framebuffer can be accessed (note that this does +not necessarily reflect anything on the actual screen). + +```c // Retrieve the cell at the cursor location on the specified plane, returning // it in 'c'. This copy is safe to use until the ncplane is destroyed/erased. int ncplane_at_cursor(struct ncplane* n, cell* c); @@ -411,18 +462,14 @@ int ncplane_at_cursor(struct ncplane* n, cell* c); void* ncplane_set_userptr(struct ncplane* n, void* opaque); void* ncplane_userptr(struct ncplane* n); const void* ncplane_userptr_const(const struct ncplane* n); +``` -// Returns the dimensions of this ncplane. -void ncplane_dim_yx(const struct ncplane* n, int* RESTRICT rows, - int* RESTRICT cols); - -// Return our current idea of the terminal dimensions in rows and cols. -static inline void -notcurses_term_dim_yx(const struct notcurses* n, int* RESTRICT rows, - int* RESTRICT cols){ - ncplane_dim_yx(notcurses_stdplane_const(n), rows, cols); -} +All output is to `ncplane`s. There is no cost in moving the cursor around the +virtual framebuffer. Output that's never rendered still has some memory transfer +cost as the virtual framebuffer is prepared, but new data overwrites it in +memory. +```c // Move the cursor to the specified position (the cursor needn't be visible). // Returns -1 on error, including negative parameters, or ones exceeding the // plane's dimensions. @@ -569,8 +616,13 @@ ncplane_vprintf_yx(struct ncplane* n, int y, int x, const char* format, va_list } return ncplane_vprintf(n, format, ap); } +``` +Lines and boxes can be drawn, interpolating their colors between their two +endpoints. For a line of a single color, be sure to specify the same channels +on both sides. Boxes allow fairly detailed specification of how they're drawn. +```c // Draw horizontal or vertical lines using the specified cell, starting at the // current cursor position. The cursor will end at the cell following the last // cell output (even, perhaps counter-intuitively, when drawing vertical @@ -696,25 +748,11 @@ ncplane_double_box_sized(struct ncplane* n, uint32_t attr, uint64_t channels, return ncplane_double_box(n, attr, channels, y + ylen - 1, x + xlen - 1, ctlword); } +``` -// Erase every cell in the ncplane, resetting all attributes to normal, all -// colors to the default color, and all cells to undrawn. All cells associated -// with this ncplane are invalidated, and must not be used after the call. -void ncplane_erase(struct ncplane* n); - -// Set the specified style bits for the ncplane 'n', whether they're actively -// supported or not. -void ncplane_styles_set(struct ncplane* n, unsigned stylebits); - -// Add the specified styles to the ncplane's existing spec. -void ncplane_styles_on(struct ncplane* n, unsigned stylebits); - -// Remove the specified styles from the ncplane's existing spec. -void ncplane_styles_off(struct ncplane* n, unsigned stylebits); - -// Return the current styling for this ncplane. -unsigned ncplane_styles(const struct ncplane* n); +My 14 year-old self would never forgive me if we didn't have sweet palette fades. +```c // Fade the ncplane out over the provided time, calling the specified function // when done. Requires a terminal which supports direct color, or at least // palette modification (if the terminal uses a palette, our ability to fade @@ -791,6 +829,10 @@ ncplane_get_bg_alpha(const struct ncplane* nc){ return channels_get_bg_alpha(ncplane_get_channels(nc)); } +// Set the alpha parameters for ncplane 'n'. +int ncplane_set_fg_alpha(struct ncplane* n, int alpha); +int ncplane_set_bg_alpha(struct ncplane* n, int alpha); + // Extract 24 bits of foreground RGB from 'n', split into subcomponents. static inline unsigned ncplane_get_fg_rgb(const struct ncplane* n, unsigned* r, unsigned* g, unsigned* @@ -873,6 +915,16 @@ typedef struct cell { // "not default color" bit is set, any color you load will be ignored. uint64_t channels; // + 8b == 16b } cell; + +#define CELL_WIDEASIAN_MASK 0x8000000080000000ull +#define CELL_FGDEFAULT_MASK 0x4000000000000000ull +#define CELL_FG_MASK 0x00ffffff00000000ull +#define CELL_BGDEFAULT_MASK 0x0000000040000000ull +#define CELL_BG_MASK 0x0000000000ffffffull +#define CELL_ALPHA_MASK 0x0000000030000000ull +#define CELL_ALPHA_SHIFT 28u +#define CELL_ALPHA_TRANS 3 +#define CELL_ALPHA_OPAQUE 0 ``` `cell`s must be initialized with `CELL_TRIVIAL_INITIALIZER` or `cell_init()` @@ -1104,6 +1156,27 @@ cell_set_bg_rgb(cell* cl, int r, int g, int b){ return channels_set_bg_rgb(&cl->channels, r, g, b); } +// Same, but with rgb assembled into a channel (i.e. lower 24 bits). +static inline int +cell_set_fg(cell* c, uint32_t channel){ + return channels_set_fg(&c->channels, channel); +} + +static inline int +cell_set_bg(cell* c, uint32_t channel){ + return channels_set_bg(&c->channels, channel); +} + +static inline int +cell_set_fg_alpha(cell* c, int alpha){ + return channels_set_fg_alpha(&c->channels, alpha); +} + +static inline int +cell_set_bg_alpha(cell* c, int alpha){ + return channels_set_bg_alpha(&c->channels, alpha); +} + // Is the foreground using the "default foreground color"? static inline bool cell_fg_default_p(const cell* cl){ @@ -1117,6 +1190,19 @@ static inline bool cell_bg_default_p(const cell* cl){ return channels_bg_default_p(cl->channels); } + +// Use the default color for the foreground. +static inline void +cell_set_fg_default(cell* c){ + channels_set_fg_default(&c->channels); +} + +// Use the default color for the background. +static inline void +cell_set_bg_default(cell* c){ + channels_set_bg_default(&c->channels); +} + ``` ### Multimedia @@ -1217,19 +1303,29 @@ channel_set_rgb(unsigned* channel, int r, int g, int b){ return 0; } +// Same, but provide an assembled, packed 24 bits of rgb. +static inline int +channel_set(unsigned* channel, unsigned rgb){ + if(rgb > 0xffffffu){ + return -1; + } + *channel = (*channel & ~CELL_BG_MASK) | CELL_BGDEFAULT_MASK | rgb; + return 0; +} + // Extract the 2-bit alpha component from a 32-bit channel. static inline unsigned channel_get_alpha(unsigned channel){ - return (channel & CELL_BGALPHA_MASK) >> 28u; + return (channel & CELL_ALPHA_MASK) >> CELL_ALPHA_SHIFT; } // Set the 2-bit alpha component of the 32-bit channel. static inline int channel_set_alpha(unsigned* channel, int alpha){ - if(alpha < 0 || alpha > 3){ + if(alpha < CELL_ALPHA_OPAQUE || alpha > CELL_ALPHA_TRANS){ return -1; } - *channel = (alpha << 28u) | (*channel & ~CELL_BGALPHA_MASK); + *channel = (alpha << CELL_ALPHA_SHIFT) | (*channel & ~CELL_ALPHA_MASK); return 0; } @@ -1317,6 +1413,27 @@ channels_set_bg_rgb(uint64_t* channels, int r, int g, int b){ return 0; } +// Same, but set an assembled 24 bits of rgb at once. +static inline int +channels_set_fg(uint64_t* channels, unsigned rgb){ + unsigned channel = channels_get_fchannel(*channels); + if(channel_set(&channel, rgb) < 0){ + return -1; + } + *channels = ((uint64_t)channel << 32llu) | (*channels & 0xffffffffllu); + return 0; +} + +static inline int +channels_set_bg(uint64_t* channels, unsigned rgb){ + unsigned channel = channels_get_bchannel(*channels); + if(channel_set(&channel, rgb) < 0){ + return -1; + } + *channels = (*channels & 0xffffffff00000000llu) | channel; + return 0; +} + // Set the 2-bit alpha component of the foreground channel. static inline int channels_set_fg_alpha(uint64_t* channels, int alpha){ @@ -1375,6 +1492,13 @@ channels_set_bg_default(uint64_t* channels){ ### Perf +Rendering performance can be very roughly categorized as inversely proportional +to the product of: +* color changes across the rendered screen, +* planar depth before an opaque glyph and background are locked in, +* number of UTF-8 bytes composing the rendered glyphs, and +* screen geometry + notcurses tracks statistics across its operation, and a snapshot can be acquired using the `notcurses_stats()` function. This function cannot fail. @@ -1414,11 +1538,6 @@ cursor is updated based on the width of the output. Along the way, notcurses attempts to minimize total amount of data written by eliding unnecessary color and style specifications, and moving the cursor over large unchanged areas. -The worst case input frame (in terms of output size) is one whose colors change -from coordinate to coordinate, uses multiple combining characters within each -grapheme cluster, and has a large geometry. Peculiarities of the terminal -make it impossible to comment more meaningfully regarding delay. - Using the "default color" as only one of the foreground or background requires emitting the `op` escape followed by the appropriate escape for changing the fore- or background (since `op` changes both at once). If you're printing full diff --git a/include/notcurses.h b/include/notcurses.h index 812300686..6f38bffe6 100644 --- a/include/notcurses.h +++ b/include/notcurses.h @@ -311,13 +311,13 @@ API int ncplane_resize(struct ncplane* n, int keepy, int keepx, int keepleny, // the standard plane. API int ncplane_destroy(struct ncplane* ncp); -// Set the ncplane's background cell to this cell. It will be rendered anywhere -// that the ncplane's gcluster is 0. The default background is all zeroes. -// Erasing the ncplane does not eliminate the background. -API int ncplane_set_background(struct ncplane* ncp, const cell* c); +// Set the ncplane's default cell to this cell. If defined, it will be rendered +// anywhere that the ncplane's gcluster is 0. Erasing the ncplane does not +// reset the default cell; this function must instead be called with a zero c. +API int ncplane_set_default(struct ncplane* ncp, const cell* c); -// Extract the ncplane's background cell into 'c'. -API int ncplane_background(struct ncplane* ncp, cell* c); +// Extract the ncplane's default cell into 'c'. +API int ncplane_default(struct ncplane* ncp, cell* c); // Move this plane relative to the standard plane. It is an error to attempt to // move the standard plane. @@ -588,16 +588,19 @@ ncplane_box_sized(struct ncplane* n, const cell* ul, const cell* ur, // Erase every cell in the ncplane, resetting all attributes to normal, all // colors to the default color, and all cells to undrawn. All cells associated -// with this ncplane is invalidated, and must not be used after the call. +// with this ncplane is invalidated, and must not be used after the call, +// excluding the default cell. API void ncplane_erase(struct ncplane* n); -#define CELL_WIDEASIAN_MASK 0x8000000000000000ull +#define CELL_WIDEASIAN_MASK 0x8000000080000000ull #define CELL_FGDEFAULT_MASK 0x4000000000000000ull -#define CELL_FGALPHA_MASK 0x3000000000000000ull #define CELL_FG_MASK 0x00ffffff00000000ull #define CELL_BGDEFAULT_MASK 0x0000000040000000ull -#define CELL_BGALPHA_MASK 0x0000000030000000ull #define CELL_BG_MASK 0x0000000000ffffffull +#define CELL_ALPHA_MASK 0x0000000030000000ull +#define CELL_ALPHA_SHIFT 28u +#define CELL_ALPHA_TRANS 3 +#define CELL_ALPHA_OPAQUE 0 // These lowest-level functions manipulate a 64-bit channel encoding directly. // Users will typically manipulate ncplane and cell channels through those APIs, @@ -647,19 +650,29 @@ channel_set_rgb(unsigned* channel, int r, int g, int b){ return 0; } +// Same, but provide an assembled, packed 24 bits of rgb. +static inline int +channel_set(unsigned* channel, unsigned rgb){ + if(rgb > 0xffffffu){ + return -1; + } + *channel = (*channel & ~CELL_BG_MASK) | CELL_BGDEFAULT_MASK | rgb; + return 0; +} + // Extract the 2-bit alpha component from a 32-bit channel. static inline unsigned channel_get_alpha(unsigned channel){ - return (channel & CELL_BGALPHA_MASK) >> 28u; + return (channel & CELL_ALPHA_MASK) >> CELL_ALPHA_SHIFT; } // Set the 2-bit alpha component of the 32-bit channel. static inline int channel_set_alpha(unsigned* channel, int alpha){ - if(alpha < 0 || alpha > 3){ + if(alpha < CELL_ALPHA_OPAQUE || alpha > CELL_ALPHA_TRANS){ return -1; } - *channel = (alpha << 28u) | (*channel & ~CELL_BGALPHA_MASK); + *channel = (alpha << CELL_ALPHA_SHIFT) | (*channel & ~CELL_ALPHA_MASK); return 0; } @@ -747,6 +760,27 @@ channels_set_bg_rgb(uint64_t* channels, int r, int g, int b){ return 0; } +// Same, but set an assembled 24 bits of rgb at once. +static inline int +channels_set_fg(uint64_t* channels, unsigned rgb){ + unsigned channel = channels_get_fchannel(*channels); + if(channel_set(&channel, rgb) < 0){ + return -1; + } + *channels = ((uint64_t)channel << 32llu) | (*channels & 0xffffffffllu); + return 0; +} + +static inline int +channels_set_bg(uint64_t* channels, unsigned rgb){ + unsigned channel = channels_get_bchannel(*channels); + if(channel_set(&channel, rgb) < 0){ + return -1; + } + *channels = (*channels & 0xffffffff00000000llu) | channel; + return 0; +} + // Set the 2-bit alpha component of the foreground channel. static inline int channels_set_fg_alpha(uint64_t* channels, int alpha){ @@ -863,6 +897,17 @@ cell_set_bg_rgb(cell* cl, int r, int g, int b){ return channels_set_bg_rgb(&cl->channels, r, g, b); } +// Same, but with rgb assembled into a channel (i.e. lower 24 bits). +static inline int +cell_set_fg(cell* c, uint32_t channel){ + return channels_set_fg(&c->channels, channel); +} + +static inline int +cell_set_bg(cell* c, uint32_t channel){ + return channels_set_bg(&c->channels, channel); +} + // Is the foreground using the "default foreground color"? static inline bool cell_fg_default_p(const cell* cl){ @@ -938,7 +983,7 @@ ncplane_get_bg_rgb(const struct ncplane* n, unsigned* r, unsigned* g, unsigned* API int ncplane_set_fg_rgb(struct ncplane* n, int r, int g, int b); API int ncplane_set_bg_rgb(struct ncplane* n, int r, int g, int b); -// Same, but with rgb assembled into a channel (i.e. lower 32 bits). +// Same, but with rgb assembled into a channel (i.e. lower 24 bits). API void ncplane_set_fg(struct ncplane* n, uint32_t channel); API void ncplane_set_bg(struct ncplane* n, uint32_t channel); @@ -946,6 +991,10 @@ API void ncplane_set_bg(struct ncplane* n, uint32_t channel); API void ncplane_set_fg_default(struct ncplane* n); API void ncplane_set_bg_default(struct ncplane* n); +// Set the alpha parameters for ncplane 'n'. +API int ncplane_set_fg_alpha(struct ncplane* n, int alpha); +API int ncplane_set_bg_alpha(struct ncplane* n, int alpha); + // Set the specified style bits for the ncplane 'n', whether they're actively // supported or not. API void ncplane_styles_set(struct ncplane* n, unsigned stylebits); @@ -1043,13 +1092,13 @@ cell_styles_off(cell* c, unsigned stylebits){ c->attrword &= ~((stylebits & 0xffff) << CELL_STYLE_SHIFT); } -// use the default color for the foreground +// Use the default color for the foreground. static inline void cell_set_fg_default(cell* c){ channels_set_fg_default(&c->channels); } -// use the default color for the background +// Use the default color for the background. static inline void cell_set_bg_default(cell* c){ channels_set_bg_default(&c->channels); @@ -1061,7 +1110,7 @@ cell_set_fg_alpha(cell* c, int alpha){ } static inline int -cell_set_bg_alpha(cell *c, int alpha){ +cell_set_bg_alpha(cell* c, int alpha){ return channels_set_bg_alpha(&c->channels, alpha); } diff --git a/src/demo/luigi.c b/src/demo/luigi.c index 1034352d9..9fae18395 100644 --- a/src/demo/luigi.c +++ b/src/demo/luigi.c @@ -107,8 +107,8 @@ static const char luigi3[] = "0000001111100000" static int draw_luigi(struct ncplane* n, const char* sprite){ cell bgc = CELL_TRIVIAL_INITIALIZER; - cell_set_bg_alpha(&bgc, 3); - ncplane_set_background(n, &bgc); + cell_set_bg_alpha(&bgc, CELL_ALPHA_TRANS); + ncplane_set_default(n, &bgc); cell_release(n, &bgc); size_t s; int sbytes; diff --git a/src/demo/outro.c b/src/demo/outro.c index a5ce6f945..46b759695 100644 --- a/src/demo/outro.c +++ b/src/demo/outro.c @@ -19,14 +19,15 @@ outro_message(struct notcurses* nc, int* rows, int* cols){ if(on == NULL){ return NULL; } - cell bgcell = CELL_TRIVIAL_INITIALIZER; + cell bgcell = CELL_SIMPLE_INITIALIZER(' '); channels_set_bg_rgb(&bgcell.channels, 0x58, 0x36, 0x58); - ncplane_set_background(on, &bgcell); + if(ncplane_set_default(on, &bgcell) < 0){ + return NULL; + } ncplane_dim_yx(on, rows, cols); int ybase = 0; // bevel the upper corners - uint64_t channel = 0; - if(channels_set_bg_alpha(&channel, 3)){ + if(ncplane_set_bg_alpha(on, CELL_ALPHA_TRANS)){ return NULL; } if(ncplane_cursor_move_yx(on, ybase, 0)){ @@ -60,6 +61,9 @@ outro_message(struct notcurses* nc, int* rows, int* cols){ if(ncplane_set_bg_rgb(on, 0, 180, 180)){ return NULL; } + if(ncplane_set_bg_alpha(on, CELL_ALPHA_OPAQUE)){ // FIXME use intermediate + return NULL; + } if(ncplane_putstr_aligned(on, ++ybase, str0, NCALIGN_CENTER) < 0){ return NULL; } diff --git a/src/demo/panelreel.c b/src/demo/panelreel.c index 8e9552446..6a45b0fc7 100644 --- a/src/demo/panelreel.c +++ b/src/demo/panelreel.c @@ -270,7 +270,7 @@ panelreel_demo_core(struct notcurses* nc, int efd, tabletctx** tctxs){ cell_set_fg_rgb(&popts.tabletattr, 19, 161, 14); cell_set_fg_rgb(&popts.borderattr, 136, 23, 152); cell_set_bg_rgb(&popts.borderattr, 0, 0, 0); - if(channels_set_bg_alpha(&popts.bgchannel, 3)){ + if(channels_set_bg_alpha(&popts.bgchannel, CELL_ALPHA_TRANS)){ return NULL; } struct ncplane* w = notcurses_stdplane(nc); diff --git a/src/demo/sliding.c b/src/demo/sliding.c index b6e696c43..bb8d43555 100644 --- a/src/demo/sliding.c +++ b/src/demo/sliding.c @@ -119,7 +119,7 @@ fill_chunk(struct ncplane* n, int idx){ cell_init(&style); cell_set_fg_rgb(&style, r, g, b); cell_prime(n, &style, "█", 0, channels); - ncplane_set_background(n, &style); + ncplane_set_default(n, &style); cell_release(n, &style); return 0; } diff --git a/src/demo/unicodeblocks.c b/src/demo/unicodeblocks.c index b08050e98..bfc72e30a 100644 --- a/src/demo/unicodeblocks.c +++ b/src/demo/unicodeblocks.c @@ -23,10 +23,16 @@ draw_block(struct ncplane* nn, uint32_t blockstart){ cell ll = CELL_TRIVIAL_INITIALIZER, lr = CELL_TRIVIAL_INITIALIZER; cell hl = CELL_TRIVIAL_INITIALIZER, vl = CELL_TRIVIAL_INITIALIZER; cells_rounded_box(nn, 0, 0, &ul, &ur, &ll, &lr, &hl, &vl); - cell_set_bg_alpha(&ul, 3); - cell_set_bg_alpha(&ur, 3); - cell_set_bg_alpha(&ll, 3); - cell_set_bg_alpha(&lr, 3); + cell_set_bg_alpha(&ul, CELL_ALPHA_TRANS); + cell_set_bg_alpha(&ur, CELL_ALPHA_TRANS); + cell_set_bg_alpha(&ll, CELL_ALPHA_TRANS); + cell_set_bg_alpha(&lr, CELL_ALPHA_TRANS); + cell_set_fg_rgb(&ll, 255, 255, 255); + cell_set_fg_rgb(&lr, 255, 255, 255); + cell_set_fg_rgb(&ul, 255, 255, 255); + cell_set_fg_rgb(&ur, 255, 255, 255); + cell_set_fg_rgb(&hl, 255, 255, 255); + cell_set_fg_rgb(&vl, 255, 255, 255); cell_set_bg_rgb(&hl, 0, 0, 0); cell_set_bg_rgb(&vl, 0, 0, 0); if(ncplane_box_sized(nn, &ul, &ur, &ll, &lr, &hl, &vl, diff --git a/src/demo/widecolor.c b/src/demo/widecolor.c index bc61aa0ec..cda04a369 100644 --- a/src/demo/widecolor.c +++ b/src/demo/widecolor.c @@ -283,9 +283,8 @@ message(struct ncplane* n, int maxy, int maxx, int num, int total, int bytes_out, int egs_out, int cols_out){ cell c = CELL_TRIVIAL_INITIALIZER; cell_load(n, &c, " "); - cell_set_fg_alpha(&c, 3); - cell_set_bg_alpha(&c, 3); - ncplane_set_background(n, &c); + cell_set_bg_alpha(&c, CELL_ALPHA_TRANS); + ncplane_set_default(n, &c); cell_release(n, &c); uint64_t channels = 0; ncplane_set_fg_rgb(n, 64, 128, 240); diff --git a/src/lib/internal.h b/src/lib/internal.h index 3b391c54e..5fe1b666a 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -50,7 +50,7 @@ typedef struct ncplane { uint64_t channels; // works the same way as cells uint32_t attrword; // same deal as in a cell void* userptr; // slot for the user to stick some opaque pointer - cell background; // cell written anywhere that fb[i].gcluster == 0 + cell defcell; // cell written anywhere that fb[i].gcluster == 0 struct notcurses* nc; // notcurses object of which we are a part } ncplane; diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index df7b4cba4..38a332b4d 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -347,7 +347,7 @@ ncplane_create(notcurses* nc, int rows, int cols, int yoff, int xoff){ p->z = nc->top; nc->top = p; p->nc = nc; - cell_init(&p->background); + cell_init(&p->defcell); nc->stats.fbbytes += fbsize; return p; } @@ -773,6 +773,8 @@ int notcurses_stop(notcurses* nc){ int ret = 0; if(nc){ drop_signals(nc); + // FIXME these can fail if we stop in the middle of a rendering operation. + // turn the fd back to blocking, perhaps? if(nc->rmcup && term_emit("rmcup", nc->rmcup, nc->ttyfp, true)){ ret = -1; } @@ -851,20 +853,28 @@ int ncplane_set_fg_rgb(ncplane* n, int r, int g, int b){ return channels_set_fg_rgb(&n->channels, r, g, b); } -void ncplane_set_fg(ncplane* n, uint32_t halfchannel){ - n->channels = ((uint64_t)halfchannel << 32ul) | (n->channels & 0xffffffffull); +void ncplane_set_fg(ncplane* n, uint32_t channel){ + n->channels = ((uint64_t)channel << 32ul) | (n->channels & 0xffffffffull); } -void ncplane_set_bg(ncplane* n, uint32_t halfchannel){ - n->channels = (n->channels & 0xffffffff00000000ull) | halfchannel; +void ncplane_set_bg(ncplane* n, uint32_t channel){ + n->channels = (n->channels & 0xffffffff00000000ull) | channel; } -int ncplane_set_background(ncplane* ncp, const cell* c){ - return cell_duplicate(ncp, &ncp->background, c); +int ncplane_set_fg_alpha(ncplane* n, int alpha){ + return channels_set_fg_alpha(&n->channels, alpha); } -int ncplane_background(ncplane* ncp, cell* c){ - return cell_duplicate(ncp, c, &ncp->background); +int ncplane_set_bg_alpha(ncplane *n, int alpha){ + return channels_set_bg_alpha(&n->channels, alpha); +} + +int ncplane_set_default(ncplane* ncp, const cell* c){ + return cell_duplicate(ncp, &ncp->defcell, c); +} + +int ncplane_default(ncplane* ncp, cell* c){ + return cell_duplicate(ncp, c, &ncp->defcell); } // 3 for foreground, 4 for background, ugh FIXME @@ -1052,10 +1062,26 @@ term_setstyles(const notcurses* nc, FILE* out, uint32_t* curattr, const cell* c, return ret; } -// find the topmost cell for this coordinate -static const cell* -visible_cell(int y, int x, ncplane** retp){ - ncplane* p = *retp; +// Find the topmost cell for this coordinate by walking down the z-buffer, +// looking for an intersecting ncplane. Once we've found one, check it for +// transparency in either the back- or foreground. If the alpha channel is +// active, keep descending and blending until we hit opacity, or bedrock. We +// recurse to find opacity, and blend the result into what we have. The +// 'findfore' and 'findback' bools control our recursion--there's no point in +// going further down when a color is locked in, so don't (for instance) recurse +// further when we have a transparent foreground and opaque background atop an +// opaque foreground and transparent background. The cell we ultimately return +// (a const ref to 'c') is backed by '*retp' via rawdog copy; the caller must +// not call cell_release() upon it, nor use it beyond the scope of the render. +// +// So, as we go down, we find planes which can have impact on the result. Once +// we've locked the result in (base case), write the deep values we have to 'c'. +// Then, as we come back up, blend them as appropriate. The actual glyph is +// whichever one occurs at the top with a non-transparent α (α < 3). To effect +// tail recursion, though, we instead write first, and then recurse, blending +// as we descend. α <= 0 is opaque. α >= 3 is fully transparent. +static ncplane* +dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha){ while(p){ // where in the plane this coordinate would be, based off absy/absx. the // true origin is 0,0, so abs=2,2 means coordinate 3,3 would be 1,1, while @@ -1064,33 +1090,52 @@ visible_cell(int y, int x, ncplane** retp){ poffy = y - p->absy; poffx = x - p->absx; if(poffy < p->leny && poffy >= 0){ - if(poffx < p->lenx && poffx >= 0){ - *retp = p; + if(poffx < p->lenx && poffx >= 0){ // p is valid for this y, x const cell* vis = &p->fb[fbcellidx(p, poffy, poffx)]; // if we never loaded any content into the cell (or obliterated it by // writing in a zero), use the plane's background cell. if(vis->gcluster == 0){ - vis = &p->background; + vis = &p->defcell; } - // FIXME do this more rigorously, PoC - if(cell_get_fg_alpha(vis) || cell_get_bg_alpha(vis)){ - *retp = p->z; - const cell* trans = visible_cell(y, x, retp); - if(trans){ - vis = trans; - }else{ - *retp = p; + bool lockedglyph = false; + int nalpha; + if(falpha > 0 && (nalpha = cell_get_fg_alpha(vis)) < CELL_ALPHA_TRANS){ + if(c->gcluster == 0){ // never write fully trans glyphs, never replace + if( (c->gcluster = vis->gcluster) ){ // index copy only + lockedglyph = true; // must return this ncplane for this glyph + c->attrword = vis->attrword; + cell_set_fg(c, cell_get_fg(vis)); // FIXME blend it in + falpha -= (CELL_ALPHA_TRANS - nalpha); // FIXME blend it in + } } } - return vis; + if(balpha > 0 && (nalpha = cell_get_bg_alpha(vis)) < CELL_ALPHA_TRANS){ + cell_set_bg(c, cell_get_bg(vis)); // FIXME blend it in + balpha -= (CELL_ALPHA_TRANS - nalpha); + } + if((falpha > 0 || balpha > 0) && p->z){ // we must go further! + ncplane* cand = dig_visible_cell(c, y, x, p->z, falpha, balpha); + if(!lockedglyph && cand){ + p = cand; + } + } + return p; } } p = p->z; } - // should never happen for valid y, x thanks to the stdscreen + // should never happen for valid y, x thanks to the stdplane. you fucked up! return NULL; } +static inline ncplane* +visible_cell(cell* c, int y, int x, ncplane* n){ + cell_init(c); + return dig_visible_cell(c, y, x, n, CELL_ALPHA_TRANS, CELL_ALPHA_TRANS); +} + +// Call with c->gcluster == 3, falpha == 3, balpha == 0, *retp == topplane. + // 'n' ends up above 'above' int ncplane_move_above(ncplane* restrict n, ncplane* restrict above){ ncplane** an = find_above_ncplane(n); @@ -1215,21 +1260,20 @@ notcurses_render_internal(notcurses* nc){ term_emit("cup", tiparm(nc->cup, y, 0), out, false); for(x = 0 ; x < nc->stdscr->lenx ; ++x){ unsigned r, g, b, br, bg, bb; - ncplane* p = nc->top; - const cell* c = visible_cell(y, x, &p); - if(c == NULL){ - continue; // shrug? - } + ncplane* p; + cell c; // no need to initialize + p = visible_cell(&c, y, x, nc->top); + assert(p); // don't try to print a wide character on the last column; it'll instead // be printed on the next line. they probably shouldn't be admitted, but // we can end up with one due to a resize. - if((x + 1 >= nc->stdscr->lenx && cell_double_wide_p(c))){ + if((x + 1 >= nc->stdscr->lenx && cell_double_wide_p(&c))){ continue; } // set the style. this can change the color back to the default; if it // does, we need update our elision possibilities. bool normalized; - term_setstyles(nc, out, &curattr, c, &normalized); + term_setstyles(nc, out, &curattr, &c, &normalized); if(normalized){ defaultelidable = true; bgelidable = false; @@ -1241,7 +1285,7 @@ notcurses_render_internal(notcurses* nc){ // then a turnon for whichever aren't default. // we can elide the default set iff the previous used both defaults - if(cell_fg_default_p(c) || cell_bg_default_p(c)){ + if(cell_fg_default_p(&c) || cell_bg_default_p(&c)){ if(!defaultelidable){ ++nc->stats.defaultemissions; term_emit("op", nc->op, out, false); @@ -1255,8 +1299,8 @@ notcurses_render_internal(notcurses* nc){ } // we can elide the foreground set iff the previous used fg and matched - if(!cell_fg_default_p(c)){ - cell_get_fg_rgb(c, &r, &g, &b); + if(!cell_fg_default_p(&c)){ + cell_get_fg_rgb(&c, &r, &g, &b); if(fgelidable && lastr == r && lastg == g && lastb == b){ ++nc->stats.fgelisions; }else{ @@ -1267,8 +1311,8 @@ notcurses_render_internal(notcurses* nc){ lastr = r; lastg = g; lastb = b; defaultelidable = false; } - if(!cell_bg_default_p(c)){ - cell_get_bg_rgb(c, &br, &bg, &bb); + if(!cell_bg_default_p(&c)){ + cell_get_bg_rgb(&c, &br, &bg, &bb); if(bgelidable && lastbr == br && lastbg == bg && lastbb == bb){ ++nc->stats.bgelisions; }else{ @@ -1279,11 +1323,11 @@ notcurses_render_internal(notcurses* nc){ lastbr = br; lastbg = bg; lastbb = bb; defaultelidable = false; } - term_putc(out, p, c); - if(cell_double_wide_p(c)){ +// fprintf(stderr, "[%02d/%02d] 0x%02x 0x%02x 0x%02x %p\n", y, x, r, g, b, p); + term_putc(out, p, &c); + if(cell_double_wide_p(&c)){ ++x; } -//fprintf(stderr, "[%02d/%02d]\n", y, x); } } ret |= fclose(out); @@ -1378,10 +1422,11 @@ int cell_duplicate(ncplane* n, cell* targ, const cell* c){ } int ncplane_putc(ncplane* n, const cell* c){ + ncplane_lock(n); if(cursor_invalid_p(n)){ + ncplane_unlock(n); return -1; } - ncplane_lock(n); cell* targ = &n->fb[fbcellidx(n, n->y, n->x)]; if(cell_duplicate(n, targ, c) < 0){ ncplane_unlock(n); @@ -1830,11 +1875,11 @@ void ncplane_erase(ncplane* n){ // we must preserve the background, but a pure cell_duplicate() would be // wiped out by the egcpool_dump(). do a duplication (to get the attrword // and channels), and then reload. - char* egc = cell_egc_copy(n, &n->background); + char* egc = cell_egc_copy(n, &n->defcell); memset(n->fb, 0, sizeof(*n->fb) * n->lenx * n->leny); egcpool_dump(&n->pool); egcpool_init(&n->pool); - cell_load(n, &n->background, egc); + cell_load(n, &n->defcell, egc); free(egc); ncplane_unlock(n); } @@ -1948,7 +1993,7 @@ alloc_ncplane_palette(ncplane* n, planepalette* pp){ } } // FIXME factor this duplication out - channels = n->background.channels; + channels = n->defcell.channels; pp->channels[y * pp->cols] = channels; channels_get_fg_rgb(channels, &r, &g, &b); if(r > pp->maxr){ @@ -2098,20 +2143,20 @@ int ncplane_fadeout(ncplane* n, const struct timespec* ts){ } } } - cell* c = &n->background; + cell* c = &n->defcell; if(!cell_fg_default_p(c)){ channels_get_fg_rgb(pp.channels[pp.cols * y], &r, &g, &b); r = r * (maxsteps - iter) / maxsteps; g = g * (maxsteps - iter) / maxsteps; b = b * (maxsteps - iter) / maxsteps; - cell_set_fg_rgb(&n->background, r, g, b); + cell_set_fg_rgb(&n->defcell, r, g, b); } if(!cell_bg_default_p(c)){ channels_get_bg_rgb(pp.channels[pp.cols * y], &br, &bg, &bb); br = br * (maxsteps - iter) / maxsteps; bg = bg * (maxsteps - iter) / maxsteps; bb = bb * (maxsteps - iter) / maxsteps; - cell_set_bg_rgb(&n->background, br, bg, bb); + cell_set_bg_rgb(&n->defcell, br, bg, bb); } notcurses_render(n->nc); uint64_t nextwake = (iter + 1) * nanosecs_step + startns; diff --git a/src/lib/panelreel.c b/src/lib/panelreel.c index 3db5fa0b6..2db466c1a 100644 --- a/src/lib/panelreel.c +++ b/src/lib/panelreel.c @@ -598,7 +598,7 @@ panelreel* panelreel_create(ncplane* w, const panelreel_options* popts, int efd) } cell bgc = CELL_TRIVIAL_INITIALIZER; bgc.channels = popts->bgchannel; - ncplane_set_background(pr->p, &bgc); + ncplane_set_default(pr->p, &bgc); cell_release(pr->p, &bgc); if(panelreel_redraw(pr)){ ncplane_destroy(pr->p); diff --git a/tests/cell.cpp b/tests/cell.cpp index 9822d01f2..1779af039 100644 --- a/tests/cell.cpp +++ b/tests/cell.cpp @@ -41,7 +41,7 @@ TEST_F(CellTest, SetItalic) { cell_styles_set(&c, CELL_STYLE_ITALIC); ASSERT_EQ(1, cell_load(n_, &c, "i")); cell_set_fg_rgb(&c, 255, 255, 255); - ncplane_set_background(n_, &c); + ncplane_set_default(n_, &c); cell_release(n_, &c); EXPECT_EQ(0, notcurses_render(nc_)); cell_styles_off(&c, CELL_STYLE_ITALIC); @@ -55,7 +55,7 @@ TEST_F(CellTest, SetBold) { cell_styles_set(&c, CELL_STYLE_BOLD); ASSERT_EQ(1, cell_load(n_, &c, "b")); cell_set_fg_rgb(&c, 255, 255, 255); - ncplane_set_background(n_, &c); + ncplane_set_default(n_, &c); cell_release(n_, &c); EXPECT_EQ(0, notcurses_render(nc_)); cell_styles_off(&c, CELL_STYLE_BOLD); @@ -69,7 +69,7 @@ TEST_F(CellTest, SetUnderline) { cell_styles_set(&c, CELL_STYLE_UNDERLINE); ASSERT_EQ(1, cell_load(n_, &c, "u")); cell_set_fg_rgb(&c, 255, 255, 255); - ncplane_set_background(n_, &c); + ncplane_set_default(n_, &c); cell_release(n_, &c); EXPECT_EQ(0, notcurses_render(nc_)); cell_styles_off(&c, CELL_STYLE_UNDERLINE);