Fix gradient engine #368 (#372)

Simplify and correct the gradient engine, resolving all test breakage.
pull/384/head
Nick Black 4 years ago committed by GitHub
parent 3f6b9c3c62
commit 745e742a12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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…
Cancel
Save