Panblitter ncplane_as_rgba() (#1697)

ncplane_as_rgba: genericize across blitters

ncplane_as_rgba() previously was implemented explicitly
in terms of NCBLIT_2x1. it now uses the `egcs` field of the
`blitset`. this supports NCBLIT_2x2 and NCBLIT_3x2 in
ncplane_rgba(). Closes #1490.
This commit is contained in:
Nick Black 2021-06-01 02:25:11 -04:00 committed by GitHub
parent d216b4115e
commit cd2b5de5fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 371 additions and 93 deletions

View File

@ -650,7 +650,7 @@ target_link_libraries(notcurses-tester
)
add_test(
NAME notcurses-tester
COMMAND notcurses-tester -p ${CMAKE_CURRENT_SOURCE_DIR}/data
COMMAND notcurses-tester -p ${CMAKE_CURRENT_SOURCE_DIR}/data --abort-after=1
)
set_tests_properties(notcurses-tester PROPERTIES RUN_SERIAL TRUE)
install(TARGETS notcurses-tester DESTINATION bin)

12
NEWS.md
View File

@ -10,6 +10,18 @@ rearrangements of Notcurses.
only if its `ncinput` argument has no modifiers active.
* Added `notcurses_cursor_yx()` to get the current location of the cursor.
* Added `ncdirect_supported_styles()`.
* `ncplane_at_yx()` now properly integrates the plane's base cell when
appropriate, and thus represents the cell as it will be used during
rendering. This change cascades, affecting `ncplane_at_yx_cell()`,
`ncplane_contents()`, and `ncplane_as_rgba()`.
* `ncplane_at_yx()` now returns the EGC when called on any column of a
wide glyph. `ncplane_at_yx_cell()` continues to duplicate the exact
`nccell`, and can thus continue to be used to distinguish between primary
and secondary columns of a wide glyph. Likewise, `notcurses_at_yx()`
now returns the EGC when called on any column of a wide glyph.
* Sadly, `ncplane_contents()` no longer accepts a `const ncplane*`, since it
might write temporaries to the plane's EGCpool during operation.
* Added `ncdirect_styles()`, to retrieve the current styling.
* 2.3.1 (2021-05-18)
* Sprixels no longer interact with their associated plane's framebuffer. This

View File

@ -1045,13 +1045,20 @@ char* ncplane_at_cursor(struct ncplane* n, uint16_t* styles, uint64_t* channels)
int ncplane_at_cursor_cell(struct ncplane* n, nccell* c);
// Retrieve the current contents of the specified cell. The EGC is returned, or
// NULL on error. This EGC must be free()d by the caller. The styles and
// channels are written to 'styles' and 'channels', respectively.
// NULL on error. This EGC must be free()d by the caller. The stylemask and
// channels are written to 'stylemask' and 'channels', respectively. The return
// represents how the cell will be used during rendering, and thus integrates
// any base cell where appropriate. If called upon the secondary columns of a
// wide glyph, the EGC will be returned (i.e. this function does not distinguish
// between the primary and secondary columns of a wide glyph).
char* ncplane_at_yx(const struct ncplane* n, int y, int x,
uint16_t* styles, uint64_t* channels);
uint16_t* stylemask, uint64_t* channels);
// Retrieve the current contents of the specified cell into 'c'. This cell is
// invalidated if the associated plane is destroyed.
// invalidated if the associated plane is destroyed. Returns the number of
// bytes in the EGC, or -1 on error. Unlike ncplane_at_yx(), when called upon
// the secondary columns of a wide glyph, the return can be distinguished from
// the primary column (nccell_wide_right_p(c) will return true).
int ncplane_at_yx_cell(struct ncplane* n, int y, int x, nccell* c);
// Create an RGBA flat array from the selected region of the ncplane 'nc'.

View File

@ -375,6 +375,16 @@ is resized, the plane is erased, or the plane is destroyed. The base cell of a
sprixelated plane has no effect; if the sprixel is not even multiples of the
cell geometry, the "excess plane" is ignored during rendering.
**ncplane_at_yx** and **ncplane_at_yx_cell** retrieve the contents of the plane
at the specified coordinate. The content is returned as it will be used during
rendering, and thus integrates any base cell as appropriate. If called on the
secondary columns of a wide glyph, **ncplane_at_yx** returns the EGC, and thus
cannot be used to distinguish between primary and secondary columns.
**ncplane_at_yx_cell**, however, preserves this information: retrieving a
secondary column of a wide glyph with **ncplane_at_yx_cell** will fill in
the **nccell** argument such that **nccell_extended_gcluster(3)** returns an
empty string, and **nccell_wide_right_p(3)** returns **true**.
# RETURN VALUES
**ncplane_create** and **ncplane_dup** return a new **struct ncplane** on
@ -394,10 +404,11 @@ respectively, of the pile containing their argument. **notcurses_top** and
**notcurses_bottom** do the same for the standard pile.
**ncplane_at_yx** and **ncplane_at_cursor** return a heap-allocated copy of the
EGC at the relevant cell, or **NULL** if the cell is invalid. The caller should free
this result. **ncplane_at_yx_cell** and **ncplane_at_cursor_cell** instead load
these values into an **nccell**, which is invalidated if the associated plane is
destroyed. The caller should release this **nccell** with **nccell_release**.
EGC at the relevant cell, or **NULL** if the cell is invalid. The caller should
free this result. **ncplane_at_yx_cell** and **ncplane_at_cursor_cell** instead
load these values into an **nccell**, which is invalidated if the associated
plane is destroyed. The caller should release this **nccell** with
**nccell_release**.
**ncplane_as_rgba** returns a heap-allocated array of **uint32_t** values,
each representing a single RGBA pixel, or **NULL** on failure.

