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( add_test(
NAME notcurses-tester 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) set_tests_properties(notcurses-tester PROPERTIES RUN_SERIAL TRUE)
install(TARGETS notcurses-tester DESTINATION bin) 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. only if its `ncinput` argument has no modifiers active.
* Added `notcurses_cursor_yx()` to get the current location of the cursor. * Added `notcurses_cursor_yx()` to get the current location of the cursor.
* Added `ncdirect_supported_styles()`. * 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) * 2.3.1 (2021-05-18)
* Sprixels no longer interact with their associated plane's framebuffer. This * 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); int ncplane_at_cursor_cell(struct ncplane* n, nccell* c);
// Retrieve the current contents of the specified cell. The EGC is returned, or // 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 // NULL on error. This EGC must be free()d by the caller. The stylemask and
// channels are written to 'styles' 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).
char* ncplane_at_yx(const struct ncplane* n, int y, int x, 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 // 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); 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'. // 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 sprixelated plane has no effect; if the sprixel is not even multiples of the
cell geometry, the "excess plane" is ignored during rendering. 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 # RETURN VALUES
**ncplane_create** and **ncplane_dup** return a new **struct ncplane** on **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. **notcurses_bottom** do the same for the standard pile.
**ncplane_at_yx** and **ncplane_at_cursor** return a heap-allocated copy of the **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 EGC at the relevant cell, or **NULL** if the cell is invalid. The caller should
this result. **ncplane_at_yx_cell** and **ncplane_at_cursor_cell** instead load free this result. **ncplane_at_yx_cell** and **ncplane_at_cursor_cell** instead
these values into an **nccell**, which is invalidated if the associated plane is load these values into an **nccell**, which is invalidated if the associated
destroyed. The caller should release this **nccell** with **nccell_release**. plane is destroyed. The caller should release this **nccell** with
**nccell_release**.
**ncplane_as_rgba** returns a heap-allocated array of **uint32_t** values, **ncplane_as_rgba** returns a heap-allocated array of **uint32_t** values,
each representing a single RGBA pixel, or **NULL** on failure. 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, and a default background. If the algorithm concludes without a color locked in,
the color as computed thus far is used. the color as computed thus far is used.
**notcurses_at_yx** retrieves a call *as rendered*. The EGC in that cell is **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. 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 # RETURN VALUES

View File

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

View File

@ -653,7 +653,7 @@ API int nccell_load(struct ncplane* n, nccell* c, const char* gcluster);
// nccell_load(), plus blast the styling with 'attr' and 'channels'. // nccell_load(), plus blast the styling with 'attr' and 'channels'.
static inline int static inline int
nccell_prime(struct ncplane* n, nccell* c, const char* gcluster, nccell_prime(struct ncplane* n, nccell* c, const char* gcluster,
uint32_t stylemask, uint64_t channels){ uint32_t stylemask, uint64_t channels){
c->stylemask = stylemask; c->stylemask = stylemask;
c->channels = channels; c->channels = channels;
int ret = nccell_load(n, c, gcluster); int ret = nccell_load(n, c, gcluster);
@ -748,7 +748,7 @@ nccell_wide_left_p(const nccell* c){
// return a pointer to the NUL-terminated EGC referenced by 'c'. this pointer // 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! // 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 // return the number of columns occupied by 'c'. returns -1 if passed a
// sprixcell. see ncstrwidth() for an equivalent for multiple EGCs. // 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 // 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 // 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, API char* ncplane_at_yx(const struct ncplane* n, int y, int x,
uint16_t* stylemask, uint64_t* channels); uint16_t* stylemask, uint64_t* channels);
// Retrieve the current contents of the specified cell into 'c'. This cell is // Retrieve the current contents of the specified cell into 'c'. This cell is
// invalidated if the associated plane is destroyed. Returns the number of // 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); 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 // 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 // '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 // 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. // '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); int leny, int lenx);
// Manipulate the opaque user pointer associated with this plane. // 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].* /// *C style function: [ncplane_contents()][crate::ncplane_contents].*
pub fn contents( pub fn contents(
&self, &mut self,
beg_y: NcDim, beg_y: NcDim,
beg_x: NcDim, beg_x: NcDim,
len_y: 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; fromx = (dx - dy * 2) / 2;
dx = dy * 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_2x1, fromy, fromx, dy, dx);
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);
if(!ncv){ if(!ncv){
ncvisual_destroy(ncv); ncvisual_destroy(ncv);
return -1; return -1;

View File

@ -206,7 +206,22 @@ void Ticker(ncpp::NotCurses* nc) {
int input_demo(ncpp::NotCurses* nc) { int input_demo(ncpp::NotCurses* nc) {
constexpr auto PLOTHEIGHT = 6; constexpr auto PLOTHEIGHT = 6;
auto n = nc->get_stdplane(&dimy, &dimx); 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{}; struct ncplot_options popts{};
// FIXME would be nice to switch over to exponential at some level // FIXME would be nice to switch over to exponential at some level
popts.flags = NCPLOT_OPTION_LABELTICKSD | NCPLOT_OPTION_PRINTSAMPLE; 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->set_bg_rgb8(0xbb, 0x64, 0xbb);
n->styles_on(CellStyle::Underline); n->styles_on(CellStyle::Underline);
if(n->putstr(0, NCAlign::Center, "mash keys, yo. give that mouse some waggle! ctrl+d exits.") <= 0){ if(n->putstr(0, NCAlign::Center, "mash keys, yo. give that mouse some waggle! ctrl+d exits.") <= 0){
ncuplot_destroy(plot);
return -1; return -1;
} }
n->styles_set(CellStyle::None); n->styles_set(CellStyle::None);
n->set_bg_default(); n->set_bg_default();
if(!nc->render()){ if(!nc->render()){
ncuplot_destroy(plot);
return -1; return -1;
} }
int y = 2; int y = 2;
@ -243,6 +260,7 @@ int input_demo(ncpp::NotCurses* nc) {
if((r == 'D' || r == 'd') && ni.ctrl){ if((r == 'D' || r == 'd') && ni.ctrl){
done = true; done = true;
tid.join(); tid.join();
ncuplot_destroy(plot);
return 0; return 0;
} }
if((r == 'L' || r == 'l') && ni.ctrl){ if((r == 'L' || r == 'l') && ni.ctrl){
@ -292,6 +310,7 @@ int input_demo(ncpp::NotCurses* nc) {
} }
if(!nc->render()){ if(!nc->render()){
mtx.unlock(); mtx.unlock();
ncuplot_destroy(plot);
throw std::runtime_error("error rendering"); throw std::runtime_error("error rendering");
} }
mtx.unlock(); mtx.unlock();
@ -309,6 +328,7 @@ int input_demo(ncpp::NotCurses* nc) {
} }
done = true; done = true;
tid.join(); tid.join();
ncuplot_destroy(plot);
return 0; 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 // NCBLIT_DEFAULT is not included, as it has no defined properties. It ought
// be replaced with some real blitter implementation by the calling widget. // 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[] = { 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, }, .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, }, .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, }, .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, }, .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, }, .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, }, .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, }, .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, }, .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, }, .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; const struct blitset* bset = notcurses_blitters;
while(bset->egcs){ while(bset->geom){
if(bset->geom == setid){ if(bset->geom == setid){
return bset; return bset;
} }

View File

@ -535,11 +535,14 @@ struct blitset {
ncblitter_e geom; ncblitter_e geom;
int width; // number of input pixels per output cell, width int width; // number of input pixels per output cell, width
int height; // number of input pixels per output cell, height 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 // 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 // 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. // 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. // 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; ncblitter blit;
const char* name; const char* name;
bool fill; bool fill;
@ -809,7 +812,7 @@ pool_extended_gcluster(const egcpool* pool, const nccell* c){
} }
static inline nccell* 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)]; 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 // 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 static inline int
pool_load_direct(egcpool* pool, nccell* c, const char* gcluster, int bytes, int cols){ pool_load_direct(egcpool* pool, nccell* c, const char* gcluster, int bytes, int cols){
char* rtl = NULL; 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){ 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 < n->leny && x < n->lenx){
if(y >= 0 && x >= 0){ 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; return NULL;
@ -176,6 +196,7 @@ int ncplane_at_yx_cell(ncplane* n, int y, int x, nccell* c){
if(y >= 0 && x >= 0){ if(y >= 0 && x >= 0){
nccell* targ = ncplane_cell_ref_yx(n, y, x); nccell* targ = ncplane_cell_ref_yx(n, y, x);
if(nccell_duplicate(n, c, targ) == 0){ if(nccell_duplicate(n, c, targ) == 0){
// FIXME take base cell into account where necessary!
return strlen(nccell_extended_gcluster(n, targ)); return strlen(nccell_extended_gcluster(n, targ));
} }
} }
@ -2583,9 +2604,40 @@ int ncdirect_inputready_fd(ncdirect* n){
return n->tcache.input.ttyinfd; return n->tcache.input.ttyinfd;
} }
uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit, // FIXME speed this up, PoC
int begy, int begx, int leny, int lenx, // given an egc, get its index in the blitter's EGC set
int* pxdimy, int* pxdimx){ 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); const notcurses* ncur = ncplane_notcurses_const(nc);
if(begy < 0 || begx < 0){ if(begy < 0 || begx < 0){
logerror(ncur, "Nil offset (%d,%d)\n", begy, begx); logerror(ncur, "Nil offset (%d,%d)\n", begy, begx);
@ -2611,23 +2663,31 @@ uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit,
begx, lenx, nc->lenx, begy, leny, nc->leny); begx, lenx, nc->lenx, begy, leny, nc->leny);
return NULL; return NULL;
} }
if(blit > NCBLIT_2x1){ if(blit == NCBLIT_PIXEL){ // FIXME extend this to support sprixels
logerror(ncur, "Blitter %d is not yet supported\n", blit); logerror(ncur, "Pixel blitter %d not yet supported\n", blit);
return NULL; return NULL;
} }
//fprintf(stderr, "ALLOCATING %zu %d %d\n", 4u * lenx * leny * 2, leny, lenx); if(blit == NCBLIT_DEFAULT){
// FIXME this all assumes NCBLIT_2x1, need blitter-specific scaling 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){ if(pxdimy){
*pxdimy = leny * 2; *pxdimy = leny * bset->height;
} }
if(pxdimx){ 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){ if(ret){
for(int y = begy, targy = 0 ; y < begy + leny ; ++y, targy += 2){ for(int y = begy, targy = 0 ; y < begy + leny ; ++y, targy += bset->height){
for(int x = begx, targx = 0 ; x < begx + lenx ; ++x, ++targx){ for(int x = begx, targx = 0 ; x < begx + lenx ; ++x, targx += bset->width){
// FIXME what if there's a wide glyph to the left of the selection?
uint16_t stylemask; uint16_t stylemask;
uint64_t channels; uint64_t channels;
char* c = ncplane_at_yx(nc, y, x, &stylemask, &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); free(ret);
return NULL; return NULL;
} }
uint32_t* top = &ret[targy * lenx + targx]; int idx = get_blitter_egc_idx(bset, c);
uint32_t* bot = &ret[(targy + 1) * lenx + targx]; if(idx < 0){
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);
free(ret); free(ret);
//fprintf(stderr, "bad rgba character: %s\n", c); free(c);
return NULL; 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); free(c);
} }
} }
@ -2667,8 +2739,21 @@ uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit,
return ret; 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 // 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){ if(begy < 0 || begx < 0){
logerror(ncplane_notcurses_const(nc), "Beginning coordinates (%d/%d) below 0\n", begy, begx); logerror(ncplane_notcurses_const(nc), "Beginning coordinates (%d/%d) below 0\n", begy, begx);
return NULL; return NULL;
@ -2698,18 +2783,18 @@ char* ncplane_contents(const ncplane* nc, int begy, int begx, int leny, int lenx
if(ret){ if(ret){
for(int y = begy, targy = 0 ; y < begy + leny ; ++y, targy += 2){ for(int y = begy, targy = 0 ; y < begy + leny ; ++y, targy += 2){
for(int x = begx, targx = 0 ; x < begx + lenx ; ++x, ++targx){ for(int x = begx, targx = 0 ; x < begx + lenx ; ++x, ++targx){
uint16_t stylemask; nccell ncl = CELL_TRIVIAL_INITIALIZER;
uint64_t channels; // we need ncplane_at_yx_cell() here instead of ncplane_at_yx(),
char* c = ncplane_at_yx(nc, y, x, &stylemask, &channels); // because we should only have one copy of each wide EGC.
if(!c){ int clen;
if((clen = ncplane_at_yx_cell(nc, y, x, &ncl)) < 0){
free(ret); free(ret);
return NULL; return NULL;
} }
size_t clen = strlen(c); const char* c = nccell_extended_gcluster(nc, &ncl);
if(clen){ if(clen){
char* tmp = realloc(ret, retlen + clen); char* tmp = realloc(ret, retlen + clen);
if(!tmp){ if(!tmp){
free(c);
free(ret); free(ret);
return NULL; 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); memcpy(ret + retlen - 1, c, clen);
retlen += clen; retlen += clen;
} }
free(c);
} }
} }
ret[retlen - 1] = '\0'; 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 \ direction, drawing egcs from the grid specification, aborting early if \
we can't draw anything in a given cell. */ \ we can't draw anything in a given cell. */ \
T intervalbase = ncp->miny; \ T intervalbase = ncp->miny; \
const wchar_t* egc = ncp->bset->egcs; \ const wchar_t* egc = ncp->bset->plotegcs; \
bool done = !ncp->bset->fill; \ bool done = !ncp->bset->fill; \
for(int y = 0 ; y < dimy ; ++y){ \ for(int y = 0 ; y < dimy ; ++y){ \
uint64_t channels = 0; \ uint64_t channels = 0; \
@ -204,6 +204,7 @@ int redraw_plot_##T(nc##X##plot* ncp){ \
if(ncp->printsample){ \ if(ncp->printsample){ \
int lastslot = ncp->slotstart ? ncp->slotstart - 1 : ncp->slotcount - 1; \ int lastslot = ncp->slotstart ? ncp->slotstart - 1 : ncp->slotcount - 1; \
ncplane_set_styles(ncp->ncp, ncp->legendstyle); \ 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_printf_aligned(ncp->ncp, 0, NCALIGN_RIGHT, "%ju", (uintmax_t)ncp->slots[lastslot]); \
} \ } \
ncplane_home(ncp->ncp); \ 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(yoff >= 0 && yoff < nc->lfdimy){
if(xoff >= 0 || xoff < nc->lfdimx){ if(xoff >= 0 || xoff < nc->lfdimx){
const nccell* srccell = &nc->lastframe[yoff * nc->lfdimx + xoff]; 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){ if(stylemask){
*stylemask = srccell->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, ncvisual* ncvisual_from_plane(const ncplane* n, ncblitter_e blit, int begy, int begx,
int leny, int lenx){ 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, "snarg: %d/%d @ %d/%d (%p)\n", leny, lenx, begy, begx, rgba);
//fprintf(stderr, "RGBA %p\n", rgba);
if(rgba == NULL){ if(rgba == NULL){
return NULL; return NULL;
} }
@ -930,7 +930,7 @@ ncvisual* ncvisual_from_plane(const ncplane* n, ncblitter_e blit, int begy, int
if(leny == -1){ if(leny == -1){
leny = (n->leny - begy); 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); free(rgba);
//fprintf(stderr, "RETURNING %p\n", ncv); //fprintf(stderr, "RETURNING %p\n", ncv);
return ncv; return ncv;

View File

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

View File

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

View File

@ -1,6 +1,6 @@
#include "main.h" #include "main.h"
TEST_CASE("Blitting") { TEST_CASE("Blit") {
auto nc_ = testing_notcurses(); auto nc_ = testing_notcurses();
REQUIRE(nullptr != nc_); REQUIRE(nullptr != nc_);
ncplane* ncp_ = notcurses_stdplane(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_)); CHECK(!notcurses_stop(nc_));
} }

View File

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

View File

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