diff --git a/doc/man/man3/notcurses_cell.3.ronn b/doc/man/man3/notcurses_cell.3.ronn index 597bb1e02..572a23501 100644 --- a/doc/man/man3/notcurses_cell.3.ronn +++ b/doc/man/man3/notcurses_cell.3.ronn @@ -11,14 +11,11 @@ notcurses_cell(3) -- operations on notcurses cells uint64_t channels; } cell; -`#define CELL_TRIVIAL_INITIALIZER \ - { .gcluster = '\0', .attrword = 0, .channels = 0, }` +`#define CELL_TRIVIAL_INITIALIZER { .gcluster = '\0', .attrword = 0, .channels = 0, }` -`#define CELL_SIMPLE_INITIALIZER(c) \ - { .gcluster = (c), .attrword = 0, .channels = 0, }` +`#define CELL_SIMPLE_INITIALIZER(c) { .gcluster = (c), .attrword = 0, .channels = 0, }` -`#define CELL_INITIALIZER(c, a, chan) \ - { .gcluster = (c), .attrword = (a), .channels = (chan), }` +`#define CELL_INITIALIZER(c, a, chan) { .gcluster = (c), .attrword = (a), .channels = (chan), }` `void cell_init(cell* c);` diff --git a/include/notcurses.h b/include/notcurses.h index 5b3a95180..78e169ec1 100644 --- a/include/notcurses.h +++ b/include/notcurses.h @@ -1002,13 +1002,19 @@ channels_set_bg_default(uint64_t* channels){ // Returns the result of blending two channels. static inline unsigned -channels_blend(unsigned c1, unsigned c2){ +channels_blend(unsigned c1, unsigned c2, unsigned blends){ if(channel_default_p(c1)){ return c1; } - int rsum = (channel_get_r(c1) + channel_get_r(c2)) / 2; - int gsum = (channel_get_g(c1) + channel_get_g(c2)) / 2; - int bsum = (channel_get_b(c1) + channel_get_b(c2)) / 2; + if(channel_default_p(c2)){ + if(blends == 0){ + channel_set_default(&c1); + } + return c1; + } + int rsum = (channel_get_r(c1) * blends + channel_get_r(c2)) / (blends + 1); + int gsum = (channel_get_g(c1) * blends + channel_get_g(c2)) / (blends + 1); + int bsum = (channel_get_b(c1) * blends + channel_get_b(c2)) / (blends + 1); unsigned blend = 0; channel_set_rgb(&blend, rsum, gsum, bsum); return blend; @@ -1039,13 +1045,13 @@ cell_set_fchannel(cell* cl, uint32_t channel){ } static inline uint64_t -cell_blend_fchannel(cell* cl, unsigned channel){ - return cell_set_fchannel(cl, channels_blend(cell_get_fchannel(cl), channel)); +cell_blend_fchannel(cell* cl, unsigned channel, unsigned blends){ + return cell_set_fchannel(cl, channels_blend(cell_get_fchannel(cl), channel, blends)); } static inline uint64_t -cell_blend_bchannel(cell* cl, unsigned channel){ - return cell_set_bchannel(cl, channels_blend(cell_get_bchannel(cl), channel)); +cell_blend_bchannel(cell* cl, unsigned channel, unsigned blends){ + return cell_set_bchannel(cl, channels_blend(cell_get_bchannel(cl), channel, blends)); } // Extract 24 bits of foreground RGB from 'cell', shifted to LSBs. diff --git a/src/lib/render.c b/src/lib/render.c index 3e5b67bee..a2bd71e13 100644 --- a/src/lib/render.c +++ b/src/lib/render.c @@ -147,6 +147,8 @@ reshape_shadow_fb(notcurses* nc){ // as we descend. α == 0 is opaque. α == 2 is fully transparent. static inline ncplane* dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha){ + unsigned fgblends = 1; + unsigned bgblends = 1; // once we decide on our glyph, it cannot be changed by anything below, so // lock in this plane for the actual cell return. ncplane* glyphplane = NULL; @@ -178,7 +180,8 @@ dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha){ } if(falpha > CELL_ALPHA_OPAQUE && cell_get_fg_alpha(vis) < CELL_ALPHA_TRANSPARENT){ if(falpha == CELL_ALPHA_BLEND){ - cell_blend_fchannel(c, cell_get_fchannel(vis)); + cell_blend_fchannel(c, cell_get_fchannel(vis), fgblends); + ++fgblends; }else{ cell_set_fchannel(c, cell_get_fchannel(vis)); } @@ -190,7 +193,8 @@ dig_visible_cell(cell* c, int y, int x, ncplane* p, int falpha, int balpha){ // background channel and balpha. if(balpha > CELL_ALPHA_OPAQUE && cell_get_bg_alpha(vis) < CELL_ALPHA_TRANSPARENT){ if(balpha == CELL_ALPHA_BLEND){ - cell_blend_bchannel(c, cell_get_bchannel(vis)); + cell_blend_bchannel(c, cell_get_bchannel(vis), bgblends); + ++bgblends; }else{ // balpha == CELL_ALPHA_TRANSPARENT cell_set_bchannel(c, cell_get_bchannel(vis)); } diff --git a/tests/channel.cpp b/tests/channel.cpp index 76feceff8..c18290dcb 100644 --- a/tests/channel.cpp +++ b/tests/channel.cpp @@ -66,3 +66,72 @@ TEST_CASE("ChannelSetDefault") { CHECK(channel_default_p(channel)); } } + +// blend of 0 ought perfectly set +TEST_CASE("ChannelBlend0") { + uint32_t c1 = 0; + uint32_t c2 = 0; + channel_set_rgb(&c1, 0x80, 0x40, 0x20); + channel_set_rgb(&c2, 0x88, 0x44, 0x22); + uint32_t c = channels_blend(c1, c2, 0); + unsigned r, g, b; + channel_get_rgb(c, &r, &g, &b); + CHECK(0x88 == r); + CHECK(0x44 == g); + CHECK(0x22 == b); +} + +// blend of 1 ought perfectly average +TEST_CASE("ChannelBlend1") { + uint32_t c1 = 0; + uint32_t c2 = 0; + channel_set_rgb(&c1, 0x80, 0x40, 0x20); + channel_set_rgb(&c2, 0x0, 0x0, 0x0); + uint32_t c = channels_blend(c1, c2, 1); + unsigned r, g, b; + channel_get_rgb(c, &r, &g, &b); + CHECK(0x40 == r); + CHECK(0x20 == g); + CHECK(0x10 == b); +} + +// you can't blend into a default color, at any number of blends +TEST_CASE("ChannelBlendDefaultLeft") { + uint32_t c1 = 0; + uint32_t c2 = 0; + channel_set_rgb(&c2, 0x80, 0x40, 0x20); + uint32_t c = channels_blend(c1, c2, 0); + CHECK(channel_default_p(c)); + unsigned r, g, b; + channel_get_rgb(c, &r, &g, &b); + CHECK(0 == r); + CHECK(0 == g); + CHECK(0 == b); + c = channels_blend(c1, c2, 1); + CHECK(channel_default_p(c)); + channel_get_rgb(c, &r, &g, &b); + CHECK(0 == r); + CHECK(0 == g); + CHECK(0 == b); +} + +// you can't blend from a default color, but blend 0 sets it +TEST_CASE("ChannelBlendDefaultRight") { + uint32_t c1 = 0; + uint32_t c2 = 0; + channel_set_rgb(&c1, 0x80, 0x40, 0x20); + CHECK(channel_default_p(c2)); + uint32_t c = channels_blend(c1, c2, 0); + CHECK(channel_default_p(c)); + unsigned r, g, b; + channel_get_rgb(c, &r, &g, &b); + CHECK(0x80 == r); + CHECK(0x40 == g); + CHECK(0x20 == b); + c = channels_blend(c1, c2, 1); + CHECK(!channel_default_p(c)); + channel_get_rgb(c, &r, &g, &b); + CHECK(0x80 == r); + CHECK(0x40 == g); + CHECK(0x20 == b); +}