View File

@ -93,8 +93,9 @@ If the algorithm concludes without an EGC, the cell is rendered with no glyph
and a default background. If the algorithm concludes without a color locked in,
the color as computed thus far is used.
**notcurses_at_yx** retrieves a call *as rendered*. The EGC in that cell is
copied and returned; it must be **free(3)**d by the caller.
**notcurses_at_yx** retrieves a cell *as rendered*. The EGC in that cell is
copied and returned; it must be **free(3)**d by the caller. If the cell is a
secondary column of a wide glyph, the glyph is still returned.
# RETURN VALUES

View File

@ -47,7 +47,7 @@
### Fedora
* In pagure/notcurses master:
* In pagure/notcurses rawhide:
* Update notcurses.spec, bump version, add changelog entry
* clear out any old ersatz detritus
* spectool -g notcurses.spec
@ -56,8 +56,9 @@
* fedpkg commit
* git push
* fedpkg build
* for each active branch:
* fedpkg switch-branch f32
* git merge master
* git merge rawhide
### Arch

View File

@ -748,7 +748,7 @@ nccell_wide_left_p(const nccell* c){
// return a pointer to the NUL-terminated EGC referenced by 'c'. this pointer
// can be invalidated by any further operation on the plane 'n', so...watch out!
API const char* nccell_extended_gcluster(const struct ncplane* n, const nccell* c);
API __attribute__ ((returns_nonnull)) const char* nccell_extended_gcluster(const struct ncplane* n, const nccell* c);
// return the number of columns occupied by 'c'. returns -1 if passed a
// sprixcell. see ncstrwidth() for an equivalent for multiple EGCs.
@ -1467,20 +1467,26 @@ API int ncplane_at_cursor_cell(struct ncplane* n, nccell* c);
// Retrieve the current contents of the specified cell. The EGC is returned, or
// NULL on error. This EGC must be free()d by the caller. The stylemask and
// channels are written to 'stylemask' and 'channels', respectively.
// channels are written to 'stylemask' and 'channels', respectively. The return
// represents how the cell will be used during rendering, and thus integrates
// any base cell where appropriate. If called upon the secondary columns of a
// wide glyph, the EGC will be returned (i.e. this function does not distinguish
// between the primary and secondary columns of a wide glyph).
API char* ncplane_at_yx(const struct ncplane* n, int y, int x,
uint16_t* stylemask, uint64_t* channels);
// Retrieve the current contents of the specified cell into 'c'. This cell is
// invalidated if the associated plane is destroyed. Returns the number of
// bytes in the EGC, or -1 on error.
// bytes in the EGC, or -1 on error. Unlike ncplane_at_yx(), when called upon
// the secondary columns of a wide glyph, the return can be distinguished from
// the primary column (nccell_wide_right_p(c) will return true).
API int ncplane_at_yx_cell(struct ncplane* n, int y, int x, nccell* c);
// Create a flat string from the EGCs of the selected region of the ncplane
// 'n'. Start at the plane's 'begy'x'begx' coordinate (which must lie on the
// plane), continuing for 'leny'x'lenx' cells. Either or both of 'leny' and
// 'lenx' can be specified as -1 to go through the boundary of the plane.
API char* ncplane_contents(const struct ncplane* n, int begy, int begx,
API char* ncplane_contents(struct ncplane* n, int begy, int begx,
int leny, int lenx);
// Manipulate the opaque user pointer associated with this plane.

View File

@ -691,7 +691,7 @@ impl NcPlane {
///
/// *C style function: [ncplane_contents()][crate::ncplane_contents].*
pub fn contents(
&self,
&mut self,
beg_y: NcDim,
beg_x: NcDim,
len_y: NcDim,

View File

@ -62,9 +62,7 @@ rotate_visual(struct notcurses* nc, struct ncplane* n, int dy, int dx){
fromx = (dx - dy * 2) / 2;
dx = dy * 2;
}
//fprintf(stderr, "ASK %d/%d @ %d/%d: %p\n", dy, dx, fromy, fromx);
struct ncvisual* ncv = ncvisual_from_plane(n, NCBLIT_DEFAULT, fromy, fromx, dy, dx);
//fprintf(stderr, "%d/%d @ %d/%d: %p\n", dy, dx, fromy, fromx, ncv);
struct ncvisual* ncv = ncvisual_from_plane(n, NCBLIT_2x1, fromy, fromx, dy, dx);
if(!ncv){
ncvisual_destroy(ncv);
return -1;

View File

@ -206,7 +206,22 @@ void Ticker(ncpp::NotCurses* nc) {
int input_demo(ncpp::NotCurses* nc) {
constexpr auto PLOTHEIGHT = 6;
auto n = nc->get_stdplane(&dimy, &dimx);
ncpp::Plane pplane{PLOTHEIGHT, dimx, dimy - PLOTHEIGHT, 0, nullptr};
struct ncplane_options nopts = {
.y = dimy - PLOTHEIGHT,
.x = 0,
.rows = PLOTHEIGHT,
.cols = dimx,
.userptr = nullptr,
.name = "plot",
.resizecb = nullptr, // FIXME
.flags = 0,
.margin_b = 0,
.margin_r = 0,
};
struct ncplane* pplane = ncplane_create(*n, &nopts);
if(pplane == nullptr){
return EXIT_FAILURE;
}
struct ncplot_options popts{};
// FIXME would be nice to switch over to exponential at some level
popts.flags = NCPLOT_OPTION_LABELTICKSD | NCPLOT_OPTION_PRINTSAMPLE;
@ -222,11 +237,13 @@ int input_demo(ncpp::NotCurses* nc) {
n->set_bg_rgb8(0xbb, 0x64, 0xbb);
n->styles_on(CellStyle::Underline);
if(n->putstr(0, NCAlign::Center, "mash keys, yo. give that mouse some waggle! ctrl+d exits.") <= 0){
ncuplot_destroy(plot);
return -1;
}
n->styles_set(CellStyle::None);
n->set_bg_default();
if(!nc->render()){
ncuplot_destroy(plot);
return -1;
}
int y = 2;
@ -243,6 +260,7 @@ int input_demo(ncpp::NotCurses* nc) {
if((r == 'D' || r == 'd') && ni.ctrl){
done = true;
tid.join();
ncuplot_destroy(plot);
return 0;
}
if((r == 'L' || r == 'l') && ni.ctrl){
@ -292,6 +310,7 @@ int input_demo(ncpp::NotCurses* nc) {
}
if(!nc->render()){
mtx.unlock();
ncuplot_destroy(plot);
throw std::runtime_error("error rendering");
}
mtx.unlock();
@ -309,6 +328,7 @@ int input_demo(ncpp::NotCurses* nc) {
}
done = true;
tid.join();
ncuplot_destroy(plot);
return 0;
}

View File

@ -850,24 +850,38 @@ braille_blit(ncplane* nc, int linesize, const void* data,
// NCBLIT_DEFAULT is not included, as it has no defined properties. It ought
// be replaced with some real blitter implementation by the calling widget.
// The order of contents is critical for 'egcs': ncplane_as_rgba() uses these
// arrays to map cells to source pixels. Map the upper-left logical bit to
// 1, and increase to the right, followed by down. The first egc ought thus
// always be space, to indicate an empty cell (all zeroes).
static struct blitset notcurses_blitters[] = {
{ .geom = NCBLIT_8x1, .width = 1, .height = 8, .egcs = L" ▁▂▃▄▅▆▇█",
{ .geom = NCBLIT_8x1, .width = 1, .height = 8,
.egcs = NULL, .plotegcs = L" ▁▂▃▄▅▆▇█",
.blit = tria_blit, .name = "eightstep", .fill = false, },
{ .geom = NCBLIT_1x1, .width = 1, .height = 1, .egcs = L"",
{ .geom = NCBLIT_1x1, .width = 1, .height = 1,
.egcs = L"", .plotegcs = L"",
.blit = tria_blit_ascii,.name = "ascii", .fill = false, },
{ .geom = NCBLIT_2x1, .width = 1, .height = 2, .egcs = L" ▄█",
{ .geom = NCBLIT_2x1, .width = 1, .height = 2,
.egcs = L" ▀▄█", .plotegcs = L" ▄█",
.blit = tria_blit, .name = "half", .fill = false, },
{ .geom = NCBLIT_2x2, .width = 2, .height = 2, .egcs = L" ▗▐▖▄▟▌▙█",
{ .geom = NCBLIT_2x2, .width = 2, .height = 2,
.egcs = L" ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█", .plotegcs = L" ▗▐▖▄▟▌▙█",
.blit = quadrant_blit, .name = "quad", .fill = false, },
{ .geom = NCBLIT_3x2, .width = 2, .height = 3, .egcs = L" 🬞🬦▐🬏🬭🬵🬷🬓🬱🬹🬻▌🬲🬺█",
{ .geom = NCBLIT_3x2, .width = 2, .height = 3,
.egcs = L" 🬀🬁🬂🬃🬄🬅🬆🬇🬈🬊🬋🬌🬍🬎🬏🬐🬑🬒🬓▌🬔🬕🬖🬗🬘🬙🬚🬛🬜🬝🬞🬟🬠🬡🬢🬣🬤🬥🬦🬧🬨🬩🬪🬫🬬🬭🬮🬯🬰🬱🬲🬳🬴🬵🬶🬷🬸🬹🬺🬻█",
.plotegcs = L" 🬞🬦▐🬏🬭🬵🬷🬓🬱🬹🬻▌🬲🬺█",
.blit = sextant_blit, .name = "sex", .fill = false, },
{ .geom = NCBLIT_4x1, .width = 1, .height = 4, .egcs = L" ▂▄▆█",
{ .geom = NCBLIT_4x1, .width = 1, .height = 4,
.egcs = NULL, .plotegcs = L" ▂▄▆█",
.blit = tria_blit, .name = "fourstep", .fill = false, },
{ .geom = NCBLIT_BRAILLE, .width = 2, .height = 4, .egcs = L"⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿",
{ .geom = NCBLIT_BRAILLE, .width = 2, .height = 4,
.egcs = NULL, .plotegcs = L"⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿", // FIXME
.blit = braille_blit, .name = "braille", .fill = true, },
{ .geom = NCBLIT_PIXEL, .width = 1, .height = 1, .egcs = L"",
{ .geom = NCBLIT_PIXEL, .width = 1, .height = 1,
.egcs = L"", .plotegcs = NULL,
.blit = sixel_blit, .name = "pixel", .fill = true, },
{ .geom = 0, .width = 0, .height = 0, .egcs = NULL,
{ .geom = 0, .width = 0, .height = 0,
.egcs = NULL, .plotegcs = NULL,
.blit = NULL, .name = NULL, .fill = false, },
};
@ -924,7 +938,7 @@ const struct blitset* lookup_blitset(const tinfo* tcache, ncblitter_e setid, boo
}
}
const struct blitset* bset = notcurses_blitters;
while(bset->egcs){
while(bset->geom){
if(bset->geom == setid){
return bset;
}

View File

@ -535,11 +535,14 @@ struct blitset {
ncblitter_e geom;
int width; // number of input pixels per output cell, width
int height; // number of input pixels per output cell, height
// the EGCs which form the blitter. bits grow left to right, and then top to
// bottom. the first character is always a space, the last a full block.
const wchar_t* egcs;
// the EGCs which form the various levels of a given plotset. if the geometry
// is wide, things are arranged with the rightmost side increasing most
// quickly, i.e. it can be indexed as height arrays of 1 + height glyphs. i.e.
// the first five braille EGCs are all 0 on the left, [0..4] on the right.
const wchar_t* egcs;
const wchar_t* plotegcs;
ncblitter blit;
const char* name;
bool fill;
@ -809,7 +812,7 @@ pool_extended_gcluster(const egcpool* pool, const nccell* c){
}
static inline nccell*
ncplane_cell_ref_yx(ncplane* n, int y, int x){
ncplane_cell_ref_yx(const ncplane* n, int y, int x){
return &n->fb[nfbcellidx(n, y, x)];
}
@ -1425,7 +1428,7 @@ pool_blit_direct(egcpool* pool, nccell* c, const char* gcluster, int bytes, int
}
// Do an RTL-check, reset the quadrant occupancy bits, and pass the cell down to
// pool_blit_direct().
// pool_blit_direct(). Returns the number of bytes loaded.
static inline int
pool_load_direct(egcpool* pool, nccell* c, const char* gcluster, int bytes, int cols){
char* rtl = NULL;

View File

@ -161,7 +161,27 @@ char* ncplane_at_cursor(ncplane* n, uint16_t* stylemask, uint64_t* channels){
char* ncplane_at_yx(const ncplane* n, int y, int x, uint16_t* stylemask, uint64_t* channels){
if(y < n->leny && x < n->lenx){
if(y >= 0 && x >= 0){
return nccell_extract(n, &n->fb[nfbcellidx(n, y, x)], stylemask, channels);
const cell* yx = &n->fb[nfbcellidx(n, y, x)];
// if we're the right side of a wide glyph, we return the main glyph
if(nccell_wide_right_p(yx)){
return ncplane_at_yx(n, y, x - 1, stylemask, channels);
}
char* ret = nccell_extract(n, yx, stylemask, channels);
if(ret == NULL){
return NULL;
}
//fprintf(stderr, "GOT [%s]\n", ret);
if(strcmp(ret, "") == 0){
ret = nccell_strdup(n, &n->basecell);
if(ret == NULL){
return NULL;
}
if(stylemask){
*stylemask = n->basecell.stylemask;
}
}
// FIXME load basecell channels if appropriate
return ret;
}
}
return NULL;
@ -176,6 +196,7 @@ int ncplane_at_yx_cell(ncplane* n, int y, int x, nccell* c){
if(y >= 0 && x >= 0){
nccell* targ = ncplane_cell_ref_yx(n, y, x);
if(nccell_duplicate(n, c, targ) == 0){
// FIXME take base cell into account where necessary!
return strlen(nccell_extended_gcluster(n, targ));
}
}
@ -2583,7 +2604,38 @@ int ncdirect_inputready_fd(ncdirect* n){
return n->tcache.input.ttyinfd;
}
uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit,
// FIXME speed this up, PoC
// given an egc, get its index in the blitter's EGC set
static int
get_blitter_egc_idx(const struct blitset* bset, const char* egc){
wchar_t wc;
mbstate_t mbs = {};
size_t sret = mbrtowc(&wc, egc, strlen(egc), &mbs);
if(sret == (size_t)-1 || sret == (size_t)-2){
return -1;
}
wchar_t* wptr = wcsrchr(bset->egcs, wc);
if(wptr == NULL){
//fprintf(stderr, "FAILED TO FIND [%s] (%lc) in [%ls]\n", egc, wc, bset->egcs);
return -1;
}
//fprintf(stderr, "FOUND [%s] (%lc) in [%ls] (%zu)\n", egc, wc, bset->egcs, wptr - bset->egcs);
return wptr - bset->egcs;
}
static bool
is_bg_p(int idx, int py, int px, int width){
// bit increases to the right, and down
const int bpos = py * width + px; // bit corresponding to pixel, 0..|egcs|-1
const unsigned mask = 1u << bpos;
if(idx & mask){
return false;
}
return true;
}
static inline uint32_t*
ncplane_as_rgba_internal(const ncplane* nc, ncblitter_e blit,
int begy, int begx, int leny, int lenx,
int* pxdimy, int* pxdimx){
const notcurses* ncur = ncplane_notcurses_const(nc);
@ -2611,23 +2663,31 @@ uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit,
begx, lenx, nc->lenx, begy, leny, nc->leny);
return NULL;
}
if(blit > NCBLIT_2x1){
logerror(ncur, "Blitter %d is not yet supported\n", blit);
if(blit == NCBLIT_PIXEL){ // FIXME extend this to support sprixels
logerror(ncur, "Pixel blitter %d not yet supported\n", blit);
return NULL;
}
//fprintf(stderr, "ALLOCATING %zu %d %d\n", 4u * lenx * leny * 2, leny, lenx);
// FIXME this all assumes NCBLIT_2x1, need blitter-specific scaling
if(blit == NCBLIT_DEFAULT){
logerror(ncur, "Must specify exact blitter, not NCBLIT_DEFAULT\n");
return NULL;
}
const struct blitset* bset = lookup_blitset(&ncur->tcache, blit, false);
if(bset == NULL){
logerror(ncur, "Blitter %d invalid in current environment\n", blit);
return NULL;
}
//fprintf(stderr, "ALLOCATING %u %d %d %p\n", 4u * lenx * leny * 2, leny, lenx, bset);
if(pxdimy){
*pxdimy = leny * 2;
*pxdimy = leny * bset->height;
}
if(pxdimx){
*pxdimx = lenx;
*pxdimx = lenx * bset->width;
}
uint32_t* ret = malloc(sizeof(*ret) * lenx * leny * 2);
uint32_t* ret = malloc(sizeof(*ret) * lenx * bset->width * leny * bset->height);
//fprintf(stderr, "GEOM: %d/%d %d/%d ret: %p\n", bset->height, bset->width, *pxdimy, *pxdimx, ret);
if(ret){
for(int y = begy, targy = 0 ; y < begy + leny ; ++y, targy += 2){
for(int x = begx, targx = 0 ; x < begx + lenx ; ++x, ++targx){
// FIXME what if there's a wide glyph to the left of the selection?
for(int y = begy, targy = 0 ; y < begy + leny ; ++y, targy += bset->height){
for(int x = begx, targx = 0 ; x < begx + lenx ; ++x, targx += bset->width){
uint16_t stylemask;
uint64_t channels;
char* c = ncplane_at_yx(nc, y, x, &stylemask, &channels);
@ -2635,31 +2695,43 @@ uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit,
free(ret);
return NULL;
}
uint32_t* top = &ret[targy * lenx + targx];
uint32_t* bot = &ret[(targy + 1) * lenx + targx];
unsigned fr, fg, fb, br, bg, bb;
ncchannels_fg_rgb8(channels, &fr, &fb, &fg);
ncchannels_bg_rgb8(channels, &br, &bb, &bg);
// FIXME how do we deal with transparency?
uint32_t frgba = (fr) + (fg << 16u) + (fb << 8u) + 0xff000000;
uint32_t brgba = (br) + (bg << 16u) + (bb << 8u) + 0xff000000;
// FIXME need to be able to pick up quadrants!
if((strcmp(c, " ") == 0) || (strcmp(c, "") == 0)){
*top = *bot = brgba;
}else if(strcmp(c, "") == 0){
*top = frgba;
*bot = brgba;
}else if(strcmp(c, "") == 0){
*top = brgba;
*bot = frgba;
}else if(strcmp(c, "") == 0){
*top = *bot = frgba;
}else{
free(c);
int idx = get_blitter_egc_idx(bset, c);
if(idx < 0){
free(ret);
//fprintf(stderr, "bad rgba character: %s\n", c);
free(c);
return NULL;
}
unsigned fr, fg, fb, br, bg, bb, fa, ba;
ncchannels_fg_rgb8(channels, &fr, &fb, &fg);
fa = ncchannels_fg_alpha(channels);
ncchannels_bg_rgb8(channels, &br, &bb, &bg);
ba = ncchannels_bg_alpha(channels);
// handle each destination pixel from this cell
for(int py = 0 ; py < bset->height ; ++py){
for(int px = 0 ; px < bset->width ; ++px){
uint32_t* p = &ret[(targy + py) * (lenx * bset->width) + (targx + px)];
bool background = is_bg_p(idx, py, px, bset->width);
if(background){
if(ba){
*p = 0;
}else{
ncpixel_set_a(p, 0xff);
ncpixel_set_r(p, br);
ncpixel_set_g(p, bb);
ncpixel_set_b(p, bg);
}
}else{
if(fa){
*p = 0;
}else{
ncpixel_set_a(p, 0xff);
ncpixel_set_r(p, fr);
ncpixel_set_g(p, fb);
ncpixel_set_b(p, fg);
}
}
}
}
free(c);
}
}
@ -2667,8 +2739,21 @@ uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit,
return ret;
}
uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit,
int begy, int begx, int leny, int lenx,
int* pxdimy, int* pxdimx){
int px, py;
if(!pxdimy){
pxdimy = &py;
}
if(!pxdimx){
pxdimx = &px;
}
return ncplane_as_rgba_internal(nc, blit, begy, begx, leny, lenx, pxdimy, pxdimx);
}
// return a heap-allocated copy of the contents
char* ncplane_contents(const ncplane* nc, int begy, int begx, int leny, int lenx){
char* ncplane_contents(ncplane* nc, int begy, int begx, int leny, int lenx){
if(begy < 0 || begx < 0){
logerror(ncplane_notcurses_const(nc), "Beginning coordinates (%d/%d) below 0\n", begy, begx);
return NULL;
@ -2698,18 +2783,18 @@ char* ncplane_contents(const ncplane* nc, int begy, int begx, int leny, int lenx
if(ret){
for(int y = begy, targy = 0 ; y < begy + leny ; ++y, targy += 2){
for(int x = begx, targx = 0 ; x < begx + lenx ; ++x, ++targx){
uint16_t stylemask;
uint64_t channels;
char* c = ncplane_at_yx(nc, y, x, &stylemask, &channels);
if(!c){
nccell ncl = CELL_TRIVIAL_INITIALIZER;
// we need ncplane_at_yx_cell() here instead of ncplane_at_yx(),
// because we should only have one copy of each wide EGC.
int clen;
if((clen = ncplane_at_yx_cell(nc, y, x, &ncl)) < 0){
free(ret);
return NULL;
}
size_t clen = strlen(c);
const char* c = nccell_extended_gcluster(nc, &ncl);
if(clen){
char* tmp = realloc(ret, retlen + clen);
if(!tmp){
free(c);
free(ret);
return NULL;
}
@ -2717,7 +2802,6 @@ char* ncplane_contents(const ncplane* nc, int begy, int begx, int leny, int lenx
memcpy(ret + retlen - 1, c, clen);
retlen += clen;
}
free(c);
}
}
ret[retlen - 1] = '\0';

View File

@ -124,7 +124,7 @@ int redraw_plot_##T(nc##X##plot* ncp){ \
direction, drawing egcs from the grid specification, aborting early if \
we can't draw anything in a given cell. */ \
T intervalbase = ncp->miny; \
const wchar_t* egc = ncp->bset->egcs; \
const wchar_t* egc = ncp->bset->plotegcs; \
bool done = !ncp->bset->fill; \
for(int y = 0 ; y < dimy ; ++y){ \
uint64_t channels = 0; \
@ -204,6 +204,7 @@ int redraw_plot_##T(nc##X##plot* ncp){ \
if(ncp->printsample){ \
int lastslot = ncp->slotstart ? ncp->slotstart - 1 : ncp->slotcount - 1; \
ncplane_set_styles(ncp->ncp, ncp->legendstyle); \
ncplane_set_channels(ncp->ncp, ncp->maxchannels); \
ncplane_printf_aligned(ncp->ncp, 0, NCALIGN_RIGHT, "%ju", (uintmax_t)ncp->slots[lastslot]); \
} \
ncplane_home(ncp->ncp); \

View File

@ -1428,6 +1428,9 @@ char* notcurses_at_yx(notcurses* nc, int yoff, int xoff, uint16_t* stylemask, ui
if(yoff >= 0 && yoff < nc->lfdimy){
if(xoff >= 0 || xoff < nc->lfdimx){
const nccell* srccell = &nc->lastframe[yoff * nc->lfdimx + xoff];
if(nccell_wide_right_p(srccell)){
return notcurses_at_yx(nc, yoff, xoff - 1, stylemask, channels);
}
if(stylemask){
*stylemask = srccell->stylemask;
}

View File

@ -916,9 +916,9 @@ ncplane* ncvisual_render(notcurses* nc, ncvisual* ncv, const struct ncvisual_opt
ncvisual* ncvisual_from_plane(const ncplane* n, ncblitter_e blit, int begy, int begx,
int leny, int lenx){
uint32_t* rgba = ncplane_as_rgba(n, blit, begy, begx, leny, lenx, NULL, NULL);
int py, px;
uint32_t* rgba = ncplane_as_rgba(n, blit, begy, begx, leny, lenx, &py, &px);
//fprintf(stderr, "snarg: %d/%d @ %d/%d (%p)\n", leny, lenx, begy, begx, rgba);
//fprintf(stderr, "RGBA %p\n", rgba);
if(rgba == NULL){
return NULL;
}
@ -930,7 +930,7 @@ ncvisual* ncvisual_from_plane(const ncplane* n, ncblitter_e blit, int begy, int
if(leny == -1){
leny = (n->leny - begy);
}
ncvisual* ncv = ncvisual_from_rgba(rgba, leny * 2, lenx * 4, lenx);
ncvisual* ncv = ncvisual_from_rgba(rgba, py, px * 4, px);
free(rgba);
//fprintf(stderr, "RETURNING %p\n", ncv);
return ncv;

View File

@ -19,10 +19,8 @@ int main(void){
ncdirect_stop(nc);
return EXIT_FAILURE;
}
printf("%08x ", i);
}else{
printf(" ");
}
printf("%08x ", i);
if(ncdirect_set_styles(nc, NCSTYLE_NONE)){
ncdirect_stop(nc);
return EXIT_FAILURE;

View File

@ -89,11 +89,12 @@ TEST_CASE("Ncpp"
NotCurses nc{ nopts };
{
auto n = nc.get_stdplane();
uint64_t chan = CHANNELS_RGB_INITIALIZER(0x22, 0xdd, 0x44, 0, 0, 0);
n->set_base(" ", 0, chan);
REQUIRE(n);
// FIXME load something onto standard plane, load it into visual, erase
// plane, render visual, check for equivalence...
// FIXME load it into visual, erase plane, render visual, check for equivalence...
{
Visual v = Visual(*n, NCBLIT_DEFAULT, 0, 0, -1, -1);
Visual v = Visual(*n, NCBLIT_1x1, 0, 0, -1, -1);
}
}
CHECK(nc.stop());

View File

@ -1,6 +1,6 @@
#include "main.h"
TEST_CASE("Blitting") {
TEST_CASE("Blit") {
auto nc_ = testing_notcurses();
REQUIRE(nullptr != nc_);
ncplane* ncp_ = notcurses_stdplane(nc_);
@ -160,5 +160,122 @@ TEST_CASE("Blitting") {
}
}
// put a visual through the ascii blitter, read it back, and check equality
SUBCASE("AsciiRoundtrip") {
const uint32_t data[2] = {
htole(0xffffffff), htole(0xff000000),
};
auto ncv = ncvisual_from_rgba(data, 1, 8, 2);
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_1x1,
.flags = 0,
.transcolor = 0,
};
auto p = ncvisual_render(nc_, ncv, &vopts);
REQUIRE(nullptr != p);
CHECK(1 == ncplane_dim_y(p));
CHECK(2 == ncplane_dim_x(p));
int pxdimy, pxdimx;
auto edata = ncplane_as_rgba(p, vopts.blitter, 0, 0, -1, -1, &pxdimy, &pxdimx);
REQUIRE(nullptr != edata);
CHECK(0 == memcmp(data, edata, sizeof(data)));
free(edata);
CHECK(0 == ncplane_destroy(p));
ncvisual_destroy(ncv);
}
// put a visual through the halfblock blitter, read it back, and check equality
SUBCASE("HalfblockRoundtrip") {
if(notcurses_canutf8(nc_)){
// two rows of four pixels ought become 1 cell row of 4 cell columns
const uint32_t data[8] = {
htole(0xffffffff), htole(0xffcccccc), htole(0xffcc8844), htole(0xffdddddd),
htole(0xffcc8844), htole(0xff000000), htole(0xffffffff), htole(0xffdddddd),
};
auto ncv = ncvisual_from_rgba(data, 2, 16, 4);
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_2x1,
.flags = 0,
.transcolor = 0,
};
auto p = ncvisual_render(nc_, ncv, &vopts);
REQUIRE(nullptr != p);
CHECK(1 == ncplane_dim_y(p));
CHECK(4 == ncplane_dim_x(p));
int pxdimy, pxdimx;
auto edata = ncplane_as_rgba(p, vopts.blitter, 0, 0, -1, -1, &pxdimy, &pxdimx);
REQUIRE(nullptr != edata);
for(size_t i = 0 ; i < sizeof(data) / sizeof(*data) ; ++i){
CHECK(edata[i] == data[i]);
}
free(edata);
CHECK(0 == ncplane_destroy(p));
ncvisual_destroy(ncv);
}
}
// put a visual through the quadblitter, read it back, and check equality
SUBCASE("QuadRoundtrip") {
if(notcurses_canquadrant(nc_)){
// two rows of 32 pixels ought become 1 cell row of 16 cell columns
const uint32_t data[64] = {
htole(0xffffffff), htole(0xffffffff), htole(0xff000000), htole(0xffffffff),
htole(0xffffffff), htole(0xff000000), htole(0xffffffff), htole(0xffffffff),
htole(0xffffffff), htole(0xffffffff), htole(0xff000000), htole(0xffffffff),
htole(0xffffffff), htole(0xff000000), htole(0xffffffff), htole(0xffffffff),
htole(0xffffffff), htole(0xffffffff), htole(0xffffffff), htole(0xffffffff),
htole(0xff000000), htole(0xff000000), htole(0xff000000), htole(0xffffffff),
htole(0xffffffff), htole(0xff000000), htole(0xffffffff), htole(0xffffffff),
htole(0xffffffff), htole(0xffffffff), htole(0xffffffff), htole(0xffffffff),
htole(0xffffffff), htole(0xffffffff), htole(0xffffffff), htole(0xffffffff),
htole(0xffffffff), htole(0xffffffff), htole(0xff000000), htole(0xffffffff),
htole(0xffffffff), htole(0xff000000), htole(0xff000000), htole(0xffffffff),
htole(0xffffffff), htole(0xff000000), htole(0xffffffff), htole(0xffffffff),
htole(0xffffffff), htole(0xffffffff), htole(0xff000000), htole(0xff000000),
htole(0xffffffff), htole(0xffffffff), htole(0xffffffff), htole(0xff000000),
htole(0xff000000), htole(0xffffffff), htole(0xffffffff), htole(0xffffffff),
htole(0xffffffff), htole(0xffffffff), htole(0xffffffff), htole(0xffffffff),
};
auto ncv = ncvisual_from_rgba(data, 2, 128, 32);
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_2x2,
.flags = 0,
.transcolor = 0,
};
auto p = ncvisual_render(nc_, ncv, &vopts);
REQUIRE(nullptr != p);
CHECK(1 == ncplane_dim_y(p));
CHECK(16 == ncplane_dim_x(p));
int pxdimy, pxdimx;
auto edata = ncplane_as_rgba(p, vopts.blitter, 0, 0, -1, -1, &pxdimy, &pxdimx);
REQUIRE(nullptr != edata);
for(size_t i = 0 ; i < sizeof(data) / sizeof(*data) ; ++i){
CHECK(edata[i] == data[i]);
}
free(edata);
CHECK(0 == ncplane_destroy(p));
ncvisual_destroy(ncv);
}
}
CHECK(!notcurses_stop(nc_));
}

View File

@ -217,7 +217,7 @@ TEST_CASE("TextLayout") {
CHECK(bytes == strlen(boundstr));
char* line = ncplane_contents(sp, 0, 0, -1, -1);
REQUIRE(line);
CHECK(0 == strcmp(line, "a 血的神"));
CHECK(0 == strcmp(line, boundstr));
free(line);
ncplane_destroy(sp);
}

View File

@ -12,6 +12,7 @@ OLDVERSION="$1"
VERSION="$2"
QUIP="$3"
# finalize the date on the most recent version, add any last-minute notes?
vi NEWS.md
git clean -f -d -x