diff --git a/USAGE.md b/USAGE.md index 4bb4ef9484..68eb304755 100644 --- a/USAGE.md +++ b/USAGE.md @@ -1601,11 +1601,22 @@ manner. // characters, followed by a NUL terminator. // // Multi-column characters can only have a single style/color throughout. +// Existence is suffering, and thus wcwidth() is not reliable. It's just +// quoting whether or not the EGC contains a "Wide Asian" double-width +// character. This is set for some things, like most emoji, and not set for +// other things, like cuneiform. True display width is a *function of the +// font and terminal*. Among the longest Unicode codepoints is +// +// U+FDFD ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM ﷽ +// +// wcwidth() rather optimistically claims this most exalted glyph to occupy +// a single column. BiDi text is too complicated for me to even get into here. +// Be assured there are no easy answers; ours is indeed a disturbing Universe. // // Each nccell occupies 16 static bytes (128 bits). The surface is thus ~1.6MB // for a (pretty large) 500x200 terminal. At 80x43, it's less than 64KB. -// Dynamic requirements can add up to 16MB to an ncplane, but such large pools -// are unlikely in common use. +// Dynamic requirements (the egcpool) can add up to 16MB to an ncplane, but +// such large pools are unlikely in common use. // // We implement some small alpha compositing. Foreground and background both // have two bits of inverted alpha. The actual grapheme written to a cell is @@ -1624,6 +1635,9 @@ manner. // meaningfully set transparency, but it can be mixed into a cascading color. // RGB is used if neither default terminal colors nor palette indexing are in // play, and fully supports all transparency options. +// +// This structure is exposed only so that most functions can be inlined. Do not +// directly modify or access the fields of this structure; use the API. typedef struct nccell { // These 32 bits, together with the associated plane's associated egcpool, // completely define this cell's EGC. Unless the EGC requires more than four @@ -1665,12 +1679,13 @@ typedef struct nccell { // can be determined by checking whether ->gcluster is zero. uint8_t width; // 1B → 6B (8 bits of EGC column width) uint16_t stylemask; // 2B → 8B (16 bits of NCSTYLE_* attributes) - // (channels & 0x8000000000000000ull): reserved, must be 0 + // (channels & 0x8000000000000000ull): blitted to upper-left quadrant // (channels & 0x4000000000000000ull): foreground is *not* "default color" // (channels & 0x3000000000000000ull): foreground alpha (2 bits) // (channels & 0x0800000000000000ull): foreground uses palette index - // (channels & 0x0400000000000000ull): glyph is entirely foreground - // (channels & 0x0300000000000000ull): reserved, must be 0 + // (channels & 0x0400000000000000ull): blitted to upper-right quadrant + // (channels & 0x0200000000000000ull): blitted to lower-left quadrant + // (channels & 0x0100000000000000ull): blitted to lower-right quadrant // (channels & 0x00ffffff00000000ull): foreground in 3x8 RGB (rrggbb) // (channels & 0x0000000080000000ull): reserved, must be 0 // (channels & 0x0000000040000000ull): background is *not* "default color" diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index f86cba8986..b170eebdcd 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -591,20 +591,20 @@ typedef struct nccell { // can be determined by checking whether ->gcluster is zero. uint8_t width; // 1B → 6B (8 bits of EGC column width) uint16_t stylemask; // 2B → 8B (16 bits of NCSTYLE_* attributes) - // (channels & 0x8000000000000000ull): reserved, must be 0 + // (channels & 0x8000000000000000ull): blitted to upper-left quadrant // (channels & 0x4000000000000000ull): foreground is *not* "default color" // (channels & 0x3000000000000000ull): foreground alpha (2 bits) // (channels & 0x0800000000000000ull): foreground uses palette index - // (channels & 0x0400000000000000ull): entirely foreground (used for optimization in rasterization) - // (channels & 0x0300000000000000ull): reserved, must be 0 - // (channels & 0x00ffffff00000000ull): foreground in 3x8 RGB (rrggbb) + // (channels & 0x0400000000000000ull): blitted to upper-right quadrant + // (channels & 0x0200000000000000ull): blitted to lower-left quadrant + // (channels & 0x0100000000000000ull): blitted to lower-right quadrant + // (channels & 0x00ffffff00000000ull): foreground in 3x8 RGB (rrggbb) / pindex // (channels & 0x0000000080000000ull): reserved, must be 0 // (channels & 0x0000000040000000ull): background is *not* "default color" // (channels & 0x0000000030000000ull): background alpha (2 bits) // (channels & 0x0000000008000000ull): background uses palette index - // (channels & 0x0000000004000000ull): drawn by ncvisual (used to trigger blitter stacking) - // (channels & 0x0000000003000000ull): reserved, must be 0 - // (channels & 0x0000000000ffffffull): background in 3x8 RGB (rrggbb) + // (channels & 0x0000000007000000ull): reserved, must be 0 + // (channels & 0x0000000000ffffffull): background in 3x8 RGB (rrggbb) / pindex // At render time, these 24-bit values are quantized down to terminal // capabilities, if necessary. There's a clear path to 10-bit support should // we one day need it, but keep things cagey for now. "default color" is diff --git a/src/lib/blit.c b/src/lib/blit.c index f91ce3da50..770d757dd2 100644 --- a/src/lib/blit.c +++ b/src/lib/blit.c @@ -125,7 +125,6 @@ tria_blit(ncplane* nc, int placey, int placex, int linesize, } if(ffmpeg_trans_p(rgbbase_up[3]) || ffmpeg_trans_p(rgbbase_down[3])){ cell_set_bg_alpha(c, CELL_ALPHA_TRANSPARENT); - cell_set_blitted(c); if(ffmpeg_trans_p(rgbbase_up[3]) && ffmpeg_trans_p(rgbbase_down[3])){ cell_set_fg_alpha(c, CELL_ALPHA_TRANSPARENT); }else if(ffmpeg_trans_p(rgbbase_up[3])){ // down has the color @@ -133,6 +132,7 @@ tria_blit(ncplane* nc, int placey, int placex, int linesize, return -1; } cell_set_fg_rgb8(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]); + cell_set_blitquadrants(c, 0, 0, 1, 1); ++total; }else{ // up has the color // upper half block @@ -140,6 +140,7 @@ tria_blit(ncplane* nc, int placey, int placex, int linesize, return -1; } cell_set_fg_rgb8(c, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2]); + cell_set_blitquadrants(c, 1, 1, 0, 0); ++total; } }else{ @@ -309,14 +310,17 @@ qtrans_check(nccell* c, bool blendcolors, egc = ""; }else{ cell_set_fg_rgb8(c, rgbbase_br[0], rgbbase_br[1], rgbbase_br[2]); + cell_set_blitquadrants(c, 0, 0, 0, 1); egc = "▗"; } }else{ if(ffmpeg_trans_p(rgbbase_br[3])){ cell_set_fg_rgb8(c, rgbbase_bl[0], rgbbase_bl[1], rgbbase_bl[2]); + cell_set_blitquadrants(c, 0, 0, 1, 0); egc = "▖"; }else{ cell_set_fchannel(c, lerp(bl, br)); + cell_set_blitquadrants(c, 0, 0, 1, 1); egc = "▄"; } } @@ -324,16 +328,20 @@ qtrans_check(nccell* c, bool blendcolors, if(ffmpeg_trans_p(rgbbase_bl[3])){ if(ffmpeg_trans_p(rgbbase_br[3])){ // entire bottom is transparent cell_set_fg_rgb8(c, rgbbase_tr[0], rgbbase_tr[1], rgbbase_tr[2]); + cell_set_blitquadrants(c, 0, 1, 0, 0); egc = "▝"; }else{ cell_set_fchannel(c, lerp(tr, br)); + cell_set_blitquadrants(c, 0, 1, 0, 1); egc = "▐"; } }else if(ffmpeg_trans_p(rgbbase_br[3])){ // only br is transparent cell_set_fchannel(c, lerp(tr, bl)); + cell_set_blitquadrants(c, 0, 1, 1, 0); egc = "▞"; }else{ cell_set_fchannel(c, trilerp(tr, bl, br)); + cell_set_blitquadrants(c, 0, 1, 1, 1); egc = "▟"; } } @@ -342,28 +350,35 @@ qtrans_check(nccell* c, bool blendcolors, if(ffmpeg_trans_p(rgbbase_bl[3])){ if(ffmpeg_trans_p(rgbbase_br[3])){ cell_set_fg_rgb8(c, rgbbase_tl[0], rgbbase_tl[1], rgbbase_tl[2]); + cell_set_blitquadrants(c, 1, 0, 0, 0); egc = "▘"; }else{ cell_set_fchannel(c, lerp(tl, br)); + cell_set_blitquadrants(c, 1, 0, 0, 1); egc = "▚"; } }else if(ffmpeg_trans_p(rgbbase_br[3])){ cell_set_fchannel(c, lerp(tl, bl)); + cell_set_blitquadrants(c, 1, 0, 1, 0); egc = "▌"; }else{ cell_set_fchannel(c, trilerp(tl, bl, br)); + cell_set_blitquadrants(c, 1, 0, 1, 1); egc = "▙"; } }else if(ffmpeg_trans_p(rgbbase_bl[3])){ if(ffmpeg_trans_p(rgbbase_br[3])){ // entire bottom is transparent cell_set_fchannel(c, lerp(tl, tr)); + cell_set_blitquadrants(c, 1, 1, 0, 0); egc = "▀"; }else{ // only bl is transparent cell_set_fchannel(c, trilerp(tl, tr, br)); + cell_set_blitquadrants(c, 1, 1, 0, 1); egc = "▜"; } }else if(ffmpeg_trans_p(rgbbase_br[3])){ // only br is transparent cell_set_fchannel(c, trilerp(tl, tr, bl)); + cell_set_blitquadrants(c, 1, 1, 1, 0); egc = "▛"; }else{ return NULL; // no transparency @@ -434,8 +449,6 @@ quadrant_blit(ncplane* nc, int placey, int placex, int linesize, cell_set_bg_alpha(c, CELL_ALPHA_BLEND); cell_set_fg_alpha(c, CELL_ALPHA_BLEND); } - }else{ - cell_set_blitted(c); } if(*egc){ if(pool_blit_direct(&nc->pool, c, egc, strlen(egc), 1) <= 0){ @@ -548,15 +561,20 @@ sex_solver(const uint32_t rgbas[6], uint64_t* channels, bool blendcolors){ } static const char* -sex_trans_check(const uint32_t rgbas[6], uint64_t* channels, bool blendcolors){ +sex_trans_check(cell* c, const uint32_t rgbas[6], bool blendcolors){ + // bit is *set* where sextant *is not* + // 32: bottom right 16: bottom left + // 8: middle right 4: middle left + // 2: upper right 1: upper left static const char* sex[64] = { - "█", "🬻", "🬺", "🬹", "🬸", "🬷", "🬶", "🬵", "🬴", "🬳", "🬲", // 10 - "🬱", "🬰", "🬯", "🬮", "🬭", "🬬", "🬫", "🬪", "🬩", "🬨", "▐", // 21 - "🬧", "🬦", "🬥", "🬤", "🬣", "🬢", "🬡", "🬠", "🬟", // 30 - "🬞", "🬝", "🬜", "🬛", "🬚", "🬙", "🬘", "🬗", "🬖", "🬕", // 40 - "🬔", "▌", "🬓", "🬒", "🬑", "🬐", "🬏", "🬎", "🬍", "🬌", // 50 - "🬋", "🬊", "🬉", "🬈", "🬇", "🬆", "🬅", "🬄", "🬃", "🬂", // 60 - "🬁", "🬀", " ", + "█", "🬻", "🬺", "🬹", "🬸", "🬷", "🬶", "🬵", + "🬴", "🬳", "🬲", "🬱", "🬰", "🬯", "🬮", "🬭", + "🬬", "🬫", "🬪", "🬩", "🬨", "▐", "🬧", "🬦", + "🬥", "🬤", "🬣", "🬢", "🬡", "🬠", "🬟", "🬞", + "🬝", "🬜", "🬛", "🬚", "🬙", "🬘", "🬗", "🬖", + "🬕", "🬔", "▌", "🬓", "🬒", "🬑", "🬐", "🬏", + "🬎", "🬍", "🬌", "🬋", "🬊", "🬉", "🬈", "🬇", + "🬆", "🬅", "🬄", "🬃", "🬂", "🬁", "🬀", " ", }; unsigned transstring = 0; unsigned r = 0, g = 0, b = 0; @@ -574,21 +592,23 @@ sex_trans_check(const uint32_t rgbas[6], uint64_t* channels, bool blendcolors){ if(transstring == 0){ return NULL; } - channels_set_bg_alpha(channels, CELL_ALPHA_TRANSPARENT); + cell_set_bg_alpha(c, CELL_ALPHA_TRANSPARENT); // there were some transparent pixels. since they get priority, the foreground // is just a general lerp across non-transparent pixels. const char* egc = sex[transstring]; - channels_set_bg_alpha(channels, CELL_ALPHA_TRANSPARENT); + cell_set_bg_alpha(c, CELL_ALPHA_TRANSPARENT); //fprintf(stderr, "transtring: %u egc: %s\n", transtring, egc); if(*egc == ' '){ // entirely transparent - channels_set_fg_alpha(channels, CELL_ALPHA_TRANSPARENT); + cell_set_fg_alpha(c, CELL_ALPHA_TRANSPARENT); return ""; }else{ // partially transparent, thus div >= 1 //fprintf(stderr, "div: %u r: %u g: %u b: %u\n", div, r, g, b); - channels_set_fchannel(channels, generalerp(r, g, b, div)); + cell_set_fchannel(c, generalerp(r, g, b, div)); if(blendcolors){ - channels_set_fg_alpha(channels, CELL_ALPHA_BLEND); + cell_set_fg_alpha(c, CELL_ALPHA_BLEND); } + cell_set_blitquadrants(c, !(transstring & 5u), !(transstring & 10u), + !(transstring & 20u), !(transstring & 40u)); } return egc; } @@ -632,11 +652,9 @@ sextant_blit(ncplane* nc, int placey, int placex, int linesize, nccell* c = ncplane_cell_ref_yx(nc, y, x); c->channels = 0; c->stylemask = 0; - const char* egc = sex_trans_check(rgbas, &c->channels, blendcolors); + const char* egc = sex_trans_check(c, rgbas, blendcolors); if(egc == NULL){ egc = sex_solver(rgbas, &c->channels, blendcolors); - }else{ - cell_set_blitted(c); } //fprintf(stderr, "sex EGC: %s channels: %016lx\n", egc, c->channels); if(*egc){ diff --git a/src/lib/internal.h b/src/lib/internal.h index 4ff8b3921e..b859fb4ec8 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -33,11 +33,11 @@ struct ncvisual_details; // Does this glyph completely obscure the background? If so, there's no need // to emit a background when rasterizing, a small optimization. -#define CELL_NOBACKGROUND_MASK 0x0400000000000000ull +#define CELL_NOBACKGROUND_MASK 0x8700000000000000ull // Was this glyph drawn as part of an ncvisual? If so, we need to honor // blitter stacking rather than the standard trichannel solver. -#define CELL_BLITTERSTACK_MASK 0x0000000004000000ull +#define CELL_BLITTERSTACK_MASK 0x8700000000000000ull // we can't define multipart ncvisual here, because OIIO requires C++ syntax, // and we can't go throwing C++ syntax into this header. so it goes. @@ -849,22 +849,27 @@ box_corner_needs(unsigned ctlword){ // solid or shaded block, or certain emoji). static inline bool cell_nobackground_p(const nccell* c){ - return c->channels & CELL_NOBACKGROUND_MASK; + return (c->channels & CELL_NOBACKGROUND_MASK) == CELL_NOBACKGROUND_MASK; } -// True if the cell was blitted as part of an ncvisual, and has a transparent -// background. +// True iff the cell was blitted as part of an ncvisual, and has a transparent +// background (being the only case where CELL_BLITTERSTACK_MASK bits are set). static inline bool cell_blitted_p(const nccell* c){ - return c->channels & CELL_BLITTERSTACK_MASK; + return c->channels & CELL_BLITTERSTACK_MASK; // any of the four bits is fine } // Set this whenever blitting an ncvisual, when we have a transparent // background. In such cases, ncvisuals underneath the cell must be rendered // slightly differently. static inline void -cell_set_blitted(nccell* c){ - c->channels |= CELL_BLITTERSTACK_MASK; +cell_set_blitquadrants(nccell* c, unsigned tl, unsigned tr, unsigned bl, unsigned br){ + // FIXME want a static assert that these four constants OR together to + // equal CELL_BLITTERSTACK_MASK, bah + c->channels |= tl ? 0x8000000000000000ull : 0; + c->channels |= tr ? 0x0400000000000000ull : 0; + c->channels |= bl ? 0x0200000000000000ull : 0; + c->channels |= br ? 0x0100000000000000ull : 0; } // Destroy a plane and all its bound descendants. diff --git a/tests/channel.cpp b/tests/channel.cpp index 074f9d1374..bc1a5dbb61 100644 --- a/tests/channel.cpp +++ b/tests/channel.cpp @@ -1,5 +1,8 @@ #include "main.h" +// These tests primarily check against expected constant values, which is +// useful in terms of maintaining ABI, but annoying when they do change. + TEST_CASE("ChannelGetRGB") { const struct t { uint32_t channel;