From 19ff574f9cf4c28115341afe1845b0bce81d7818 Mon Sep 17 00:00:00 2001 From: Erik Schnetter Date: Fri, 20 Dec 2024 17:03:04 -0500 Subject: [PATCH 1/3] Support Unicode 16 octants --- README.md | 9 +- doc/man/man1/notcurses-info.1.md | 5 +- doc/man/man3/notcurses_capabilities.3.md | 4 + doc/man/man3/notcurses_direct.3.md | 2 + doc/man/man3/notcurses_plot.3.md | 6 +- doc/man/man3/notcurses_visual.3.md | 11 +- include/ncpp/NotCurses.hh | 5 + include/notcurses/direct.h | 6 + include/notcurses/ncseqs.h | 33 ++ include/notcurses/notcurses.h | 11 +- src/demo/keller.c | 1 + src/info/main.c | 1 + src/lib/blit.c | 481 ++++++++++++++++++++++- src/lib/blitset.h | 3 + src/poc/blitters.c | 1 + src/tests/blit.cpp | 1 + src/tests/plot.cpp | 27 ++ 17 files changed, 592 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 57a213f7cd..cb1cd7fb26 100644 --- a/README.md +++ b/README.md @@ -226,9 +226,9 @@ likely get blanks or � (U+FFFD, REPLACEMENT CHARACTER) for missing characters, and subsequent characters on the line may be misplaced. It is worth knowing that several terminals draw the block characters directly, -rather than loading them from a font. This is generally desirable. Quadrants -and sextants are not the place to demonstrate your design virtuosity. To -inspect your environment's rendering of drawing characters, run +rather than loading them from a font. This is generally desirable. Quadrants, +sextants, and octants are not the place to demonstrate your design virtuosity. +To inspect your environment's rendering of drawing characters, run `notcurses-info`. The desired output ought look something like this:

