Sixel unit tests #1580 (#1586)

Two unit tests on Sixel round trips, one with wipes #1580.
This commit is contained in:
Nick Black 2021-04-24 01:51:26 -04:00 committed by GitHub
parent 4cceb01acf
commit ba6088578d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 244 additions and 49 deletions

View File

@ -26,9 +26,8 @@ fn main() -> NcResult<()> {
// vframe.inflate(1)?; // this works // vframe.inflate(1)?; // this works
vframe.resize(1, 1)?; // but it doesn't matter which values we put here vframe.resize(6, 1)?;
// FIXME: render always gives an error
vframe.render(&mut nc, &voptions)?; vframe.render(&mut nc, &voptions)?;
rsleep![&mut nc, 2]; rsleep![&mut nc, 2];

View File

@ -946,10 +946,11 @@ sprixel* sprixel_by_id(const ncpile* n, uint32_t id);
void sprixel_invalidate(sprixel* s, int y, int x); void sprixel_invalidate(sprixel* s, int y, int x);
void sprixel_movefrom(sprixel* s, int y, int x); void sprixel_movefrom(sprixel* s, int y, int x);
// is the sprixel backend kitty? int sixel_blit(ncplane* nc, int linesize, const void* data,
static inline bool sprixel_kitty_p(const tinfo* t){ int leny, int lenx, const blitterargs* bargs);
return t->pixel_shutdown == kitty_shutdown;
} int kitty_blit(ncplane* nc, int linesize, const void* data,
int leny, int lenx, const blitterargs* bargs);
static inline int static inline int
sprite_destroy(const notcurses* nc, const ncpile* p, FILE* out, sprixel* s){ sprite_destroy(const notcurses* nc, const ncpile* p, FILE* out, sprixel* s){
@ -1008,6 +1009,12 @@ sprixel_state(const sprixel* s, int y, int x){
return s->n->tacache[localy * s->dimx + localx]; return s->n->tacache[localy * s->dimx + localx];
} }
// is sprixel backend kitty (only valid after calling setup_kitty_bitmaps())?
// FIXME kill this off, and use different states instead
static inline bool sprixel_kitty_p(const tinfo* t){
return t->pixel_shutdown == kitty_shutdown;
}
static inline void static inline void
pool_release(egcpool* pool, nccell* c){ pool_release(egcpool* pool, nccell* c){
if(cell_extended_p(c)){ if(cell_extended_p(c)){
@ -1588,12 +1595,6 @@ ncdirect_bg_default_p(const struct ncdirect* nc){
return channels_bg_default_p(ncdirect_channels(nc)); return channels_bg_default_p(ncdirect_channels(nc));
} }
int sixel_blit(ncplane* nc, int linesize, const void* data,
int leny, int lenx, const blitterargs* bargs);
int kitty_blit(ncplane* nc, int linesize, const void* data,
int leny, int lenx, const blitterargs* bargs);
int term_fg_rgb8(bool RGBflag, const char* setaf, int colors, FILE* out, int term_fg_rgb8(bool RGBflag, const char* setaf, int colors, FILE* out,
unsigned r, unsigned g, unsigned b); unsigned r, unsigned g, unsigned b);

View File

@ -1029,14 +1029,15 @@ rasterize_core(notcurses* nc, const ncpile* p, FILE* out, unsigned phase){
//fprintf(stderr, "RAST %08x [%s] to %d/%d cols: %u %016lx\n", srccell->gcluster, pool_extended_gcluster(&nc->pool, srccell), y, x, srccell->width, srccell->channels); //fprintf(stderr, "RAST %08x [%s] to %d/%d cols: %u %016lx\n", srccell->gcluster, pool_extended_gcluster(&nc->pool, srccell), y, x, srccell->width, srccell->channels);
// this is used to invalidate the sprixel in the first text round, // this is used to invalidate the sprixel in the first text round,
// which is only necessary for sixel, not kitty. // which is only necessary for sixel, not kitty.
if(rvec[damageidx].sprixel if(rvec[damageidx].sprixel){
&& sprixel_state(rvec[damageidx].sprixel, y - nc->stdplane->absy, x - nc->stdplane->absx) != SPRIXCELL_TRANSPARENT sprixcell_e scstate = sprixel_state(rvec[damageidx].sprixel, y - nc->stdplane->absy, x - nc->stdplane->absx);
&& sprixel_state(rvec[damageidx].sprixel, y - nc->stdplane->absy, x - nc->stdplane->absx) != SPRIXCELL_ANNIHILATED if(scstate != SPRIXCELL_TRANSPARENT
&& !rvec[damageidx].s.p_beats_sprixel && scstate != SPRIXCELL_ANNIHILATED && !rvec[damageidx].s.p_beats_sprixel
&& !sprixel_kitty_p(&nc->tcache)){ && !sprixel_kitty_p(&nc->tcache)){
//fprintf(stderr, "INVALIDATING at %d/%d (%u)\n", y, x, rvec[damageidx].s.p_beats_sprixel); //fprintf(stderr, "INVALIDATING at %d/%d (%u)\n", y, x, rvec[damageidx].s.p_beats_sprixel);
sprixel_invalidate(rvec[damageidx].sprixel, y, x); sprixel_invalidate(rvec[damageidx].sprixel, y, x);
}
} }
if(term_putc(out, &nc->pool, srccell)){ if(term_putc(out, &nc->pool, srccell)){
return -1; return -1;

View File

@ -2,6 +2,34 @@
#include <ncurses.h> // needed for some definitions, see terminfo(3ncurses) #include <ncurses.h> // needed for some definitions, see terminfo(3ncurses)
#include "internal.h" #include "internal.h"
// we found Sixel support -- set up the API
static inline void
setup_sixel_bitmaps(tinfo* ti){
ti->bitmap_supported = true;
ti->color_registers = 256; // assumed default [shrug]
ti->pixel_init = sixel_init;
ti->pixel_draw = sixel_draw;
ti->sixel_maxx = 4096; // whee!
ti->sixel_maxy = 4096;
ti->pixel_remove = NULL;
ti->pixel_destroy = sixel_delete;
ti->pixel_cell_wipe = sixel_wipe;
ti->pixel_shutdown = sixel_shutdown;
ti->sprixel_height_factor = 6;
}
static inline void
setup_kitty_bitmaps(tinfo* ti){
ti->pixel_cell_wipe = kitty_wipe;
ti->pixel_destroy = kitty_delete;
ti->pixel_init = kitty_init;
ti->pixel_remove = kitty_remove;
ti->pixel_draw = kitty_draw;
ti->pixel_shutdown = kitty_shutdown;
ti->sprixel_height_factor = 1;
set_pixel_blitter(kitty_blit);
}
static bool static bool
query_rgb(void){ query_rgb(void){
bool rgb = tigetflag("RGB") == 1; bool rgb = tigetflag("RGB") == 1;
@ -61,14 +89,7 @@ apply_term_heuristics(tinfo* ti, const char* termname){
ti->quadrants = true; ti->quadrants = true;
ti->pixel_query_done = true; ti->pixel_query_done = true;
ti->bitmap_supported = true; ti->bitmap_supported = true;
ti->pixel_cell_wipe = kitty_wipe; setup_kitty_bitmaps(ti);
ti->pixel_destroy = kitty_delete;
ti->pixel_init = kitty_init;
ti->pixel_remove = kitty_remove;
ti->pixel_draw = kitty_draw;
ti->pixel_shutdown = kitty_shutdown;
ti->sprixel_height_factor = 1;
set_pixel_blitter(kitty_blit);
}else if(strstr(termname, "alacritty")){ }else if(strstr(termname, "alacritty")){
ti->alacritty_sixel_hack = true; ti->alacritty_sixel_hack = true;
ti->quadrants = true; ti->quadrants = true;
@ -334,22 +355,6 @@ query_sixel_details(tinfo* ti, int fd){
return 0; return 0;
} }
// we found Sixel support -- set up the API
static void
setup_sixel(tinfo* ti){
ti->bitmap_supported = true;
ti->color_registers = 256; // assumed default [shrug]
ti->pixel_init = sixel_init;
ti->pixel_draw = sixel_draw;
ti->sixel_maxx = 4096; // whee!
ti->sixel_maxy = 4096;
ti->pixel_remove = NULL;
ti->pixel_destroy = sixel_delete;
ti->pixel_cell_wipe = sixel_wipe;
ti->pixel_shutdown = sixel_shutdown;
ti->sprixel_height_factor = 6;
}
// query for Sixel support // query for Sixel support
static int static int
query_sixel(tinfo* ti, int fd){ query_sixel(tinfo* ti, int fd){
@ -389,7 +394,7 @@ query_sixel(tinfo* ti, int fd){
case WANT_SEMI: case WANT_SEMI:
if(in == ';'){ if(in == ';'){
if(in4){ if(in4){
setup_sixel(ti); setup_sixel_bitmaps(ti);
} }
state = WANT_C4; state = WANT_C4;
}else if(in == 'c'){ }else if(in == 'c'){
@ -404,7 +409,7 @@ query_sixel(tinfo* ti, int fd){
// until graphics/ayosec is merged, alacritty doesn't actually // until graphics/ayosec is merged, alacritty doesn't actually
// have sixel support. enable this then. FIXME // have sixel support. enable this then. FIXME
/*if(ti->alacritty_sixel_hack){ /*if(ti->alacritty_sixel_hack){
setup_sixel(ti); setup_sixel_bitmaps(ti);
}*/ }*/
state = DONE; state = DONE;
}else if(in == ';'){ }else if(in == ';'){

View File

@ -22,6 +22,30 @@ TEST_CASE("Bitmaps") {
CHECK(nc_->tcache.bitmap_supported); CHECK(nc_->tcache.bitmap_supported);
} }
SUBCASE("SprixelResize") {
auto y = 10;
auto x = 10;
std::vector<uint32_t> v(x * y, htole(0xe61c28ff));
auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
REQUIRE(nullptr != ncv);
struct ncvisual_options vopts = {
.n = nullptr,
.scaling = NCSCALE_NONE,
.y = 0, .x = 0,
.begy = 0, .begx = 0,
.leny = 0, .lenx = 0,
.blitter = NCBLIT_PIXEL,
.flags = NCVISUAL_OPTION_NODEGRADE,
.transcolor = 0,
};
CHECK(0 == ncvisual_resize(ncv, 6, 1)); // FIXME get down to 1, 1
auto n = ncvisual_render(nc_, ncv, &vopts);
REQUIRE(nullptr != n);
auto s = n->sprite;
REQUIRE(nullptr != s);
ncvisual_destroy(ncv);
}
#ifdef NOTCURSES_USE_MULTIMEDIA #ifdef NOTCURSES_USE_MULTIMEDIA
SUBCASE("PixelRender") { SUBCASE("PixelRender") {
auto ncv = ncvisual_from_file(find_data("worldmap.png")); auto ncv = ncvisual_from_file(find_data("worldmap.png"));

View File

@ -18,6 +18,7 @@ auto testing_notcurses() -> struct notcurses* {
notcurses_options nopts{}; notcurses_options nopts{};
// FIXME get loglevel from command line. enabling it by default leads to // FIXME get loglevel from command line. enabling it by default leads to
// more confusion than useful information, so leave it off by default. // more confusion than useful information, so leave it off by default.
//nopts.loglevel = NCLOGLEVEL_TRACE;
nopts.flags = NCOPTION_SUPPRESS_BANNERS | NCOPTION_NO_ALTERNATE_SCREEN; nopts.flags = NCOPTION_SUPPRESS_BANNERS | NCOPTION_NO_ALTERNATE_SCREEN;
auto nc = notcurses_init(&nopts, nullptr); auto nc = notcurses_init(&nopts, nullptr);
return nc; return nc;
@ -84,6 +85,11 @@ reset_terminal(){
printf("%s", str); printf("%s", str);
} }
fflush(stdout); fflush(stdout);
str = tigetstr("cnorm");
if(str != (char*)-1){
printf("%s", str);
}
fflush(stdout);
close(fd); close(fd);
} }
} }

View File

@ -1,6 +1,141 @@
#include "main.h" #include "main.h"
#include "visual-details.h" #include "visual-details.h"
#include <vector> #include <vector>
#include <iostream>
// convert the sprixel at s having pixel dimensions dimyXdimx to an rgb(a)
// matrix for easier analysis. breaks on malformed sixels.
std::vector<uint32_t> sixel_to_rgb(const char* s, int dimy, int dimx) {
std::vector<uint32_t> bmap(dimy * dimx, 0x00000000ull);
std::vector<uint32_t> colors;
// first we skip the header
while(*s != '#'){
++s;
}
// now we build the color table (form: #Pc;Pu;Px;Py;Pz). data starts with
// a color spec lacking a semicolon.
enum {
STATE_WANT_HASH,
STATE_WANT_COLOR,
STATE_WANT_COLORSEMI,
STATE_WANT_COLORSPACE,
STATE_WANT_DATA,
} state = STATE_WANT_HASH;
unsigned color = 0;
unsigned x = 0;
unsigned y = 0;
unsigned rle = 1;
while(*s){
if(*s == '\e'){
break;
}
if(state == STATE_WANT_HASH){
REQUIRE('#' == *s);
state = STATE_WANT_COLOR;
}else if(state == STATE_WANT_COLOR){
CHECK(isdigit(*s));
color = 0;
do{
color *= 10;
color += *s - '0';
++s;
}while(isdigit(*s));
//std::cerr << "Got color " << color << std::endl;
--s;
state = STATE_WANT_COLORSEMI;
}else if(state == STATE_WANT_COLORSEMI){
// if we get a semicolon, we're a colorspec, otherwise data
if(*s == ';'){
state = STATE_WANT_COLORSPACE;
}else{
state = STATE_WANT_DATA;
rle = 1;
}
}else if(state == STATE_WANT_COLORSPACE){
CHECK('2' == *(s++));
CHECK(';' == *(s++));
int r = 0;
do{
r *= 10;
r += *s - '0';
++s;
}while(isdigit(*s));
CHECK(';' == *(s++));
int g = 0;
do{
g *= 10;
g += *s - '0';
++s;
}while(isdigit(*s));
CHECK(';' == *(s++));
int b = 0;
do{
b *= 10;
b += *s - '0';
++s;
}while(isdigit(*s));
uint32_t rgb = 0xff000000 + (r << 16u) * 255 / 100 + (g << 8u) * 255 / 100 + b * 255 / 100;
//std::cerr << "Got color " << color << ": " << r << "/" << g << "/" << b << std::endl;
if(color >= colors.capacity()){
colors.resize(color + 1);
}
colors[color] = rgb;
state = STATE_WANT_HASH;
--s;
}
// read until we hit next colorspec
if(state == STATE_WANT_DATA){
//std::cerr << "Character " << *s << std::endl;
if(*s == '#'){
state = STATE_WANT_HASH;
--s;
}else if(*s == '!'){ // RLE
++s;
rle = 0;
do{
rle *= 10;
rle += *s - '0';
++s;
}while(isdigit(*s));
CHECK(2 < rle);
--s;
}else if(*s == '$'){
x = 0;
state = STATE_WANT_HASH;
}else if(*s == '-'){
x = 0;
y += 6;
state = STATE_WANT_HASH;
}else{
//std::cerr << "RLE: " << rle << " pos: " << y << "*" << x << std::endl;
for(unsigned xpos = x ; xpos < x + rle ; ++xpos){
for(unsigned ypos = y ; ypos < y + 6 ; ++ypos){
if((*s - 63) & (1u << (ypos - y))){
// ought be an empty pixel
CHECK(0x00000000ull == bmap[ypos * dimx + xpos]);
//std::cerr << *s << " BMAP[" << ypos << "][" << xpos << "] = " << std::hex << colors[color] << std::dec << std::endl;
bmap[ypos * dimx + xpos] = colors[color];
}
}
}
x += rle;
rle = 1;
}
}
++s;
}
return bmap;
}
/*
void print_bmap(const std::vector<uint32_t> rgba, int pixy, int pixx){
for(int y = 0 ; y < pixy ; ++y){
for(int x = 0 ; x < pixx ; ++x){
std::cerr << "rgba[" << y << "][" << x << "] (" << y * x << "): " << std::hex << rgba[y * pixx + x] << std::dec << std::endl;
}
}
}
*/
TEST_CASE("Sixels") { TEST_CASE("Sixels") {
auto nc_ = testing_notcurses(); auto nc_ = testing_notcurses();
@ -10,14 +145,36 @@ TEST_CASE("Sixels") {
auto n_ = notcurses_stdplane(nc_); auto n_ = notcurses_stdplane(nc_);
REQUIRE(n_); REQUIRE(n_);
// FIXME force Sixel support // this can only run with a Sixel backend
if(notcurses_check_pixel_support(nc_) <= 0){ if(notcurses_check_pixel_support(nc_) <= 0){
CHECK(0 == nc_->tcache.bitmap_supported); return;
CHECK(!notcurses_stop(nc_)); }
if(nc_->tcache.color_registers <= 0){
return; return;
} }
#ifdef NOTCURSES_USE_MULTIMEDIA #ifdef NOTCURSES_USE_MULTIMEDIA
SUBCASE("SixelRoundtrip") {
CHECK(1 == ncplane_set_base(n_, "&", 0, 0));
auto ncv = ncvisual_from_file(find_data("worldmap.png"));
REQUIRE(ncv);
struct ncvisual_options vopts{};
vopts.blitter = NCBLIT_PIXEL;
vopts.flags = NCVISUAL_OPTION_NODEGRADE;
auto newn = ncvisual_render(nc_, ncv, &vopts);
CHECK(newn);
CHECK(0 == notcurses_render(nc_));
auto rgb = sixel_to_rgb(newn->sprite->glyph, newn->sprite->pixy, newn->sprite->pixx);
for(int y = 0 ; y < newn->sprite->pixy ; ++y){
for(int x = 0 ; x < newn->sprite->pixx ; ++x){
//fprintf(stderr, "%03d/%03d NCV: %08x RGB: %08x\n", y, x, ncv->data[y * newn->sprite->pixx + x], rgb[y * newn->sprite->pixx + x]);
// FIXME
//CHECK(ncv->data[y * newn->sprite->pixx + x] == rgb[y * newn->sprite->pixx + x]);
}
}
ncvisual_destroy(ncv);
}
SUBCASE("SixelBlit") { SUBCASE("SixelBlit") {
CHECK(1 == ncplane_set_base(n_, "&", 0, 0)); CHECK(1 == ncplane_set_base(n_, "&", 0, 0));
auto ncv = ncvisual_from_file(find_data("natasha-blur.png")); auto ncv = ncvisual_from_file(find_data("natasha-blur.png"));
@ -27,6 +184,8 @@ TEST_CASE("Sixels") {
vopts.flags = NCVISUAL_OPTION_NODEGRADE; vopts.flags = NCVISUAL_OPTION_NODEGRADE;
auto newn = ncvisual_render(nc_, ncv, &vopts); auto newn = ncvisual_render(nc_, ncv, &vopts);
CHECK(newn); CHECK(newn);
auto rgbold = sixel_to_rgb(newn->sprite->glyph, newn->sprite->pixy, newn->sprite->pixx);
//print_bmap(rgbold, newn->sprite->pixy, newn->sprite->pixx);
CHECK(0 == notcurses_render(nc_)); CHECK(0 == notcurses_render(nc_));
struct ncplane_options nopts = { struct ncplane_options nopts = {
.y = ncplane_dim_y(newn) * 3 / 4, .y = ncplane_dim_y(newn) * 3 / 4,
@ -43,12 +202,12 @@ TEST_CASE("Sixels") {
uint64_t chan = CHANNELS_RGB_INITIALIZER(0, 0, 0, 0, 0, 0); uint64_t chan = CHANNELS_RGB_INITIALIZER(0, 0, 0, 0, 0, 0);
CHECK(1 == ncplane_set_base(blockerplane, " ", 0, chan)); CHECK(1 == ncplane_set_base(blockerplane, " ", 0, chan));
CHECK(0 == notcurses_render(nc_)); CHECK(0 == notcurses_render(nc_));
sleep(2);
CHECK(1 == ncplane_set_base(n_, "%", 0, 0)); CHECK(1 == ncplane_set_base(n_, "%", 0, 0));
CHECK(0 == notcurses_render(nc_)); CHECK(0 == notcurses_render(nc_));
sleep(2);
// FIXME at this point currently, we get a degraded back of the orca // FIXME at this point currently, we get a degraded back of the orca
// test via conversion back to image? unsure // test via conversion back to image? unsure
auto rgbnew = sixel_to_rgb(newn->sprite->glyph, newn->sprite->pixy, newn->sprite->pixx);
//print_bmap(rgbnew, newn->sprite->pixy, newn->sprite->pixx);
CHECK(0 == ncplane_destroy(newn)); CHECK(0 == ncplane_destroy(newn));
CHECK(0 == ncplane_destroy(blockerplane)); CHECK(0 == ncplane_destroy(blockerplane));
ncvisual_destroy(ncv); ncvisual_destroy(ncv);