mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 17:19:03 -04:00
Simplify and correct the gradient engine, resolving all test breakage.
This commit is contained in:
parent
3f6b9c3c62
commit
745e742a12
@ -2657,6 +2657,11 @@ 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).
|
||||
|
||||
Certain EGCs are understood to be all-foreground or all-background. U+2588
|
||||
FULL BLOCK is all foreground. U+0020 SPACE is all background. When such a
|
||||
character is used, notcurses will emit whichever character can take advantage
|
||||
of the current color.
|
||||
|
||||
## Included tools
|
||||
|
||||
Five binaries are built as part of notcurses:
|
||||
|
@ -875,7 +875,16 @@ API int ncplane_polyfill_yx(struct ncplane* n, int y, int x, const cell* c);
|
||||
// are composed into foreground and background gradients. To do a vertical
|
||||
// gradient, 'ul' ought equal 'ur' and 'll' ought equal 'lr'. To do a
|
||||
// horizontal gradient, 'ul' ought equal 'll' and 'ur' ought equal 'ul'. To
|
||||
// color everything the same, all four channels should be equivalent.
|
||||
// color everything the same, all four channels should be equivalent. The
|
||||
// resulting alpha values are equal to incoming alpha values.
|
||||
//
|
||||
// Preconditions for gradient operations (error otherwise):
|
||||
//
|
||||
// all: only RGB colors (no defaults, no palette-indexed)
|
||||
// all: all alpha values must be the same
|
||||
// 1x1: all four colors must be the same
|
||||
// 1xN: both top and both bottom colors must be the same (vertical gradient)
|
||||
// Nx1: both left and both right colors must be the same (horizontal gradient)
|
||||
API int ncplane_gradient(struct ncplane* n, const char* egc, uint32_t attrword,
|
||||
uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr,
|
||||
int ystop, int xstop);
|
||||
@ -886,6 +895,9 @@ static inline int
|
||||
ncplane_gradient_sized(struct ncplane* n, const char* egc, uint32_t attrword,
|
||||
uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr,
|
||||
int ylen, int xlen){
|
||||
if(ylen < 1 || xlen < 1){
|
||||
return -1;
|
||||
}
|
||||
int y, x;
|
||||
ncplane_cursor_yx(n, &y, &x);
|
||||
return ncplane_gradient(n, egc, attrword, ul, ur, ll, lr, y + ylen - 1, x + xlen - 1);
|
||||
|
@ -24,10 +24,10 @@ int intro(struct notcurses* nc){
|
||||
struct ncplane* ncp = notcurses_stddim_yx(nc, &rows, &cols);
|
||||
uint64_t cul, cur, cll, clr;
|
||||
cul = cur = cll = clr = 0;
|
||||
channels_set_fg_rgb(&cul, 0, 0xd0, 0);
|
||||
channels_set_fg_rgb(&cur, 0xff, 0, 0);
|
||||
channels_set_fg_rgb(&cll, 0x88, 0, 0xcc);
|
||||
channels_set_fg_rgb(&clr, 0, 0, 0);
|
||||
channels_set_fg_rgb(&cul, 0, 0xd0, 0); channels_set_bg(&cul, 0);
|
||||
channels_set_fg_rgb(&cur, 0xff, 0, 0); channels_set_bg(&cur, 0);
|
||||
channels_set_fg_rgb(&cll, 0x88, 0, 0xcc); channels_set_bg(&cll, 0);
|
||||
channels_set_fg_rgb(&clr, 0, 0, 0); channels_set_bg(&clr, 0);
|
||||
// we use full block rather+fg than space+bg to conflict less with the menu
|
||||
if(ncplane_cursor_move_yx(ncp, 0, 0)){
|
||||
return -1;
|
||||
|
@ -105,10 +105,10 @@ fill_chunk(struct ncplane* n, int idx){
|
||||
channels_set_fg_rgb(&channels, r, g, b);
|
||||
uint64_t ul, ur, ll, lr;
|
||||
ul = ur = ll = lr = 0;
|
||||
channels_set_fg_rgb(&ul, r, g, b);
|
||||
channels_set_fg_rgb(&ur, g, b, r);
|
||||
channels_set_fg_rgb(&ll, b, r, g);
|
||||
channels_set_fg_rgb(&lr, r, g, b);
|
||||
channels_set_fg_rgb(&ul, r, g, b); channels_set_bg(&ul, 0);
|
||||
channels_set_fg_rgb(&lr, r, g, b); channels_set_bg(&lr, 0);
|
||||
channels_set_fg_rgb(&ur, g, b, r); channels_set_bg(&ur, 0);
|
||||
channels_set_fg_rgb(&ll, b, r, g); channels_set_bg(&ll, 0);
|
||||
if(ncplane_gradient_sized(n, "█", 0, ul, ur, ll, lr, maxy, maxx)){
|
||||
return -1;
|
||||
}
|
||||
|
@ -1932,45 +1932,55 @@ int ncplane_polyfill_yx(ncplane* n, int y, int x, const cell* c){
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Our gradient is a 2d lerp among the four corners of the region. We start
|
||||
// with the observation that each corner ought be its exact specified corner,
|
||||
// and the middle ought be the exact average of all four corners' components.
|
||||
// Another observation is that if all four corners are the same, every cell
|
||||
// ought be the exact same color. From this arises the observation that a
|
||||
// perimeter element is not affected by the other three sides:
|
||||
//
|
||||
// a corner element is defined by itself
|
||||
// a perimeter element is defined by the two points on its side
|
||||
// an internal element is defined by all four points
|
||||
//
|
||||
// 2D equation of state: solve for each quadrant's contribution (min 2x2):
|
||||
//
|
||||
// X' = (xlen - 1) - X
|
||||
// Y' = (ylen - 1) - Y
|
||||
// TLC: X' * Y' * TL
|
||||
// TRC: X * Y' * TR
|
||||
// BLC: X' * Y * BL
|
||||
// BRC: X * Y * BR
|
||||
// steps: (xlen - 1) * (ylen - 1) [maximum steps away from origin]
|
||||
//
|
||||
// Then add TLC + TRC + BLC + BRC + steps / 2, and divide by steps (the
|
||||
// steps / 2 is to work around truncate-towards-zero).
|
||||
static int
|
||||
calc_gradient_component(unsigned ul, unsigned ur, unsigned ll, unsigned lr,
|
||||
calc_gradient_component(unsigned tl, unsigned tr, unsigned bl, unsigned br,
|
||||
int y, int x, int ylen, int xlen){
|
||||
/*fprintf(stderr, "%08x %08x %08x %08x %d %d %d %d -> %08x\n",
|
||||
ul, ur, ll, lr, y, x, ylen, xlen,
|
||||
(((xlen - x) * ul / xlen + x * ur / xlen) * (ylen - y)) / ylen +
|
||||
(((xlen - x) * ll / xlen + x * lr / xlen) * (y)) / ylen);*/
|
||||
/*unsigned lchuk = xlen * !!((xlen - x) % xlen);
|
||||
unsigned rchuk = xlen * !!(x % (xlen));*/
|
||||
|
||||
const unsigned xchuk = xlen / 2;
|
||||
|
||||
unsigned upchuk = ylen * !!(((((xlen - x) * ul + xchuk) / xlen + (x * ur + xchuk) / xlen) * (ylen - y)) % ylen);
|
||||
unsigned downchuk = ylen * !!(((((xlen - x) * ll + xchuk) / xlen + (x * lr + xchuk) / xlen) * y) % ylen);
|
||||
|
||||
upchuk = downchuk = ylen / 2;
|
||||
//upchuk = 0;
|
||||
//downchuk = 0;
|
||||
|
||||
//fprintf(stderr, "uc: %u dc: %u lc: %u rc: %u\n", upchuk, downchuk, xchuk, xchuk);
|
||||
|
||||
int up = ((((xlen - x) * ul + xchuk) / xlen + (x * ur + xchuk) / xlen) * (ylen - y) + upchuk) / ylen;
|
||||
int down = ((((xlen - x) * ll + xchuk) / xlen + (x * lr + xchuk) / xlen) * y + downchuk) / ylen;
|
||||
/*
|
||||
fprintf(stderr, "UL: (xlen - x) * ul / xlen (%d - %d) * %u / %d (%g)\n", xlen, x, ul, xlen, (((float)xlen - x) * ul + xchuk) / xlen);
|
||||
fprintf(stderr, "UR: x * ur / xlen %d * %u / %d (%g)\n", x, ur, xlen, ((float)x * ur + xchuk) / xlen);
|
||||
fprintf(stderr, "ul: %u ur: %u u: %u\n", (xlen - x) * ul / xlen,
|
||||
x * ur / xlen, up);
|
||||
fprintf(stderr, "%u %u %u %u top: %u bottom: %u -> %u\n",
|
||||
ul, ur, ll, lr, up, down, up + down);*/
|
||||
return up + down;
|
||||
assert(xlen >= 2);
|
||||
assert(ylen >= 2);
|
||||
assert(y >= 0);
|
||||
assert(y < ylen);
|
||||
assert(x >= 0);
|
||||
assert(x < xlen);
|
||||
const int avm = (ylen - 1) - y;
|
||||
const int ahm = (xlen - 1) - x;
|
||||
const int tlc = ahm * avm * tl;
|
||||
const int blc = ahm * y * bl;
|
||||
const int trc = x * avm * tr;
|
||||
const int brc = y * x * br;
|
||||
const int divisor = (ylen - 1) * (xlen - 1);
|
||||
return ((tlc + blc + trc + brc) + divisor / 2) / divisor;
|
||||
}
|
||||
|
||||
// calculate one of the channels of a gradient at a particular point.
|
||||
static uint32_t
|
||||
static inline uint32_t
|
||||
calc_gradient_channel(uint32_t ul, uint32_t ur, uint32_t ll, uint32_t lr,
|
||||
int y, int x, int ylen, int xlen){
|
||||
uint32_t chan = 0;
|
||||
channel_set_rgb(&chan, calc_gradient_component(channel_r(ul), channel_r(ur),
|
||||
channel_set_rgb_clipped(&chan,
|
||||
calc_gradient_component(channel_r(ul), channel_r(ur),
|
||||
channel_r(ll), channel_r(lr),
|
||||
y, x, ylen, xlen),
|
||||
calc_gradient_component(channel_g(ul), channel_g(ur),
|
||||
@ -1984,7 +1994,7 @@ calc_gradient_channel(uint32_t ul, uint32_t ur, uint32_t ll, uint32_t lr,
|
||||
|
||||
// calculate both channels of a gradient at a particular point, storing them
|
||||
// into `c`->channels. x and y ought be the location within the gradient.
|
||||
static void
|
||||
static inline void
|
||||
calc_gradient_channels(cell* c, uint64_t ul, uint64_t ur, uint64_t ll,
|
||||
uint64_t lr, int y, int x, int ylen, int xlen){
|
||||
cell_set_fchannel(c, calc_gradient_channel(channels_fchannel(ul),
|
||||
@ -2002,6 +2012,13 @@ calc_gradient_channels(cell* c, uint64_t ul, uint64_t ur, uint64_t ll,
|
||||
int ncplane_gradient(ncplane* n, const char* egc, uint32_t attrword,
|
||||
uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr,
|
||||
int ystop, int xstop){
|
||||
// Can't use default or palette-indexed colors in a gradient
|
||||
if(channel_default_p(ul) || channel_default_p(ll) ||
|
||||
channel_default_p(lr) || channel_default_p(ur) ||
|
||||
channel_palindex_p(ul) || channel_palindex_p(ll) ||
|
||||
channel_palindex_p(lr) || channel_palindex_p(ur)){
|
||||
return -1;
|
||||
}
|
||||
int yoff, xoff, ymax, xmax;
|
||||
ncplane_cursor_yx(n, &yoff, &xoff);
|
||||
// must be at least 1x1, with its upper-left corner at the current cursor
|
||||
@ -2018,8 +2035,19 @@ int ncplane_gradient(ncplane* n, const char* egc, uint32_t attrword,
|
||||
}
|
||||
const int xlen = xstop - xoff + 1;
|
||||
const int ylen = ystop - yoff + 1;
|
||||
for(int y = yoff ; y < ystop + 1 ; ++y){
|
||||
for(int x = xoff ; x < xstop + 1 ; ++x){
|
||||
if(ylen == 1){
|
||||
if(xlen == 1){
|
||||
// single cell FIXME
|
||||
}else{
|
||||
// horizontal 1d FIXME
|
||||
}
|
||||
return 0;
|
||||
}else if(xlen == 1){
|
||||
// vertical 1d FIXME
|
||||
return 0;
|
||||
}
|
||||
for(int y = yoff ; y <= ystop ; ++y){
|
||||
for(int x = xoff ; x <= xstop ; ++x){
|
||||
cell* targc = ncplane_cell_ref_yx(n, y, x);
|
||||
targc->channels = 0;
|
||||
if(cell_load(n, targc, egc) < 0){
|
||||
@ -2033,7 +2061,14 @@ int ncplane_gradient(ncplane* n, const char* egc, uint32_t attrword,
|
||||
}
|
||||
|
||||
int ncplane_stain(struct ncplane* n, int ystop, int xstop,
|
||||
uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr){
|
||||
uint64_t tl, uint64_t tr, uint64_t bl, uint64_t br){
|
||||
// Can't use default or palette-indexed colors in a gradient
|
||||
if(channel_default_p(tl) || channel_default_p(bl) ||
|
||||
channel_default_p(br) || channel_default_p(tr) ||
|
||||
channel_palindex_p(tl) || channel_palindex_p(bl) ||
|
||||
channel_palindex_p(br) || channel_palindex_p(tr)){
|
||||
return -1;
|
||||
}
|
||||
int yoff, xoff, ymax, xmax;
|
||||
ncplane_cursor_yx(n, &yoff, &xoff);
|
||||
// must be at least 1x1, with its upper-left corner at the current cursor
|
||||
@ -2050,10 +2085,10 @@ int ncplane_stain(struct ncplane* n, int ystop, int xstop,
|
||||
}
|
||||
const int xlen = xstop - xoff + 1;
|
||||
const int ylen = ystop - yoff + 1;
|
||||
for(int y = yoff ; y < ystop + 1 ; ++y){
|
||||
for(int x = xoff ; x < xstop + 1 ; ++x){
|
||||
for(int y = yoff ; y <= ystop ; ++y){
|
||||
for(int x = xoff ; x <= xstop ; ++x){
|
||||
cell* targc = ncplane_cell_ref_yx(n, y, x);
|
||||
calc_gradient_channels(targc, ul, ur, ll, lr, y - yoff, x - xoff, ylen, xlen);
|
||||
calc_gradient_channels(targc, tl, tr, bl, br, y - yoff, x - xoff, ylen, xlen);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
@ -419,6 +419,9 @@ notcurses_render_internal(notcurses* nc, struct crender* rvec){
|
||||
struct crender* crender = &rvec[fbcellidx(y, dimx, x)];
|
||||
lock_in_highcontrast(targc, crender);
|
||||
cell* prevcell = &nc->lastframe[fbcellidx(y, dimx, x)];
|
||||
if(targc->gcluster == 0){
|
||||
targc->gcluster = ' ';
|
||||
}
|
||||
if(cellcmp_and_dupfar(&nc->pool, prevcell, crender->p, targc)){
|
||||
crender->damaged = true;
|
||||
}
|
||||
|
@ -65,30 +65,23 @@ TEST_CASE("Fills") {
|
||||
}
|
||||
|
||||
SUBCASE("GradientMonochromatic") {
|
||||
uint64_t ul, ur, ll, lr;
|
||||
ul = ur = ll = lr = 0;
|
||||
channels_set_fg(&ul, 0x40f040);
|
||||
channels_set_bg(&ul, 0x40f040);
|
||||
channels_set_fg(&ur, 0x40f040);
|
||||
channels_set_bg(&ur, 0x40f040);
|
||||
channels_set_fg(&ll, 0x40f040);
|
||||
channels_set_bg(&ll, 0x40f040);
|
||||
channels_set_fg(&lr, 0x40f040);
|
||||
channels_set_bg(&lr, 0x40f040);
|
||||
uint64_t c = 0;
|
||||
channels_set_fg(&c, 0x40f040);
|
||||
channels_set_bg(&c, 0x40f040);
|
||||
int dimy, dimx;
|
||||
ncplane_dim_yx(n_, &dimy, &dimx);
|
||||
REQUIRE(0 == ncplane_gradient_sized(n_, "M", 0, ul, ur, ll, lr, dimy, dimx));
|
||||
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||
REQUIRE(0 == ncplane_gradient_sized(n_, "M", 0, c, c, c, c, dimy, dimx));
|
||||
cell cl = CELL_TRIVIAL_INITIALIZER;
|
||||
uint64_t channels = 0;
|
||||
channels_set_fg(&channels, 0x40f040);
|
||||
channels_set_bg(&channels, 0x40f040);
|
||||
// check all squares
|
||||
for(int y = 0 ; y < dimy ; ++y){
|
||||
for(int x = 0 ; x < dimx ; ++x){
|
||||
REQUIRE(0 <= ncplane_at_yx(n_, y, x, &c));
|
||||
CHECK('M' == c.gcluster);
|
||||
CHECK(0 == c.attrword);
|
||||
CHECK(channels == c.channels);
|
||||
REQUIRE(0 <= ncplane_at_yx(n_, y, x, &cl));
|
||||
CHECK('M' == cl.gcluster);
|
||||
CHECK(0 == cl.attrword);
|
||||
CHECK(channels == cl.channels);
|
||||
}
|
||||
}
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
@ -127,8 +120,7 @@ TEST_CASE("Fills") {
|
||||
lastyrgb = c.channels;
|
||||
CHECK(ul == c.channels);
|
||||
}else if(y == dimy - 1){
|
||||
/*fprintf(stderr, "HJAVE %016lx, WANT %016lx %d %d\n", c.channels, ll, y, x);
|
||||
CHECK(ll == c.channels);*/
|
||||
CHECK(ll == c.channels);
|
||||
}
|
||||
lastxrgb = c.channels;
|
||||
}else{
|
||||
@ -138,8 +130,7 @@ TEST_CASE("Fills") {
|
||||
if(y == 0){
|
||||
CHECK(ur == c.channels);
|
||||
}else if(y == dimy - 1){
|
||||
/*fprintf(stderr, "LR %016lx, WANT %016lx %d %d\n", c.channels, lr, y, x);
|
||||
CHECK(lr == c.channels);*/
|
||||
CHECK(lr == c.channels);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -201,6 +192,51 @@ TEST_CASE("Fills") {
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
}
|
||||
|
||||
SUBCASE("Ncplane_Format") {
|
||||
int sbytes;
|
||||
CHECK(0 == ncplane_set_fg(n_, 0x444444));
|
||||
CHECK(1 == ncplane_putegc(n_, "A", &sbytes));
|
||||
CHECK(0 == ncplane_set_fg(n_, 0x888888));
|
||||
CHECK(1 == ncplane_putegc(n_, "B", &sbytes));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
// attr should change, but not the EGC/color
|
||||
CHECK(0 == ncplane_cursor_move_yx(n_, 0, 0));
|
||||
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||
cell_styles_on(&c, NCSTYLE_BOLD);
|
||||
CHECK(0 == ncplane_format(n_, 0, 0, c.attrword));
|
||||
cell d = CELL_TRIVIAL_INITIALIZER;
|
||||
CHECK(1 == ncplane_at_yx(n_, 0, 0, &d));
|
||||
CHECK(d.attrword == c.attrword);
|
||||
CHECK(0x444444 == cell_fg(&d));
|
||||
}
|
||||
|
||||
SUBCASE("Ncplane_Stain") {
|
||||
int sbytes;
|
||||
CHECK(0 == ncplane_set_fg(n_, 0x444444));
|
||||
for(int y = 0 ; y < 8 ; ++y){
|
||||
for(int x = 0 ; x < 8 ; ++x){
|
||||
CHECK(1 == ncplane_putegc_yx(n_, y, x, "A", &sbytes));
|
||||
}
|
||||
}
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
// attr should change, but not the EGC/color
|
||||
CHECK(0 == ncplane_cursor_move_yx(n_, 0, 0));
|
||||
uint64_t channels = 0;
|
||||
channels_set_fg_rgb(&channels, 0x88, 0x99, 0x77);
|
||||
channels_set_bg(&channels, 0);
|
||||
REQUIRE(0 == ncplane_stain(n_, 7, 7, channels, channels, channels, channels));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
cell d = CELL_TRIVIAL_INITIALIZER;
|
||||
for(int y = 0 ; y < 8 ; ++y){
|
||||
for(int x = 0 ; x < 8 ; ++x){
|
||||
CHECK(1 == ncplane_at_yx(n_, y, x, &d));
|
||||
CHECK(channels == d.channels);
|
||||
REQUIRE(cell_simple_p(&d));
|
||||
CHECK('A' == d.gcluster);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(0 == notcurses_stop(nc_));
|
||||
CHECK(0 == fclose(outfp_));
|
||||
|
||||
|
@ -968,24 +968,6 @@ TEST_CASE("NCPlane") {
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
}
|
||||
|
||||
SUBCASE("Ncplane_Format") {
|
||||
int sbytes;
|
||||
CHECK(0 == ncplane_set_fg(n_, 0x444444));
|
||||
CHECK(1 == ncplane_putegc(n_, "A", &sbytes));
|
||||
CHECK(0 == ncplane_set_fg(n_, 0x888888));
|
||||
CHECK(1 == ncplane_putegc(n_, "B", &sbytes));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
// attr should change, but not the EGC/color
|
||||
CHECK(0 == ncplane_cursor_move_yx(n_, 0, 0));
|
||||
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||
cell_styles_on(&c, NCSTYLE_BOLD);
|
||||
CHECK(0 == ncplane_format(n_, 0, 0, c.attrword));
|
||||
cell d = CELL_TRIVIAL_INITIALIZER;
|
||||
CHECK(1 == ncplane_at_yx(n_, 0, 0, &d));
|
||||
CHECK(d.attrword == c.attrword);
|
||||
CHECK(0x444444 == cell_fg(&d));
|
||||
}
|
||||
|
||||
SUBCASE("EGCStainable") {
|
||||
cell c = CELL_TRIVIAL_INITIALIZER;
|
||||
int sbytes;
|
||||
|
Loading…
x
Reference in New Issue
Block a user