@@ -309,7 +309,8 @@ If things break or seem otherwise lackluster, **please** consult the Notcurses will not make use of bitmap protocols unless the terminal positively indicates support for them, even if NCBLIT_PIXEL has been requested. Likewise, sextants (NCBLIT_3x2) won't be used without - Unicode 13 support, etc. ncvisual_blit() will use the best blitter + Unicode 13 support, octants (NCBLIT_4x2) won't be used without + Unicode 17 support, etc. ncvisual_blit() will use the best blitter available, unless NCVISUAL_OPTION_NODEGRADE is provided (in which case it will fail). diff --git a/doc/man/man1/notcurses-info.1.md b/doc/man/man1/notcurses-info.1.md index 5d62023182..ac75451acf 100644 --- a/doc/man/man1/notcurses-info.1.md +++ b/doc/man/man1/notcurses-info.1.md @@ -17,7 +17,7 @@ terminal environment, including material loaded from **terminfo(5)** (based on the **TERM** environment variable), replies from the terminal in response to our queries, and built-in heuristics. -The Unicode half block, quadrant, sextant, and Braille glyphs are all included +The Unicode half block, quadrant, sextant, octant, and Braille glyphs are all included in the output. If their appearance is irregular, it might behoove you to choose another font. @@ -70,6 +70,7 @@ The next five lines describe properties of the terminal environment: * 2x1: Upper- and lower-half blocks are available * 2x2: Quadrant blocks are available * 3x2: Sextant blocks are available + * 4x2: Octant blocks are available * 4x2: Braille characters are available * img: Images can be decoded * vid: Video can be decoded @@ -93,7 +94,7 @@ To the right of this material is the Notcurses homepage's URI, and the Notcurses logo (the latter only if bitmap graphics are available). The final eleven lines, only printed when in a UTF8 locale, show various -Unicode glyphs. The first four lines include the quadrant, sextant, and +Unicode glyphs. The first four lines include the quadrant, sextant, octant, and box-drawing characters. The next four lines include the entire Braille set. The following two lines include many of the Symbols for Legacy Computing introduced in Unicode 13. The final line includes many emoji. diff --git a/doc/man/man3/notcurses_capabilities.3.md b/doc/man/man3/notcurses_capabilities.3.md index 1e004437f1..4957c579ca 100644 --- a/doc/man/man3/notcurses_capabilities.3.md +++ b/doc/man/man3/notcurses_capabilities.3.md @@ -34,6 +34,8 @@ notcurses_capabilities - runtime capability detection **bool notcurses_cansextant(const struct notcurses* ***nc***);** +**bool notcurses_canoctant(const struct notcurses* ***nc***);** + **bool notcurses_canbraille(const struct notcurses* ***nc***);** **bool notcurses_canpixel(const struct notcurses* ***nc***);** @@ -92,6 +94,8 @@ multimedia support capable of decoding videos. **notcurses_canutf8** returns **true** if the configured locale uses UTF-8 encoding, and the locale was successfully loaded. +**notcurses_canoctant** returns **true** if the heuristics suggest +that the terminal can properly render Unicode 17 octants. Likewise, **notcurses_cansextant** returns **true** if the heuristics suggest that the terminal can properly render Unicode 13 sextants. Likewise, **notcurses_canquadrant** and **notcurses_canhalfblock** return **true** diff --git a/doc/man/man3/notcurses_direct.3.md b/doc/man/man3/notcurses_direct.3.md index a0e84e196f..2077783229 100644 --- a/doc/man/man3/notcurses_direct.3.md +++ b/doc/man/man3/notcurses_direct.3.md @@ -114,6 +114,8 @@ notcurses_direct - the Direct Mode API **bool ncdirect_cansextant(const struct ncdirect* ***nc***);** +**bool ncdirect_canoctant(const struct ncdirect* ***nc***);** + **bool ncdirect_canbraille(const struct ncdirect* ***nc***);** **bool ncdirect_canget_cursor(const struct ncdirect* ***nc***);** diff --git a/doc/man/man3/notcurses_plot.3.md b/doc/man/man3/notcurses_plot.3.md index ad4bcf4642..fef84d58f4 100644 --- a/doc/man/man3/notcurses_plot.3.md +++ b/doc/man/man3/notcurses_plot.3.md @@ -84,9 +84,9 @@ contribute to the plot. Supplying an **x** below the current window is an error, and has no effect. More granular block glyphs means more resolution in your plots, but they can -be difficult to differentiate at small text sizes. Sextants and Braille allow -for more resolution on the independent variable. It can be difficult to predict -how the Braille glyphs will look in a given font. +be difficult to differentiate at small text sizes. Octants, sextants, and Braille +allow for more resolution on the independent variable. It can be difficult to +predict how the Braille glyphs will look in a given font. The same **ncplot_options** struct can be used with all ncplot types. The **flags** field is a bitmask composed of: diff --git a/doc/man/man3/notcurses_visual.3.md b/doc/man/man3/notcurses_visual.3.md index 6229f27f8e..039cdc1b0e 100644 --- a/doc/man/man3/notcurses_visual.3.md +++ b/doc/man/man3/notcurses_visual.3.md @@ -24,6 +24,7 @@ typedef enum { NCBLIT_2x1, // halves + 1x1 NCBLIT_2x2, // quadrants + 2x1 NCBLIT_3x2, // sextants + 1x1 + NCBLIT_4x2, // octants + quadrants + 2x1 + 1x1 NCBLIT_BRAILLE, // 4 rows, 2 cols (braille) NCBLIT_PIXEL, // pixel graphics NCBLIT_4x1, // four vertical levels, (plots) @@ -238,6 +239,7 @@ The different **ncblitter_e** values select from among available glyph sets: * **NCBLIT_2x1**: Adds the half blocks (▄▀) to **NCBLIT_1x1**. * **NCBLIT_2x2**: Adds left and right half blocks (▌▐) and quadrants (▖▗▟▙) to **NCBLIT_2x1**. * **NCBLIT_3x2**: Adds sextants to **NCBLIT_1x1**. +* **NCBLIT_4x2**: Adds octants to **NCBLIT_2x2**. * **NCBLIT_BRAILLE**: 4 rows and 2 columns of braille (⡀⡄⡆⡇⢀⣀⣄⣆⣇⢠⣠⣤⣦⣧⢰⣰⣴⣶⣷⢸⣸⣼⣾⣿). * **NCBLIT_PIXEL**: Adds pixel graphics (these also work in ASCII). @@ -343,10 +345,11 @@ or if both ***nc*** and ***n*** are **NULL**. **ncvisual_media_defblitter** returns the blitter selected by **NCBLIT_DEFAULT** in the specified configuration. If UTF8 is not enabled, this will always be -**NCBLIT_1x1**. If ***scale*** is **NCSCALE_NONE** or **NCSCALE_SCALE**, the -aspect-preserving **NCBLIT_2x1** will be returned. If sextants are available -(see **notcurses_cansextant**), this will be **NCBLIT_3x2**, or otherwise -**NCBLIT_2x2**. +**NCBLIT_1x1**. If octants are available, (see **notcurses_canictant**), the +aspect-preserving **NCBLIT_4x2** will be returned. If ***scale*** is +**NCSCALE_NONE** or **NCSCALE_SCALE**, the aspect-preserving **NCBLIT_2x1** +will be returned. If sextants are available (see **notcurses_cansextant**), +this will be **NCBLIT_3x2**, or otherwise **NCBLIT_2x2**. # NOTES diff --git a/include/ncpp/NotCurses.hh b/include/ncpp/NotCurses.hh index 92d570141e..72688fc01c 100644 --- a/include/ncpp/NotCurses.hh +++ b/include/ncpp/NotCurses.hh @@ -126,6 +126,11 @@ namespace ncpp return notcurses_cansextant (nc); } + bool can_octant () const noexcept + { + return notcurses_canoctant (nc); + } + bool can_utf8 () const noexcept { return notcurses_canutf8 (nc); diff --git a/include/notcurses/direct.h b/include/notcurses/direct.h index 4f789a934c..d7dbc3afee 100644 --- a/include/notcurses/direct.h +++ b/include/notcurses/direct.h @@ -394,6 +394,12 @@ ncdirect_cansextant(const struct ncdirect* nc){ return ncdirect_canutf8(nc) && ncdirect_capabilities(nc)->sextants; } +// Can we reliably use Unicode 16 octants? +static inline bool +ncdirect_canoctant(const struct ncdirect* nc){ + return ncdirect_canutf8(nc) && ncdirect_capabilities(nc)->octants; +} + // Can we reliably use Unicode Braille? static inline bool ncdirect_canbraille(const struct ncdirect* nc){ diff --git a/include/notcurses/ncseqs.h b/include/notcurses/ncseqs.h index a02aa7ac7d..1413909d49 100644 --- a/include/notcurses/ncseqs.h +++ b/include/notcurses/ncseqs.h @@ -56,6 +56,39 @@ extern "C" { #define NCHALFBLOCKS L" ▀▄█" #define NCQUADBLOCKS L" ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█" #define NCSEXBLOCKS L" 🬀🬁🬂🬃🬄🬅🬆🬇🬈🬊🬋🬌🬍🬎🬏🬐🬑🬒🬓▌🬔🬕🬖🬗🬘🬙🬚🬛🬜🬝🬞🬟🬠🬡🬢🬣🬤🬥🬦🬧▐🬨🬩🬪🬫🬬🬭🬮🬯🬰🬱🬲🬳🬴🬵🬶🬷🬸🬹🬺🬻█" +#define NCOCTBLOCKS \ + L" \U0001CEA8\U0001CEAB\U0001FB82\U0001CD00\U00002598\U0001CD01\U0001CD02"\ + "\U0001CD03\U0001CD04\U0000259D\U0001CD05\U0001CD06\U0001CD07\U0001CD08\U00002580"\ + "\U0001CD09\U0001CD0A\U0001CD0B\U0001CD0C\U0001FBE6\U0001CD0D\U0001CD0E\U0001CD0F"\ + "\U0001CD10\U0001CD11\U0001CD12\U0001CD13\U0001CD14\U0001CD15\U0001CD16\U0001CD17"\ + "\U0001CD18\U0001CD19\U0001CD1A\U0001CD1B\U0001CD1C\U0001CD1D\U0001CD1E\U0001CD1F"\ + "\U0001FBE7\U0001CD20\U0001CD21\U0001CD22\U0001CD23\U0001CD24\U0001CD25\U0001CD26"\ + "\U0001CD27\U0001CD28\U0001CD29\U0001CD2A\U0001CD2B\U0001CD2C\U0001CD2D\U0001CD2E"\ + "\U0001CD2F\U0001CD30\U0001CD31\U0001CD32\U0001CD33\U0001CD34\U0001CD35\U0001FB85"\ + "\U0001CEA3\U0001CD36\U0001CD37\U0001CD38\U0001CD39\U0001CD3A\U0001CD3B\U0001CD3C"\ + "\U0001CD3D\U0001CD3E\U0001CD3F\U0001CD40\U0001CD41\U0001CD42\U0001CD43\U0001CD44"\ + "\U00002596\U0001CD45\U0001CD46\U0001CD47\U0001CD48\U0000258C\U0001CD49\U0001CD4A"\ + "\U0001CD4B\U0001CD4C\U0000259E\U0001CD4D\U0001CD4E\U0001CD4F\U0001CD50\U0000259B"\ + "\U0001CD51\U0001CD52\U0001CD53\U0001CD54\U0001CD55\U0001CD56\U0001CD57\U0001CD58"\ + "\U0001CD59\U0001CD5A\U0001CD5B\U0001CD5C\U0001CD5D\U0001CD5E\U0001CD5F\U0001CD60"\ + "\U0001CD61\U0001CD62\U0001CD63\U0001CD64\U0001CD65\U0001CD66\U0001CD67\U0001CD68"\ + "\U0001CD69\U0001CD6A\U0001CD6B\U0001CD6C\U0001CD6D\U0001CD6E\U0001CD6F\U0001CD70"\ + "\U0001CEA0\U0001CD71\U0001CD72\U0001CD73\U0001CD74\U0001CD75\U0001CD76\U0001CD77"\ + "\U0001CD78\U0001CD79\U0001CD7A\U0001CD7B\U0001CD7C\U0001CD7D\U0001CD7E\U0001CD7F"\ + "\U0001CD80\U0001CD81\U0001CD82\U0001CD83\U0001CD84\U0001CD85\U0001CD86\U0001CD87"\ + "\U0001CD88\U0001CD89\U0001CD8A\U0001CD8B\U0001CD8C\U0001CD8D\U0001CD8E\U0001CD8F"\ + "\U00002597\U0001CD90\U0001CD91\U0001CD92\U0001CD93\U0000259A\U0001CD94\U0001CD95"\ + "\U0001CD96\U0001CD97\U00002590\U0001CD98\U0001CD99\U0001CD9A\U0001CD9B\U0000259C"\ + "\U0001CD9C\U0001CD9D\U0001CD9E\U0001CD9F\U0001CDA0\U0001CDA1\U0001CDA2\U0001CDA3"\ + "\U0001CDA4\U0001CDA5\U0001CDA6\U0001CDA7\U0001CDA8\U0001CDA9\U0001CDAA\U0001CDAB"\ + "\U00002582\U0001CDAC\U0001CDAD\U0001CDAE\U0001CDAF\U0001CDB0\U0001CDB1\U0001CDB2"\ + "\U0001CDB3\U0001CDB4\U0001CDB5\U0001CDB6\U0001CDB7\U0001CDB8\U0001CDB9\U0001CDBA"\ + "\U0001CDBB\U0001CDBC\U0001CDBD\U0001CDBE\U0001CDBF\U0001CDC0\U0001CDC1\U0001CDC2"\ + "\U0001CDC3\U0001CDC4\U0001CDC5\U0001CDC6\U0001CDC7\U0001CDC8\U0001CDC9\U0001CDCA"\ + "\U0001CDCB\U0001CDCC\U0001CDCD\U0001CDCE\U0001CDCF\U0001CDD0\U0001CDD1\U0001CDD2"\ + "\U0001CDD3\U0001CDD4\U0001CDD5\U0001CDD6\U0001CDD7\U0001CDD8\U0001CDD9\U0001CDDA"\ + "\U00002584\U0001CDDB\U0001CDDC\U0001CDDD\U0001CDDE\U00002599\U0001CDDF\U0001CDE0"\ + "\U0001CDE1\U0001CDE2\U0000259F\U0001CDE3\U00002586\U0001CDE4\U0001CDE5\U00002588" #define NCBRAILLEEGCS \ L"\u2800\u2801\u2808\u2809\u2802\u2803\u280a\u280b\u2810\u2811\u2818\u2819\u2812\u2813\u281a\u281b"\ "\u2804\u2805\u280c\u280d\u2806\u2807\u280e\u280f\u2814\u2815\u281c\u281d\u2816\u2817\u281e\u281f"\ diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index 7ba75da264..fd06ab1395 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -68,6 +68,7 @@ typedef enum { NCBLIT_2x1, // halves + 1x1 (space) ▄▀ NCBLIT_2x2, // quadrants + 2x1 ▗▐ ▖▀▟▌▙ NCBLIT_3x2, // sextants (*NOT* 2x2) 🬀🬁🬂🬃🬄🬅🬆🬇🬈🬉🬊🬋🬌🬍🬎🬏🬐🬑🬒🬓🬔🬕🬖🬗🬘🬙🬚🬛🬜🬝🬞 + NCBLIT_4x2, // octants NCBLIT_BRAILLE, // 4 rows, 2 cols (braille) ⡀⡄⡆⡇⢀⣀⣄⣆⣇⢠⣠⣤⣦⣧⢰⣰⣴⣶⣷⢸⣸⣼⣾⣿ NCBLIT_PIXEL, // pixel graphics // these blitters are suitable only for plots, not general media @@ -1641,6 +1642,7 @@ typedef struct nccapabilities { bool halfblocks;// we assume halfblocks, but some are known to lack them bool quadrants; // do we have (good, vetted) Unicode 1 quadrant support? bool sextants; // do we have (good, vetted) Unicode 13 sextant support? + bool octants; // do we have (good, vetted) Unicode 16 octant support? bool braille; // do we have Braille support? (linux console does not) } nccapabilities; @@ -1756,6 +1758,12 @@ notcurses_cansextant(const struct notcurses* nc){ return notcurses_canutf8(nc) && notcurses_capabilities(nc)->sextants; } +// Can we reliably use Unicode 16 octants? +__attribute__ ((nonnull (1))) __attribute__ ((pure)) static inline bool +notcurses_canoctant(const struct notcurses* nc){ + return notcurses_canutf8(nc) && notcurses_capabilities(nc)->octants; +} + // Can we reliably use Unicode Braille? __attribute__ ((nonnull (1))) __attribute__ ((pure)) static inline bool notcurses_canbraille(const struct notcurses* nc){ @@ -3510,11 +3518,12 @@ API ALLOC struct ncplane* ncvisual_subtitle_plane(struct ncplane* parent, // Get the default *media* (not plot) blitter for this environment when using // the specified scaling method. Currently, this means: // - if lacking UTF-8, NCBLIT_1x1 +// - otherwise, if octants are known to be good, NCBLIT_4x2 // - otherwise, if not NCSCALE_STRETCH, NCBLIT_2x1 // - otherwise, if sextants are not known to be good, NCBLIT_2x2 // - otherwise NCBLIT_3x2 // NCBLIT_2x2 and NCBLIT_3x2 both distort the original aspect ratio, thus -// NCBLIT_2x1 is used outside of NCSCALE_STRETCH. +// NCBLIT_4x2 or NCBLIT_2x1 is used outside of NCSCALE_STRETCH. API ncblitter_e ncvisual_media_defblitter(const struct notcurses* nc, ncscale_e scale) __attribute__ ((nonnull (1))); diff --git a/src/demo/keller.c b/src/demo/keller.c index ddd759c304..83b2ee07fd 100644 --- a/src/demo/keller.c +++ b/src/demo/keller.c @@ -16,6 +16,7 @@ visualize(struct notcurses* nc, struct ncvisual* ncv){ { NCBLIT_2x1, notcurses_canhalfblock, }, { NCBLIT_2x2, notcurses_canquadrant, }, { NCBLIT_3x2, notcurses_cansextant, }, + { NCBLIT_4x2, notcurses_canoctant, }, { NCBLIT_PIXEL, notcurses_canpixel, }, }; struct ncplane* stdn = notcurses_stdplane(nc); diff --git a/src/info/main.c b/src/info/main.c index c879f05817..d8b9631d2a 100644 --- a/src/info/main.c +++ b/src/info/main.c @@ -463,6 +463,7 @@ tinfo_debug_styles(const notcurses* nc, struct ncplane* n, const char* indent){ tinfo_debug_cap(n, "2x1", notcurses_canhalfblock(nc)); tinfo_debug_cap(n, "2x2", notcurses_canquadrant(nc)); tinfo_debug_cap(n, "3x2", notcurses_cansextant(nc)); + tinfo_debug_cap(n, "4x2", notcurses_canoctant(nc)); tinfo_debug_cap(n, "4x2", notcurses_canbraille(nc)); tinfo_debug_cap(n, "img", notcurses_canopen_images(nc)); tinfo_debug_cap(n, "vid", notcurses_canopen_videos(nc)); diff --git a/src/lib/blit.c b/src/lib/blit.c index 24e019e9f8..83efdc2161 100644 --- a/src/lib/blit.c +++ b/src/lib/blit.c @@ -711,6 +711,448 @@ sextant_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx, return total; } +// Bit is *set* where octant *is*: +// 0 1 +// 2 3 +// 4 5 +// 6 7 +// Same as NCOCTBLOCKS but as array of characters +static const char* const octant_egcs[256] = { + "\x20", + "\U0001CEA8", + "\U0001CEAB", + "\U0001FB82", + "\U0001CD00", + "\U00002598", + "\U0001CD01", + "\U0001CD02", + "\U0001CD03", + "\U0001CD04", + "\U0000259D", + "\U0001CD05", + "\U0001CD06", + "\U0001CD07", + "\U0001CD08", + "\U00002580", + "\U0001CD09", + "\U0001CD0A", + "\U0001CD0B", + "\U0001CD0C", + "\U0001FBE6", + "\U0001CD0D", + "\U0001CD0E", + "\U0001CD0F", + "\U0001CD10", + "\U0001CD11", + "\U0001CD12", + "\U0001CD13", + "\U0001CD14", + "\U0001CD15", + "\U0001CD16", + "\U0001CD17", + "\U0001CD18", + "\U0001CD19", + "\U0001CD1A", + "\U0001CD1B", + "\U0001CD1C", + "\U0001CD1D", + "\U0001CD1E", + "\U0001CD1F", + "\U0001FBE7", + "\U0001CD20", + "\U0001CD21", + "\U0001CD22", + "\U0001CD23", + "\U0001CD24", + "\U0001CD25", + "\U0001CD26", + "\U0001CD27", + "\U0001CD28", + "\U0001CD29", + "\U0001CD2A", + "\U0001CD2B", + "\U0001CD2C", + "\U0001CD2D", + "\U0001CD2E", + "\U0001CD2F", + "\U0001CD30", + "\U0001CD31", + "\U0001CD32", + "\U0001CD33", + "\U0001CD34", + "\U0001CD35", + "\U0001FB85", + "\U0001CEA3", + "\U0001CD36", + "\U0001CD37", + "\U0001CD38", + "\U0001CD39", + "\U0001CD3A", + "\U0001CD3B", + "\U0001CD3C", + "\U0001CD3D", + "\U0001CD3E", + "\U0001CD3F", + "\U0001CD40", + "\U0001CD41", + "\U0001CD42", + "\U0001CD43", + "\U0001CD44", + "\U00002596", + "\U0001CD45", + "\U0001CD46", + "\U0001CD47", + "\U0001CD48", + "\U0000258C", + "\U0001CD49", + "\U0001CD4A", + "\U0001CD4B", + "\U0001CD4C", + "\U0000259E", + "\U0001CD4D", + "\U0001CD4E", + "\U0001CD4F", + "\U0001CD50", + "\U0000259B", + "\U0001CD51", + "\U0001CD52", + "\U0001CD53", + "\U0001CD54", + "\U0001CD55", + "\U0001CD56", + "\U0001CD57", + "\U0001CD58", + "\U0001CD59", + "\U0001CD5A", + "\U0001CD5B", + "\U0001CD5C", + "\U0001CD5D", + "\U0001CD5E", + "\U0001CD5F", + "\U0001CD60", + "\U0001CD61", + "\U0001CD62", + "\U0001CD63", + "\U0001CD64", + "\U0001CD65", + "\U0001CD66", + "\U0001CD67", + "\U0001CD68", + "\U0001CD69", + "\U0001CD6A", + "\U0001CD6B", + "\U0001CD6C", + "\U0001CD6D", + "\U0001CD6E", + "\U0001CD6F", + "\U0001CD70", + "\U0001CEA0", + "\U0001CD71", + "\U0001CD72", + "\U0001CD73", + "\U0001CD74", + "\U0001CD75", + "\U0001CD76", + "\U0001CD77", + "\U0001CD78", + "\U0001CD79", + "\U0001CD7A", + "\U0001CD7B", + "\U0001CD7C", + "\U0001CD7D", + "\U0001CD7E", + "\U0001CD7F", + "\U0001CD80", + "\U0001CD81", + "\U0001CD82", + "\U0001CD83", + "\U0001CD84", + "\U0001CD85", + "\U0001CD86", + "\U0001CD87", + "\U0001CD88", + "\U0001CD89", + "\U0001CD8A", + "\U0001CD8B", + "\U0001CD8C", + "\U0001CD8D", + "\U0001CD8E", + "\U0001CD8F", + "\U00002597", + "\U0001CD90", + "\U0001CD91", + "\U0001CD92", + "\U0001CD93", + "\U0000259A", + "\U0001CD94", + "\U0001CD95", + "\U0001CD96", + "\U0001CD97", + "\U00002590", + "\U0001CD98", + "\U0001CD99", + "\U0001CD9A", + "\U0001CD9B", + "\U0000259C", + "\U0001CD9C", + "\U0001CD9D", + "\U0001CD9E", + "\U0001CD9F", + "\U0001CDA0", + "\U0001CDA1", + "\U0001CDA2", + "\U0001CDA3", + "\U0001CDA4", + "\U0001CDA5", + "\U0001CDA6", + "\U0001CDA7", + "\U0001CDA8", + "\U0001CDA9", + "\U0001CDAA", + "\U0001CDAB", + "\U00002582", + "\U0001CDAC", + "\U0001CDAD", + "\U0001CDAE", + "\U0001CDAF", + "\U0001CDB0", + "\U0001CDB1", + "\U0001CDB2", + "\U0001CDB3", + "\U0001CDB4", + "\U0001CDB5", + "\U0001CDB6", + "\U0001CDB7", + "\U0001CDB8", + "\U0001CDB9", + "\U0001CDBA", + "\U0001CDBB", + "\U0001CDBC", + "\U0001CDBD", + "\U0001CDBE", + "\U0001CDBF", + "\U0001CDC0", + "\U0001CDC1", + "\U0001CDC2", + "\U0001CDC3", + "\U0001CDC4", + "\U0001CDC5", + "\U0001CDC6", + "\U0001CDC7", + "\U0001CDC8", + "\U0001CDC9", + "\U0001CDCA", + "\U0001CDCB", + "\U0001CDCC", + "\U0001CDCD", + "\U0001CDCE", + "\U0001CDCF", + "\U0001CDD0", + "\U0001CDD1", + "\U0001CDD2", + "\U0001CDD3", + "\U0001CDD4", + "\U0001CDD5", + "\U0001CDD6", + "\U0001CDD7", + "\U0001CDD8", + "\U0001CDD9", + "\U0001CDDA", + "\U00002584", + "\U0001CDDB", + "\U0001CDDC", + "\U0001CDDD", + "\U0001CDDE", + "\U00002599", + "\U0001CDDF", + "\U0001CDE0", + "\U0001CDE1", + "\U0001CDE2", + "\U0000259F", + "\U0001CDE3", + "\U00002586", + "\U0001CDE4", + "\U0001CDE5", + "\U00002588", +}; + +// Solve for the cell rendered by this 4x2 sample. None of the input pixels may +// be transparent (that ought already have been handled). We use exhaustive +// search, which might be quite computationally intensive for the worst case +// (all eight pixels are different colors). We want to solve for the 2-partition +// of pixels that minimizes total source distance from the resulting lerps. +static const char* +oct_solver(const uint32_t rgbas[8], uint64_t* channels, unsigned blendcolors, + unsigned nointerpolate){ + // Each element within the set of 256 has an inverse element within + // the set, for which we would calculate the same total differences, + // so just handle the first 128. + // + // We loop over the bitstrings, dividing the pixels into two sets, + // and then taking a general lerp over each set. we then compute the + // sum of absolute differences, and see if it's the new minimum. + int best = -1; + uint32_t mindiff = UINT_MAX; + for(size_t glyph = 0; glyph < 128; ++glyph){ + unsigned rsum0 = 0, rsum1 = 0; + unsigned gsum0 = 0, gsum1 = 0; + unsigned bsum0 = 0, bsum1 = 0; + int insum = 0; + int outsum = 0; + for(unsigned mask = 0 ; mask < 8 ; ++mask){ + if(glyph & (1u << mask)){ + if(!nointerpolate || !insum){ + rsum0 += ncpixel_r(rgbas[mask]); + gsum0 += ncpixel_g(rgbas[mask]); + bsum0 += ncpixel_b(rgbas[mask]); + ++insum; + } + }else{ + if(!nointerpolate || !outsum){ + rsum1 += ncpixel_r(rgbas[mask]); + gsum1 += ncpixel_g(rgbas[mask]); + bsum1 += ncpixel_b(rgbas[mask]); + ++outsum; + } + } + } + uint32_t l0 = generalerp(rsum0, gsum0, bsum0, insum); + uint32_t l1 = generalerp(rsum1, gsum1, bsum1, outsum); + uint32_t totaldiff = 0; + for(unsigned mask = 0 ; mask < 8 ; ++mask){ + unsigned r, g, b; + if(glyph & (1u << mask)){ + ncchannel_rgb8(l0, &r, &g, &b); + }else{ + ncchannel_rgb8(l1, &r, &g, &b); + } + uint32_t rdiff = rgb_diff(ncpixel_r(rgbas[mask]), ncpixel_g(rgbas[mask]), + ncpixel_b(rgbas[mask]), r, g, b); + totaldiff += rdiff; + } + if(totaldiff < mindiff){ + mindiff = totaldiff; + best = glyph; + ncchannels_set_fchannel(channels, l0); + ncchannels_set_bchannel(channels, l1); + } + if(totaldiff == 0){ // can't beat that! + break; + } + } + assert(best >= 0 && best < 128); + if(blendcolors){ + ncchannels_set_fg_alpha(channels, NCALPHA_BLEND); + ncchannels_set_bg_alpha(channels, NCALPHA_BLEND); + } + return octant_egcs[best]; +} + +static const char* +oct_trans_check(nccell* c, const uint32_t rgbas[8], unsigned blendcolors, + uint32_t transcolor, unsigned nointerpolate){ + unsigned transstring = 0; + unsigned r = 0, g = 0, b = 0; + unsigned div = 0; + for(unsigned mask = 0 ; mask < 8 ; ++mask){ + if(rgba_trans_p(rgbas[mask], transcolor)){ + transstring |= (1u << mask); + }else if(!nointerpolate || !div){ + r += ncpixel_r(rgbas[mask]); + g += ncpixel_g(rgbas[mask]); + b += ncpixel_b(rgbas[mask]); + ++div; + } + } + if(transstring == 0){ // there was no transparency + return NULL; + } + nccell_set_bg_alpha(c, NCALPHA_TRANSPARENT); + // there were some transparent pixels. since they get priority, the foreground + // is just a general lerp across non-transparent pixels. + const char* egc = octant_egcs[transstring ^ 0xff]; + nccell_set_bg_alpha(c, NCALPHA_TRANSPARENT); +//fprintf(stderr, "transtring: %u egc: %s\n", transtring, egc); + if(*egc == ' '){ // entirely transparent + nccell_set_fg_alpha(c, NCALPHA_TRANSPARENT); + return ""; + }else{ // partially transparent, thus div >= 1 +//fprintf(stderr, "div: %u r: %u g: %u b: %u\n", div, r, g, b); + cell_set_fchannel(c, generalerp(r, g, b, div)); + if(blendcolors){ + nccell_set_fg_alpha(c, NCALPHA_BLEND); + } + cell_set_blitquadrants(c, !(transstring & 5u), !(transstring & 10u), + !(transstring & 20u), !(transstring & 40u)); + } +//fprintf(stderr, "OCT-BQ: 0x%x\n", cell_blittedquadrants(c)); + return egc; +} + +// octant blitter. maps 4x2 to each cell. since we only have two colors at +// our disposal (foreground and background), we lose some fidelity. +static inline int +octant_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx, + const blitterargs* bargs){ + const unsigned nointerpolate = bargs->flags & NCVISUAL_OPTION_NOINTERPOLATE; + const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND; + unsigned dimy, dimx, x, y; + int total = 0; // number of cells written + ncplane_dim_yx(nc, &dimy, &dimx); +//fprintf(stderr, "octblitter %dx%d -> %d/%d+%d/%d\n", leny, lenx, dimy, dimx, bargs->u.cell.placey, bargs->u.cell.placex); + const unsigned char* dat = data; + int visy = bargs->begy; + for(y = bargs->u.cell.placey ; visy < (bargs->begy + leny) && y < dimy ; ++y, visy += 4){ + if(ncplane_cursor_move_yx(nc, y, bargs->u.cell.placex < 0 ? 0 : bargs->u.cell.placex)){ + return -1; + } + int visx = bargs->begx; + for(x = bargs->u.cell.placex ; visx < (bargs->begx + lenx) && x < dimx ; ++x, visx += 2){ + uint32_t rgbas[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + memcpy(&rgbas[0], (dat + (linesize * visy) + (visx * 4)), sizeof(*rgbas)); + if(visx < bargs->begx + lenx - 1){ + memcpy(&rgbas[1], (dat + (linesize * visy) + ((visx + 1) * 4)), sizeof(*rgbas)); + if(visy < bargs->begy + leny - 1){ + memcpy(&rgbas[3], (dat + (linesize * (visy + 1)) + ((visx + 1) * 4)), sizeof(*rgbas)); + if(visy < bargs->begy + leny - 2){ + memcpy(&rgbas[5], (dat + (linesize * (visy + 2)) + ((visx + 1) * 4)), sizeof(*rgbas)); + if(visy < bargs->begy + leny - 3){ + memcpy(&rgbas[7], (dat + (linesize * (visy + 3)) + ((visx + 1) * 4)), sizeof(*rgbas)); + } + } + } + } + if(visy < bargs->begy + leny - 1){ + memcpy(&rgbas[2], (dat + (linesize * (visy + 1)) + (visx * 4)), sizeof(*rgbas)); + if(visy < bargs->begy + leny - 2){ + memcpy(&rgbas[4], (dat + (linesize * (visy + 2)) + (visx * 4)), sizeof(*rgbas)); + if(visy < bargs->begy + leny - 3){ + memcpy(&rgbas[6], (dat + (linesize * (visy + 3)) + (visx * 4)), sizeof(*rgbas)); + } + } + } + nccell* c = ncplane_cell_ref_yx(nc, y, x); + c->channels = 0; + c->stylemask = 0; + const char* egc = oct_trans_check(c, rgbas, blendcolors, bargs->transcolor, nointerpolate); + if(egc == NULL){ // no transparency; run a full solver + egc = oct_solver(rgbas, &c->channels, blendcolors, nointerpolate); + cell_set_blitquadrants(c, 1, 1, 1, 1); + } +//fprintf(stderr, "oct EGC: %s channels: %016lx\n", egc, c->channels); + if(*egc){ + if(pool_blit_direct(&nc->pool, c, egc, strlen(egc), 1) <= 0){ + return -1; + } + ++total; + }else{ + nccell_release(nc, c); + } + } + } + return total; +} + // fold the r, g, and b components of the pixel into *r, *g, and *b, and // increment *foldcount static inline void @@ -867,6 +1309,34 @@ static struct blitset notcurses_blitters[] = { { .geom = NCBLIT_3x2, .width = 2, .height = 3, .egcs = NCSEXBLOCKS, .plotegcs = L" 🬞🬦▐🬏🬭🬵🬷🬓🬱🬹🬻▌🬲🬺█", .blit = sextant_blit, .name = "sex", .fill = false, }, + { .geom = NCBLIT_4x2, .width = 2, .height = 4, + .egcs = NCOCTBLOCKS, + .plotegcs = (L"\0x20" + L"\U0001cea0" + L"\U00002597" + L"\U0001CD96" + L"\U0001CD91" + L"\U0001CEA3" + L"\U00002582" + L"\U0001CDCB" + L"\U0001CDD3" + L"\U0001CDCD" + L"\U00002596" + L"\U0001CDBB" + L"\U00002584" + L"\U0001CDE1" + L"\U0001CDDC" + L"\U0001CD48" + L"\U0001CDBF" + L"\U0001CDDE" + L"\U00002586" + L"\U0001CDDF" + L"\U0000258C" + L"\U0001CDC0" + L"\U00002599" + L"\U0001CDE4" + L"\U0001CDE0"), + .blit = octant_blit, .name = "oct", .fill = false, }, { .geom = NCBLIT_BRAILLE, .width = 2, .height = 4, .egcs = NCBRAILLEEGCS, .plotegcs = L"⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿", @@ -898,13 +1368,22 @@ const struct blitset* lookup_blitset(const tinfo* tcache, ncblitter_e setid, if(setid == NCBLIT_DEFAULT){ // ought have resolved NCBLIT_DEFAULT before now return NULL; } - // without braille support, NCBLIT_BRAILLE decays to NCBLIT_3x2 + // without braille support, NCBLIT_BRAILLE decays to NCBLIT_4x2 if(setid == NCBLIT_BRAILLE){ if(tcache->caps.braille){ return ¬curses_blitters[setid - 1]; }else if(!may_degrade){ return NULL; } + setid = NCBLIT_4x2; + } + // without octant support, NCBLIT_4x2 decays to NCBLIT_3x2 + if(setid == NCBLIT_4x2){ + if(tcache->caps.octants){ + return ¬curses_blitters[setid - 1]; + }else if(!may_degrade){ + return NULL; + } setid = NCBLIT_3x2; } // without bitmap support, NCBLIT_PIXEL decays to NCBLIT_3x2 diff --git a/src/lib/blitset.h b/src/lib/blitset.h index 69ed55e3cf..9dcf9ffdf8 100644 --- a/src/lib/blitset.h +++ b/src/lib/blitset.h @@ -32,6 +32,9 @@ rgba_blitter_default(const tinfo* tcache, ncscale_e scale){ if(!tcache->caps.utf8){ return NCBLIT_1x1; // only one that works in ASCII } + if(tcache->caps.octants){ + return NCBLIT_4x2; + } if(scale == NCSCALE_NONE || scale == NCSCALE_SCALE){ return NCBLIT_2x1; } diff --git a/src/poc/blitters.c b/src/poc/blitters.c index ca383c46d3..0b6f6f7986 100644 --- a/src/poc/blitters.c +++ b/src/poc/blitters.c @@ -28,6 +28,7 @@ int main(int argc, char** argv){ NCBLIT_2x1, // full/(upper|left) blocks NCBLIT_2x2, // quadrants NCBLIT_3x2, // sextants + NCBLIT_4x2, // octants NCBLIT_BRAILLE, // 4 rows, 2 cols (braille) NCBLIT_PIXEL, // pixel graphics -1, diff --git a/src/tests/blit.cpp b/src/tests/blit.cpp index 727ebf741e..41aade6ff9 100644 --- a/src/tests/blit.cpp +++ b/src/tests/blit.cpp @@ -10,6 +10,7 @@ TEST_CASE("Blit") { SUBCASE("BlitterStrings") { CHECK(0 == strcmp("pixel", notcurses_str_blitter(NCBLIT_PIXEL))); + CHECK(0 == strcmp("oct", notcurses_str_blitter(NCBLIT_4x2))); CHECK(0 == strcmp("sex", notcurses_str_blitter(NCBLIT_3x2))); CHECK(0 == strcmp("quad", notcurses_str_blitter(NCBLIT_2x2))); CHECK(0 == strcmp("half", notcurses_str_blitter(NCBLIT_2x1))); diff --git a/src/tests/plot.cpp b/src/tests/plot.cpp index cc0f2e203a..77bcf57baa 100644 --- a/src/tests/plot.cpp +++ b/src/tests/plot.cpp @@ -256,6 +256,33 @@ TEST_CASE("Plot") { ncuplot_destroy(p); } + SUBCASE("Octantlot1Row") { + ncplane_options nopts = { + .y = 1, .x = 1, .rows = 1, .cols = 25, + .userptr = nullptr, .name = "plot", .resizecb = nullptr, .flags = 0, + .margin_b = 0, .margin_r = 0, + }; + auto ncp = ncplane_create(n_, &nopts); + REQUIRE(ncp); + ncplot_options popts; + memset(&popts, 0, sizeof(popts)); + popts.maxchannels = NCCHANNELS_INITIALIZER(0xff, 0xff, 0xff, 0, 0, 0); + popts.minchannels = NCCHANNELS_INITIALIZER(0, 0xff, 0, 0, 0, 0); + ncchannels_set_bg_alpha(&popts.minchannels, NCALPHA_BLEND); + ncchannels_set_fg_alpha(&popts.minchannels, NCALPHA_BLEND); + popts.gridtype = NCBLIT_4x2; + auto p = ncuplot_create(ncp, &popts, 0, 0); + REQUIRE(p); + for(auto i = 0 ; i < 5 ; ++i){ + for(auto j = 0 ; j < 5 ; ++j){ + CHECK(0 == ncuplot_add_sample(p, i * 10 + j * 2, i)); + CHECK(0 == ncuplot_add_sample(p, i * 10 + j * 2 + 1, j)); + } + } + CHECK(0 == notcurses_render(nc_)); + ncuplot_destroy(p); + } + SUBCASE("BraillePlot1Row") { ncplane_options nopts = { .y = 1, .x = 1, .rows = 1, .cols = 25, From 3ee3dc06ce7bc50d8148c612b8a78845672ebf0b Mon Sep 17 00:00:00 2001 From: Erik Schnetter Date: Sun, 22 Dec 2024 16:37:31 -0500 Subject: [PATCH 2/3] Correct mistakes, update default blitter choice --- README.md | 2 +- doc/man/man3/notcurses_capabilities.3.md | 4 ++-- doc/man/man3/notcurses_visual.3.md | 14 ++++++++------ include/notcurses/notcurses.h | 4 ++-- src/lib/blit.c | 2 +- src/lib/blitset.h | 6 +++--- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index cb1cd7fb26..bcae9b80b6 100644 --- a/README.md +++ b/README.md @@ -310,7 +310,7 @@ If things break or seem otherwise lackluster, **please** consult the indicates support for them, even if NCBLIT_PIXEL has been requested. Likewise, sextants (NCBLIT_3x2) won't be used without Unicode 13 support, octants (NCBLIT_4x2) won't be used without - Unicode 17 support, etc. ncvisual_blit() will use the best blitter + Unicode 16 support, etc. ncvisual_blit() will use the best blitter available, unless NCVISUAL_OPTION_NODEGRADE is provided (in which case it will fail). diff --git a/doc/man/man3/notcurses_capabilities.3.md b/doc/man/man3/notcurses_capabilities.3.md index 4957c579ca..7493c3d589 100644 --- a/doc/man/man3/notcurses_capabilities.3.md +++ b/doc/man/man3/notcurses_capabilities.3.md @@ -95,9 +95,9 @@ multimedia support capable of decoding videos. UTF-8 encoding, and the locale was successfully loaded. **notcurses_canoctant** returns **true** if the heuristics suggest -that the terminal can properly render Unicode 17 octants. Likewise, +that the terminal can properly render Unicode 16 octants. **notcurses_cansextant** returns **true** if the heuristics suggest -that the terminal can properly render Unicode 13 sextants. Likewise, +that the terminal can properly render Unicode 13 sextants, and **notcurses_canquadrant** and **notcurses_canhalfblock** return **true** if the heuristics suggest that the terminal can properly render Unicode quadrants and halfblocks, respectively. **notcurses_canbraille** returns diff --git a/doc/man/man3/notcurses_visual.3.md b/doc/man/man3/notcurses_visual.3.md index 039cdc1b0e..1712bf7442 100644 --- a/doc/man/man3/notcurses_visual.3.md +++ b/doc/man/man3/notcurses_visual.3.md @@ -345,11 +345,12 @@ or if both ***nc*** and ***n*** are **NULL**. **ncvisual_media_defblitter** returns the blitter selected by **NCBLIT_DEFAULT** in the specified configuration. If UTF8 is not enabled, this will always be -**NCBLIT_1x1**. If octants are available, (see **notcurses_canictant**), the -aspect-preserving **NCBLIT_4x2** will be returned. If ***scale*** is -**NCSCALE_NONE** or **NCSCALE_SCALE**, the aspect-preserving **NCBLIT_2x1** -will be returned. If sextants are available (see **notcurses_cansextant**), -this will be **NCBLIT_3x2**, or otherwise **NCBLIT_2x2**. +**NCBLIT_1x1**. If ***scale*** is **NCSCALE_NONE** or **NCSCALE_SCALE**, the +aspect-preserving **NCBLIT_2x1** will be returned. If octants are +available (see **notcurses_canoctant**), this will be **NCBLIT_4x2**, +or otherwise, if sextants are available (see +**notcurses_cansextant**), this will be **NCBLIT_3x2**, or otherwise +**NCBLIT_2x2**. # NOTES @@ -362,7 +363,8 @@ a multimedia backend include **ncvisual_from_file** and Sixel documentation can be found at [Dankwiki](https://nick-black.com/dankwiki/index.php?title=Sixel). Kitty's graphics protocol is specified in [its documentation](https://sw.kovidgoyal.net/kitty/graphics-protocol.html). -Bad font support can ruin **NCBLIT_2x2**, **NCBLIT_3x2**, **NCBLIT_4x1**, +Bad font support can ruin **NCBLIT_2x2**, **NCBLIT_4x2**, **NCBLIT_3x2**, +**NCBLIT_4x1**, **NCBLIT_BRAILLE**, and **NCBLIT_8x1**. Braille glyphs ought ideally draw only the raised dots, rather than drawing all eight dots with two different styles. It's often best for the emulator to draw these glyphs itself. diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index fd06ab1395..99998701c6 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -3518,12 +3518,12 @@ API ALLOC struct ncplane* ncvisual_subtitle_plane(struct ncplane* parent, // Get the default *media* (not plot) blitter for this environment when using // the specified scaling method. Currently, this means: // - if lacking UTF-8, NCBLIT_1x1 -// - otherwise, if octants are known to be good, NCBLIT_4x2 // - otherwise, if not NCSCALE_STRETCH, NCBLIT_2x1 +// - otherwise, if octants are known to be good, NCBLIT_4x2 // - otherwise, if sextants are not known to be good, NCBLIT_2x2 // - otherwise NCBLIT_3x2 // NCBLIT_2x2 and NCBLIT_3x2 both distort the original aspect ratio, thus -// NCBLIT_4x2 or NCBLIT_2x1 is used outside of NCSCALE_STRETCH. +// NCBLIT_2x1 is used outside of NCSCALE_STRETCH. API ncblitter_e ncvisual_media_defblitter(const struct notcurses* nc, ncscale_e scale) __attribute__ ((nonnull (1))); diff --git a/src/lib/blit.c b/src/lib/blit.c index 83efdc2161..24f8574dfb 100644 --- a/src/lib/blit.c +++ b/src/lib/blit.c @@ -1220,7 +1220,7 @@ braille_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx, // 1 4 // 2 5 // 3 6 - // 4 7 + // 7 8 // FIXME fold this into the above? if(!rgba_trans_p(*rgbbase_l0, bargs->transcolor)){ egcidx |= 1u; diff --git a/src/lib/blitset.h b/src/lib/blitset.h index 9dcf9ffdf8..554b54a50b 100644 --- a/src/lib/blitset.h +++ b/src/lib/blitset.h @@ -32,12 +32,12 @@ rgba_blitter_default(const tinfo* tcache, ncscale_e scale){ if(!tcache->caps.utf8){ return NCBLIT_1x1; // only one that works in ASCII } - if(tcache->caps.octants){ - return NCBLIT_4x2; - } if(scale == NCSCALE_NONE || scale == NCSCALE_SCALE){ return NCBLIT_2x1; } + if(tcache->caps.octants){ + return NCBLIT_4x2; + } if(tcache->caps.sextants){ return NCBLIT_3x2; } From ede060d483bdd3aa170893cc6c74981d6ff90d39 Mon Sep 17 00:00:00 2001 From: Erik Schnetter Date: Mon, 23 Dec 2024 10:15:40 -0500 Subject: [PATCH 3/3] Implement generic 4x2 blitter --- src/lib/blit.c | 579 ++++++++++--------------------------------------- 1 file changed, 114 insertions(+), 465 deletions(-) diff --git a/src/lib/blit.c b/src/lib/blit.c index 24f8574dfb..f77424f69e 100644 --- a/src/lib/blit.c +++ b/src/lib/blit.c @@ -711,447 +711,87 @@ sextant_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx, return total; } -// Bit is *set* where octant *is*: +// Bit is set where octant is present: // 0 1 // 2 3 // 4 5 // 6 7 -// Same as NCOCTBLOCKS but as array of characters -static const char* const octant_egcs[256] = { - "\x20", - "\U0001CEA8", - "\U0001CEAB", - "\U0001FB82", - "\U0001CD00", - "\U00002598", - "\U0001CD01", - "\U0001CD02", - "\U0001CD03", - "\U0001CD04", - "\U0000259D", - "\U0001CD05", - "\U0001CD06", - "\U0001CD07", - "\U0001CD08", - "\U00002580", - "\U0001CD09", - "\U0001CD0A", - "\U0001CD0B", - "\U0001CD0C", - "\U0001FBE6", - "\U0001CD0D", - "\U0001CD0E", - "\U0001CD0F", - "\U0001CD10", - "\U0001CD11", - "\U0001CD12", - "\U0001CD13", - "\U0001CD14", - "\U0001CD15", - "\U0001CD16", - "\U0001CD17", - "\U0001CD18", - "\U0001CD19", - "\U0001CD1A", - "\U0001CD1B", - "\U0001CD1C", - "\U0001CD1D", - "\U0001CD1E", - "\U0001CD1F", - "\U0001FBE7", - "\U0001CD20", - "\U0001CD21", - "\U0001CD22", - "\U0001CD23", - "\U0001CD24", - "\U0001CD25", - "\U0001CD26", - "\U0001CD27", - "\U0001CD28", - "\U0001CD29", - "\U0001CD2A", - "\U0001CD2B", - "\U0001CD2C", - "\U0001CD2D", - "\U0001CD2E", - "\U0001CD2F", - "\U0001CD30", - "\U0001CD31", - "\U0001CD32", - "\U0001CD33", - "\U0001CD34", - "\U0001CD35", - "\U0001FB85", - "\U0001CEA3", - "\U0001CD36", - "\U0001CD37", - "\U0001CD38", - "\U0001CD39", - "\U0001CD3A", - "\U0001CD3B", - "\U0001CD3C", - "\U0001CD3D", - "\U0001CD3E", - "\U0001CD3F", - "\U0001CD40", - "\U0001CD41", - "\U0001CD42", - "\U0001CD43", - "\U0001CD44", - "\U00002596", - "\U0001CD45", - "\U0001CD46", - "\U0001CD47", - "\U0001CD48", - "\U0000258C", - "\U0001CD49", - "\U0001CD4A", - "\U0001CD4B", - "\U0001CD4C", - "\U0000259E", - "\U0001CD4D", - "\U0001CD4E", - "\U0001CD4F", - "\U0001CD50", - "\U0000259B", - "\U0001CD51", - "\U0001CD52", - "\U0001CD53", - "\U0001CD54", - "\U0001CD55", - "\U0001CD56", - "\U0001CD57", - "\U0001CD58", - "\U0001CD59", - "\U0001CD5A", - "\U0001CD5B", - "\U0001CD5C", - "\U0001CD5D", - "\U0001CD5E", - "\U0001CD5F", - "\U0001CD60", - "\U0001CD61", - "\U0001CD62", - "\U0001CD63", - "\U0001CD64", - "\U0001CD65", - "\U0001CD66", - "\U0001CD67", - "\U0001CD68", - "\U0001CD69", - "\U0001CD6A", - "\U0001CD6B", - "\U0001CD6C", - "\U0001CD6D", - "\U0001CD6E", - "\U0001CD6F", - "\U0001CD70", - "\U0001CEA0", - "\U0001CD71", - "\U0001CD72", - "\U0001CD73", - "\U0001CD74", - "\U0001CD75", - "\U0001CD76", - "\U0001CD77", - "\U0001CD78", - "\U0001CD79", - "\U0001CD7A", - "\U0001CD7B", - "\U0001CD7C", - "\U0001CD7D", - "\U0001CD7E", - "\U0001CD7F", - "\U0001CD80", - "\U0001CD81", - "\U0001CD82", - "\U0001CD83", - "\U0001CD84", - "\U0001CD85", - "\U0001CD86", - "\U0001CD87", - "\U0001CD88", - "\U0001CD89", - "\U0001CD8A", - "\U0001CD8B", - "\U0001CD8C", - "\U0001CD8D", - "\U0001CD8E", - "\U0001CD8F", - "\U00002597", - "\U0001CD90", - "\U0001CD91", - "\U0001CD92", - "\U0001CD93", - "\U0000259A", - "\U0001CD94", - "\U0001CD95", - "\U0001CD96", - "\U0001CD97", - "\U00002590", - "\U0001CD98", - "\U0001CD99", - "\U0001CD9A", - "\U0001CD9B", - "\U0000259C", - "\U0001CD9C", - "\U0001CD9D", - "\U0001CD9E", - "\U0001CD9F", - "\U0001CDA0", - "\U0001CDA1", - "\U0001CDA2", - "\U0001CDA3", - "\U0001CDA4", - "\U0001CDA5", - "\U0001CDA6", - "\U0001CDA7", - "\U0001CDA8", - "\U0001CDA9", - "\U0001CDAA", - "\U0001CDAB", - "\U00002582", - "\U0001CDAC", - "\U0001CDAD", - "\U0001CDAE", - "\U0001CDAF", - "\U0001CDB0", - "\U0001CDB1", - "\U0001CDB2", - "\U0001CDB3", - "\U0001CDB4", - "\U0001CDB5", - "\U0001CDB6", - "\U0001CDB7", - "\U0001CDB8", - "\U0001CDB9", - "\U0001CDBA", - "\U0001CDBB", - "\U0001CDBC", - "\U0001CDBD", - "\U0001CDBE", - "\U0001CDBF", - "\U0001CDC0", - "\U0001CDC1", - "\U0001CDC2", - "\U0001CDC3", - "\U0001CDC4", - "\U0001CDC5", - "\U0001CDC6", - "\U0001CDC7", - "\U0001CDC8", - "\U0001CDC9", - "\U0001CDCA", - "\U0001CDCB", - "\U0001CDCC", - "\U0001CDCD", - "\U0001CDCE", - "\U0001CDCF", - "\U0001CDD0", - "\U0001CDD1", - "\U0001CDD2", - "\U0001CDD3", - "\U0001CDD4", - "\U0001CDD5", - "\U0001CDD6", - "\U0001CDD7", - "\U0001CDD8", - "\U0001CDD9", - "\U0001CDDA", - "\U00002584", - "\U0001CDDB", - "\U0001CDDC", - "\U0001CDDD", - "\U0001CDDE", - "\U00002599", - "\U0001CDDF", - "\U0001CDE0", - "\U0001CDE1", - "\U0001CDE2", - "\U0000259F", - "\U0001CDE3", - "\U00002586", - "\U0001CDE4", - "\U0001CDE5", - "\U00002588", +// Same as NCOCTBLOCKS but as array of fixed-width strings +static const char octant_egcs[256][5] = { + "\x20", "\U0001CEA8", "\U0001CEAB", "\U0001FB82", "\U0001CD00", "\U00002598", "\U0001CD01", "\U0001CD02", + "\U0001CD03", "\U0001CD04", "\U0000259D", "\U0001CD05", "\U0001CD06", "\U0001CD07", "\U0001CD08", "\U00002580", + "\U0001CD09", "\U0001CD0A", "\U0001CD0B", "\U0001CD0C", "\U0001FBE6", "\U0001CD0D", "\U0001CD0E", "\U0001CD0F", + "\U0001CD10", "\U0001CD11", "\U0001CD12", "\U0001CD13", "\U0001CD14", "\U0001CD15", "\U0001CD16", "\U0001CD17", + "\U0001CD18", "\U0001CD19", "\U0001CD1A", "\U0001CD1B", "\U0001CD1C", "\U0001CD1D", "\U0001CD1E", "\U0001CD1F", + "\U0001FBE7", "\U0001CD20", "\U0001CD21", "\U0001CD22", "\U0001CD23", "\U0001CD24", "\U0001CD25", "\U0001CD26", + "\U0001CD27", "\U0001CD28", "\U0001CD29", "\U0001CD2A", "\U0001CD2B", "\U0001CD2C", "\U0001CD2D", "\U0001CD2E", + "\U0001CD2F", "\U0001CD30", "\U0001CD31", "\U0001CD32", "\U0001CD33", "\U0001CD34", "\U0001CD35", "\U0001FB85", + "\U0001CEA3", "\U0001CD36", "\U0001CD37", "\U0001CD38", "\U0001CD39", "\U0001CD3A", "\U0001CD3B", "\U0001CD3C", + "\U0001CD3D", "\U0001CD3E", "\U0001CD3F", "\U0001CD40", "\U0001CD41", "\U0001CD42", "\U0001CD43", "\U0001CD44", + "\U00002596", "\U0001CD45", "\U0001CD46", "\U0001CD47", "\U0001CD48", "\U0000258C", "\U0001CD49", "\U0001CD4A", + "\U0001CD4B", "\U0001CD4C", "\U0000259E", "\U0001CD4D", "\U0001CD4E", "\U0001CD4F", "\U0001CD50", "\U0000259B", + "\U0001CD51", "\U0001CD52", "\U0001CD53", "\U0001CD54", "\U0001CD55", "\U0001CD56", "\U0001CD57", "\U0001CD58", + "\U0001CD59", "\U0001CD5A", "\U0001CD5B", "\U0001CD5C", "\U0001CD5D", "\U0001CD5E", "\U0001CD5F", "\U0001CD60", + "\U0001CD61", "\U0001CD62", "\U0001CD63", "\U0001CD64", "\U0001CD65", "\U0001CD66", "\U0001CD67", "\U0001CD68", + "\U0001CD69", "\U0001CD6A", "\U0001CD6B", "\U0001CD6C", "\U0001CD6D", "\U0001CD6E", "\U0001CD6F", "\U0001CD70", + "\U0001CEA0", "\U0001CD71", "\U0001CD72", "\U0001CD73", "\U0001CD74", "\U0001CD75", "\U0001CD76", "\U0001CD77", + "\U0001CD78", "\U0001CD79", "\U0001CD7A", "\U0001CD7B", "\U0001CD7C", "\U0001CD7D", "\U0001CD7E", "\U0001CD7F", + "\U0001CD80", "\U0001CD81", "\U0001CD82", "\U0001CD83", "\U0001CD84", "\U0001CD85", "\U0001CD86", "\U0001CD87", + "\U0001CD88", "\U0001CD89", "\U0001CD8A", "\U0001CD8B", "\U0001CD8C", "\U0001CD8D", "\U0001CD8E", "\U0001CD8F", + "\U00002597", "\U0001CD90", "\U0001CD91", "\U0001CD92", "\U0001CD93", "\U0000259A", "\U0001CD94", "\U0001CD95", + "\U0001CD96", "\U0001CD97", "\U00002590", "\U0001CD98", "\U0001CD99", "\U0001CD9A", "\U0001CD9B", "\U0000259C", + "\U0001CD9C", "\U0001CD9D", "\U0001CD9E", "\U0001CD9F", "\U0001CDA0", "\U0001CDA1", "\U0001CDA2", "\U0001CDA3", + "\U0001CDA4", "\U0001CDA5", "\U0001CDA6", "\U0001CDA7", "\U0001CDA8", "\U0001CDA9", "\U0001CDAA", "\U0001CDAB", + "\U00002582", "\U0001CDAC", "\U0001CDAD", "\U0001CDAE", "\U0001CDAF", "\U0001CDB0", "\U0001CDB1", "\U0001CDB2", + "\U0001CDB3", "\U0001CDB4", "\U0001CDB5", "\U0001CDB6", "\U0001CDB7", "\U0001CDB8", "\U0001CDB9", "\U0001CDBA", + "\U0001CDBB", "\U0001CDBC", "\U0001CDBD", "\U0001CDBE", "\U0001CDBF", "\U0001CDC0", "\U0001CDC1", "\U0001CDC2", + "\U0001CDC3", "\U0001CDC4", "\U0001CDC5", "\U0001CDC6", "\U0001CDC7", "\U0001CDC8", "\U0001CDC9", "\U0001CDCA", + "\U0001CDCB", "\U0001CDCC", "\U0001CDCD", "\U0001CDCE", "\U0001CDCF", "\U0001CDD0", "\U0001CDD1", "\U0001CDD2", + "\U0001CDD3", "\U0001CDD4", "\U0001CDD5", "\U0001CDD6", "\U0001CDD7", "\U0001CDD8", "\U0001CDD9", "\U0001CDDA", + "\U00002584", "\U0001CDDB", "\U0001CDDC", "\U0001CDDD", "\U0001CDDE", "\U00002599", "\U0001CDDF", "\U0001CDE0", + "\U0001CDE1", "\U0001CDE2", "\U0000259F", "\U0001CDE3", "\U00002586", "\U0001CDE4", "\U0001CDE5", "\U00002588", }; -// Solve for the cell rendered by this 4x2 sample. None of the input pixels may -// be transparent (that ought already have been handled). We use exhaustive -// search, which might be quite computationally intensive for the worst case -// (all eight pixels are different colors). We want to solve for the 2-partition -// of pixels that minimizes total source distance from the resulting lerps. -static const char* -oct_solver(const uint32_t rgbas[8], uint64_t* channels, unsigned blendcolors, - unsigned nointerpolate){ - // Each element within the set of 256 has an inverse element within - // the set, for which we would calculate the same total differences, - // so just handle the first 128. - // - // We loop over the bitstrings, dividing the pixels into two sets, - // and then taking a general lerp over each set. we then compute the - // sum of absolute differences, and see if it's the new minimum. - int best = -1; - uint32_t mindiff = UINT_MAX; - for(size_t glyph = 0; glyph < 128; ++glyph){ - unsigned rsum0 = 0, rsum1 = 0; - unsigned gsum0 = 0, gsum1 = 0; - unsigned bsum0 = 0, bsum1 = 0; - int insum = 0; - int outsum = 0; - for(unsigned mask = 0 ; mask < 8 ; ++mask){ - if(glyph & (1u << mask)){ - if(!nointerpolate || !insum){ - rsum0 += ncpixel_r(rgbas[mask]); - gsum0 += ncpixel_g(rgbas[mask]); - bsum0 += ncpixel_b(rgbas[mask]); - ++insum; - } - }else{ - if(!nointerpolate || !outsum){ - rsum1 += ncpixel_r(rgbas[mask]); - gsum1 += ncpixel_g(rgbas[mask]); - bsum1 += ncpixel_b(rgbas[mask]); - ++outsum; - } - } - } - uint32_t l0 = generalerp(rsum0, gsum0, bsum0, insum); - uint32_t l1 = generalerp(rsum1, gsum1, bsum1, outsum); - uint32_t totaldiff = 0; - for(unsigned mask = 0 ; mask < 8 ; ++mask){ - unsigned r, g, b; - if(glyph & (1u << mask)){ - ncchannel_rgb8(l0, &r, &g, &b); - }else{ - ncchannel_rgb8(l1, &r, &g, &b); - } - uint32_t rdiff = rgb_diff(ncpixel_r(rgbas[mask]), ncpixel_g(rgbas[mask]), - ncpixel_b(rgbas[mask]), r, g, b); - totaldiff += rdiff; - } - if(totaldiff < mindiff){ - mindiff = totaldiff; - best = glyph; - ncchannels_set_fchannel(channels, l0); - ncchannels_set_bchannel(channels, l1); - } - if(totaldiff == 0){ // can't beat that! - break; - } - } - assert(best >= 0 && best < 128); - if(blendcolors){ - ncchannels_set_fg_alpha(channels, NCALPHA_BLEND); - ncchannels_set_bg_alpha(channels, NCALPHA_BLEND); - } - return octant_egcs[best]; -} - -static const char* -oct_trans_check(nccell* c, const uint32_t rgbas[8], unsigned blendcolors, - uint32_t transcolor, unsigned nointerpolate){ - unsigned transstring = 0; - unsigned r = 0, g = 0, b = 0; - unsigned div = 0; - for(unsigned mask = 0 ; mask < 8 ; ++mask){ - if(rgba_trans_p(rgbas[mask], transcolor)){ - transstring |= (1u << mask); - }else if(!nointerpolate || !div){ - r += ncpixel_r(rgbas[mask]); - g += ncpixel_g(rgbas[mask]); - b += ncpixel_b(rgbas[mask]); - ++div; - } - } - if(transstring == 0){ // there was no transparency - return NULL; - } - nccell_set_bg_alpha(c, NCALPHA_TRANSPARENT); - // there were some transparent pixels. since they get priority, the foreground - // is just a general lerp across non-transparent pixels. - const char* egc = octant_egcs[transstring ^ 0xff]; - nccell_set_bg_alpha(c, NCALPHA_TRANSPARENT); -//fprintf(stderr, "transtring: %u egc: %s\n", transtring, egc); - if(*egc == ' '){ // entirely transparent - nccell_set_fg_alpha(c, NCALPHA_TRANSPARENT); - return ""; - }else{ // partially transparent, thus div >= 1 -//fprintf(stderr, "div: %u r: %u g: %u b: %u\n", div, r, g, b); - cell_set_fchannel(c, generalerp(r, g, b, div)); - if(blendcolors){ - nccell_set_fg_alpha(c, NCALPHA_BLEND); - } - cell_set_blitquadrants(c, !(transstring & 5u), !(transstring & 10u), - !(transstring & 20u), !(transstring & 40u)); - } -//fprintf(stderr, "OCT-BQ: 0x%x\n", cell_blittedquadrants(c)); - return egc; -} - -// octant blitter. maps 4x2 to each cell. since we only have two colors at -// our disposal (foreground and background), we lose some fidelity. -static inline int -octant_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx, - const blitterargs* bargs){ - const unsigned nointerpolate = bargs->flags & NCVISUAL_OPTION_NOINTERPOLATE; - const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND; - unsigned dimy, dimx, x, y; - int total = 0; // number of cells written - ncplane_dim_yx(nc, &dimy, &dimx); -//fprintf(stderr, "octblitter %dx%d -> %d/%d+%d/%d\n", leny, lenx, dimy, dimx, bargs->u.cell.placey, bargs->u.cell.placex); - const unsigned char* dat = data; - int visy = bargs->begy; - for(y = bargs->u.cell.placey ; visy < (bargs->begy + leny) && y < dimy ; ++y, visy += 4){ - if(ncplane_cursor_move_yx(nc, y, bargs->u.cell.placex < 0 ? 0 : bargs->u.cell.placex)){ - return -1; - } - int visx = bargs->begx; - for(x = bargs->u.cell.placex ; visx < (bargs->begx + lenx) && x < dimx ; ++x, visx += 2){ - uint32_t rgbas[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; - memcpy(&rgbas[0], (dat + (linesize * visy) + (visx * 4)), sizeof(*rgbas)); - if(visx < bargs->begx + lenx - 1){ - memcpy(&rgbas[1], (dat + (linesize * visy) + ((visx + 1) * 4)), sizeof(*rgbas)); - if(visy < bargs->begy + leny - 1){ - memcpy(&rgbas[3], (dat + (linesize * (visy + 1)) + ((visx + 1) * 4)), sizeof(*rgbas)); - if(visy < bargs->begy + leny - 2){ - memcpy(&rgbas[5], (dat + (linesize * (visy + 2)) + ((visx + 1) * 4)), sizeof(*rgbas)); - if(visy < bargs->begy + leny - 3){ - memcpy(&rgbas[7], (dat + (linesize * (visy + 3)) + ((visx + 1) * 4)), sizeof(*rgbas)); - } - } - } - } - if(visy < bargs->begy + leny - 1){ - memcpy(&rgbas[2], (dat + (linesize * (visy + 1)) + (visx * 4)), sizeof(*rgbas)); - if(visy < bargs->begy + leny - 2){ - memcpy(&rgbas[4], (dat + (linesize * (visy + 2)) + (visx * 4)), sizeof(*rgbas)); - if(visy < bargs->begy + leny - 3){ - memcpy(&rgbas[6], (dat + (linesize * (visy + 3)) + (visx * 4)), sizeof(*rgbas)); - } - } - } - nccell* c = ncplane_cell_ref_yx(nc, y, x); - c->channels = 0; - c->stylemask = 0; - const char* egc = oct_trans_check(c, rgbas, blendcolors, bargs->transcolor, nointerpolate); - if(egc == NULL){ // no transparency; run a full solver - egc = oct_solver(rgbas, &c->channels, blendcolors, nointerpolate); - cell_set_blitquadrants(c, 1, 1, 1, 1); - } -//fprintf(stderr, "oct EGC: %s channels: %016lx\n", egc, c->channels); - if(*egc){ - if(pool_blit_direct(&nc->pool, c, egc, strlen(egc), 1) <= 0){ - return -1; - } - ++total; - }else{ - nccell_release(nc, c); - } - } - } - return total; -} +// Bit is set where Braille dot is present: +// 0 1 +// 2 3 +// 4 5 +// 6 7 +// Similar to NCBRAILLEEGCS, but in a different order since we number the bits differently +static const char braille_egcs[256][5] = { + "\u2800", "\u2801", "\u2808", "\u2809", "\u2802", "\u2803", "\u280a", "\u280b", + "\u2810", "\u2811", "\u2818", "\u2819", "\u2812", "\u2813", "\u281a", "\u281b", + "\u2804", "\u2805", "\u280c", "\u280d", "\u2806", "\u2807", "\u280e", "\u280f", + "\u2814", "\u2815", "\u281c", "\u281d", "\u2816", "\u2817", "\u281e", "\u281f", + "\u2820", "\u2821", "\u2828", "\u2829", "\u2822", "\u2823", "\u282a", "\u282b", + "\u2830", "\u2831", "\u2838", "\u2839", "\u2832", "\u2833", "\u283a", "\u283b", + "\u2824", "\u2825", "\u282c", "\u282d", "\u2826", "\u2827", "\u282e", "\u282f", + "\u2834", "\u2835", "\u283c", "\u283d", "\u2836", "\u2837", "\u283e", "\u283f", + "\u2840", "\u2841", "\u2848", "\u2849", "\u2842", "\u2843", "\u284a", "\u284b", + "\u2850", "\u2851", "\u2858", "\u2859", "\u2852", "\u2853", "\u285a", "\u285b", + "\u2844", "\u2845", "\u284c", "\u284d", "\u2846", "\u2847", "\u284e", "\u284f", + "\u2854", "\u2855", "\u285c", "\u285d", "\u2856", "\u2857", "\u285e", "\u285f", + "\u2860", "\u2861", "\u2868", "\u2869", "\u2862", "\u2863", "\u286a", "\u286b", + "\u2870", "\u2871", "\u2878", "\u2879", "\u2872", "\u2873", "\u287a", "\u287b", + "\u2864", "\u2865", "\u286c", "\u286d", "\u2866", "\u2867", "\u286e", "\u286f", + "\u2874", "\u2875", "\u287c", "\u287d", "\u2876", "\u2877", "\u287e", "\u287f", + "\u2880", "\u2881", "\u2888", "\u2889", "\u2882", "\u2883", "\u288a", "\u288b", + "\u2890", "\u2891", "\u2898", "\u2899", "\u2892", "\u2893", "\u289a", "\u289b", + "\u2884", "\u2885", "\u288c", "\u288d", "\u2886", "\u2887", "\u288e", "\u288f", + "\u2894", "\u2895", "\u289c", "\u289d", "\u2896", "\u2897", "\u289e", "\u289f", + "\u28a0", "\u28a1", "\u28a8", "\u28a9", "\u28a2", "\u28a3", "\u28aa", "\u28ab", + "\u28b0", "\u28b1", "\u28b8", "\u28b9", "\u28b2", "\u28b3", "\u28ba", "\u28bb", + "\u28a4", "\u28a5", "\u28ac", "\u28ad", "\u28a6", "\u28a7", "\u28ae", "\u28af", + "\u28b4", "\u28b5", "\u28bc", "\u28bd", "\u28b6", "\u28b7", "\u28be", "\u28bf", + "\u28c0", "\u28c1", "\u28c8", "\u28c9", "\u28c2", "\u28c3", "\u28ca", "\u28cb", + "\u28d0", "\u28d1", "\u28d8", "\u28d9", "\u28d2", "\u28d3", "\u28da", "\u28db", + "\u28c4", "\u28c5", "\u28cc", "\u28cd", "\u28c6", "\u28c7", "\u28ce", "\u28cf", + "\u28d4", "\u28d5", "\u28dc", "\u28dd", "\u28d6", "\u28d7", "\u28de", "\u28df", + "\u28e0", "\u28e1", "\u28e8", "\u28e9", "\u28e2", "\u28e3", "\u28ea", "\u28eb", + "\u28f0", "\u28f1", "\u28f8", "\u28f9", "\u28f2", "\u28f3", "\u28fa", "\u28fb", + "\u28e4", "\u28e5", "\u28ec", "\u28ed", "\u28e6", "\u28e7", "\u28ee", "\u28ef", + "\u28f4", "\u28f5", "\u28fc", "\u28fd", "\u28f6", "\u28f7", "\u28fe", "\u28ff", +}; // fold the r, g, and b components of the pixel into *r, *g, and *b, and // increment *foldcount @@ -1164,13 +804,14 @@ fold_rgb8(unsigned* restrict r, unsigned* restrict g, unsigned* restrict b, ++*foldcount; } -// Braille blitter. maps 4x2 to each cell. since we only have one color at -// our disposal (foreground), we lose some fidelity. this is optimal for -// visuals with only two colors in a given area, as it packs lots of -// resolution. always transparent background. +// generic 4x2 blitter, used for octant and Braille. maps 4x2 to each +// cell. since we only have one color at our disposal (foreground), we +// lose some fidelity. this is optimal for visuals with only two +// colors in a given area, as it packs lots of resolution. always +// transparent background. static inline int -braille_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx, - const blitterargs* bargs){ +blit_4x2(ncplane* nc, int linesize, const void* data, int leny, int lenx, + const blitterargs* bargs, const char egcs[256][5]){ const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND; unsigned dimy, dimx, x, y; int total = 0; // number of cells written @@ -1216,31 +857,31 @@ braille_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx, } } } - // braille block is ordered (where 1 is the LSB) - // 1 4 - // 2 5 - // 3 6 - // 7 8 + // 4x2 block is ordered (where 0 is the LSB) + // 0 1 + // 2 3 + // 4 5 + // 6 7 // FIXME fold this into the above? if(!rgba_trans_p(*rgbbase_l0, bargs->transcolor)){ egcidx |= 1u; fold_rgb8(&r, &g, &b, rgbbase_l0, &blends); } - if(!rgba_trans_p(*rgbbase_l1, bargs->transcolor)){ + if(!rgba_trans_p(*rgbbase_r0, bargs->transcolor)){ egcidx |= 2u; - fold_rgb8(&r, &g, &b, rgbbase_l1, &blends); + fold_rgb8(&r, &g, &b, rgbbase_r0, &blends); } - if(!rgba_trans_p(*rgbbase_l2, bargs->transcolor)){ + if(!rgba_trans_p(*rgbbase_l1, bargs->transcolor)){ egcidx |= 4u; - fold_rgb8(&r, &g, &b, rgbbase_l2, &blends); + fold_rgb8(&r, &g, &b, rgbbase_l1, &blends); } - if(!rgba_trans_p(*rgbbase_r0, bargs->transcolor)){ + if(!rgba_trans_p(*rgbbase_r1, bargs->transcolor)){ egcidx |= 8u; - fold_rgb8(&r, &g, &b, rgbbase_r0, &blends); + fold_rgb8(&r, &g, &b, rgbbase_r1, &blends); } - if(!rgba_trans_p(*rgbbase_r1, bargs->transcolor)){ + if(!rgba_trans_p(*rgbbase_l2, bargs->transcolor)){ egcidx |= 16u; - fold_rgb8(&r, &g, &b, rgbbase_r1, &blends); + fold_rgb8(&r, &g, &b, rgbbase_l2, &blends); } if(!rgba_trans_p(*rgbbase_r2, bargs->transcolor)){ egcidx |= 32u; @@ -1274,11 +915,7 @@ braille_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx, if(blends){ nccell_set_fg_rgb8(c, r / blends, g / blends, b / blends); } - // UTF-8 encodings of the Braille Patterns are always 0xe2 0xaX 0xCC, - // where 0 <= X <= 3 and 0x80 <= CC <= 0xbf (4 groups of 64). - char egc[4] = { 0xe2, 0xa0, 0x80, 0x00 }; - egc[2] += egcidx % 64; - egc[1] += egcidx / 64; + const char* egc = egcs[egcidx]; if(pool_blit_direct(&nc->pool, c, egc, strlen(egc), 1) <= 0){ return -1; } @@ -1289,6 +926,18 @@ braille_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx, return total; } +static inline int +braille_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx, + const blitterargs* bargs){ + return blit_4x2(nc, linesize, data, leny, lenx, bargs, braille_egcs); +} + +static inline int +octant_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx, + const blitterargs* bargs){ + return blit_4x2(nc, linesize, data, leny, lenx, bargs, octant_egcs); +} + // 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 @@ -1317,23 +966,23 @@ static struct blitset notcurses_blitters[] = { L"\U0001CD96" L"\U0001CD91" L"\U0001CEA3" - L"\U00002582" + L"\U00002582" L"\U0001CDCB" L"\U0001CDD3" L"\U0001CDCD" - L"\U00002596" + L"\U00002596" L"\U0001CDBB" - L"\U00002584" + L"\U00002584" L"\U0001CDE1" L"\U0001CDDC" L"\U0001CD48" L"\U0001CDBF" L"\U0001CDDE" - L"\U00002586" + L"\U00002586" L"\U0001CDDF" - L"\U0000258C" + L"\U0000258C" L"\U0001CDC0" - L"\U00002599" + L"\U00002599" L"\U0001CDE4" L"\U0001CDE0"), .blit = octant_blit, .name = "oct", .fill = false, },