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
This commit is contained in:
Nick Black 2019-12-14 17:34:10 -05:00 committed by GitHub
parent 582017a16a
commit 8bd8055f72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 351 additions and 129 deletions

207
README.md
View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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,

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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);