mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 09:09:03 -04:00
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:
parent
d216b4115e
commit
cd2b5de5fa
@ -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
12
NEWS.md
@ -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
|
||||
|
15
USAGE.md
15
USAGE.md
@ -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'.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
* fedpkg switch-branch f32
|
||||
* git merge master
|
||||
* for each active branch:
|
||||
* fedpkg switch-branch f32
|
||||
* git merge rawhide
|
||||
|
||||
### Arch
|
||||
|
||||
|
@ -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'.
|
||||
static inline int
|
||||
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->channels = channels;
|
||||
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
|
||||
// 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.
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,9 +2604,40 @@ int ncdirect_inputready_fd(ncdirect* n){
|
||||
return n->tcache.input.ttyinfd;
|
||||
}
|
||||
|
||||
uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit,
|
||||
int begy, int begx, int leny, int lenx,
|
||||
int* pxdimy, int* pxdimx){
|
||||
// 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);
|
||||
if(begy < 0 || begx < 0){
|
||||
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);
|
||||
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';
|
||||
|
@ -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); \
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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_));
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user