qrcode generalization #699 (#713)

Add convenience function ncplane_home(). Add an ncblitter_e param
to ncplane_qrcode(), and split int maxversion into value-result
int* ymax and int* xmax. Write the actual sizes of the resulting
visual into these parameters. Update the qrcode demo. Add the
qrcode PoC. Update demos to ncplane_home(), where possible.

ncplane_qrcode() now takes an ncblitter_e and two value-result int*s
in the place of a single value int. The final size of the displayed qrcode
is written to *ymax and *xmax. If the code can't fit within the specified
dimensions, an error is returned. Standard rules for pluggable blitters
apply regarding fallback etc. #699
pull/715/head
Nick Black 4 years ago committed by GitHub
parent 22dc5014cc
commit 0084dbaa6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -15,6 +15,11 @@ rearrangements of Notcurses.
can be taken into account (the latter contains a `blitter_e` field).
* Added `ncuplot_sample()` and `ncdplot_sample()`, allowing retrieval of
sample data from `ncuplot`s and `ncdplot`s, respectively.
* Added convenience function `ncplane_home()`, which sets the cursor
to the plane's origin (and returns `void`, since it cannot fail).
* `ncplane_qrcode()` now accepts an `ncblitter_e`, and two value-result
`int*`s `ymax` and `xmax`. The actual size of the drawn code is
returned in these parameters.
* 1.5.0 (2020-07-08)
* The various `bool`s of `struct notcurses_options` have been folded into

@ -11,7 +11,7 @@ version 2, notcurses will honor Semantic Versioning.
* [Reels](#reels) ([ncreel Examples](#ncreel-examples))
* [Widgets](#widgets) ([Readers](#readers))
* [Channels](#channels)
* [Visuals](#visuals) ([Multimedia](#multimedia))
* [Visuals](#visuals) ([QR codes](#qrcodes)) ([Multimedia](#multimedia)) ([Pixels](#pixels))
* [C++](#c++)
A full API reference [is available](https://nick-black.com/notcurses/). Manual
@ -2675,6 +2675,22 @@ int ncvisual_stream(struct notcurses* nc, struct ncvisual* ncv,
const struct ncvisual_options* vopts, void* curry);
```
### QR codes
If build with libqrcodegen support, `ncplane_qrcode()` can be used to draw
a QR code for arbitrary data.
```c
// Draw a QR code at the current position on the plane. If there is insufficient
// room to draw the code here, or there is any other error, non-zero will be
// returned. Otherwise, the QR code "version" (size) is returned. The QR code
// is (version * 4 + 17) columns wide, and ⌈version * 4 + 17⌉ rows tall (the
// properly-scaled values are written back to '*ymax' and '*xmax').
int ncplane_qrcode(struct ncplane* n, ncblitter_e blitter, int* ymax,
int* xmax, const void* data, size_t len);
```
### Multimedia
When compiled against a suitable engine (FFmpeg and OpenImageIO are both

@ -1027,9 +1027,9 @@ namespace ncpp
return error_guard_cond<bool, bool> (ret, ret);
}
int qrcode (int maxversion, const void *data, size_t len) const NOEXCEPT_MAYBE
int qrcode (ncblitter_e blitter, int* ymax, int* xmax, const void *data, size_t len) const NOEXCEPT_MAYBE
{
int ret = ncplane_qrcode (plane, maxversion, data, len);
int ret = ncplane_qrcode (plane, blitter, ymax, xmax, data, len);
return error_guard_cond<int> (ret, ret < 0);
}

@ -1339,6 +1339,9 @@ ncplane_align(const struct ncplane* n, ncalign_e align, int c){
// plane's dimensions.
API int ncplane_cursor_move_yx(struct ncplane* n, int y, int x);
// Move the cursor to 0, 0. Can't fail.
API void ncplane_home(struct ncplane* n);
// Get the current position of the cursor within n. y and/or x may be NULL.
API void ncplane_cursor_yx(const struct ncplane* n, int* RESTRICT y, int* RESTRICT x);
@ -1998,12 +2001,24 @@ ncplane_fg_alpha(const struct ncplane* nc){
return channels_fg_alpha(ncplane_channels(nc));
}
// Is the plane's foreground using the "default foreground color"?
static inline bool
ncplane_fg_default_p(const struct ncplane* nc){
return channels_fg_default_p(ncplane_channels(nc));
}
// Extract 2 bits of background alpha from 'struct ncplane', shifted to LSBs.
static inline unsigned
ncplane_bg_alpha(const struct ncplane* nc){
return channels_bg_alpha(ncplane_channels(nc));
}
// Is the plane's background using the "default background color"?
static inline bool
ncplane_bg_default_p(const struct ncplane* nc){
return channels_bg_default_p(ncplane_channels(nc));
}
// Extract 24 bits of foreground RGB from 'n', split into components.
static inline unsigned
ncplane_fg_rgb(const struct ncplane* n, unsigned* r, unsigned* g, unsigned* b){
@ -2947,11 +2962,10 @@ API int ncsubproc_destroy(struct ncsubproc* n);
// Draw a QR code at the current position on the plane. If there is insufficient
// room to draw the code here, or there is any other error, non-zero will be
// returned. Otherwise, the QR code "version" (size) is returned. The QR code
// is (version * 4 + 17) columns wide, and ⌈version * 4 + 17 / 2⌉ rows tall. If
// maxversion is not zero, it plays a hard limit on the QR code size. Though the
// max version of current QR codes is 40, greater values are allowed, for
// future compatability (provide 0 for no artificial bound).
API int ncplane_qrcode(struct ncplane* n, int maxversion, const void* data, size_t len);
// is (version * 4 + 17) columns wide, and ⌈version * 4 + 17⌉ rows tall (the
// properly-scaled values are written back to '*ymax' and '*xmax').
API int ncplane_qrcode(struct ncplane* n, ncblitter_e blitter, int* ymax,
int* xmax, const void* data, size_t len);
#define NCREADER_OPTION_HORSCROLL 0x0001ull
#define NCREADER_OPTION_VERSCROLL 0x0002ull

@ -502,6 +502,7 @@ void ncreader_destroy(struct ncreader* n, char** contents);
int ncplane_puttext(struct ncplane* n, int y, ncalign_e align, const char* text, size_t* bytes);
int ncplane_putnstr_yx(struct ncplane* n, int y, int x, size_t s, const char* gclusters);
int ncplane_putnstr_aligned(struct ncplane* n, int y, ncalign_e align, size_t s, const char* gclustarr);
int ncplane_qrcode(struct ncplane* n, ncblitter_e blitter, int* ymax, int* xmax, const void* data, size_t len);
""")
if __name__ == "__main__":

@ -55,7 +55,7 @@ int allglyphs_demo(struct notcurses* nc){
int dimy, dimx;
struct ncplane* n = notcurses_stddim_yx(nc, &dimy, &dimx);
ncplane_erase(n);
ncplane_cursor_move_yx(n, 0, 0);
ncplane_home(n);
uint32_t tl = 0, tr = 0, bl = 0, br = 0;
channel_set_rgb(&tl, 0, 0, 0);
channel_set_rgb(&tr, 0, 0xff, 0);

@ -7,30 +7,32 @@ static int y, x, dy, dx;
static int
dragonmayer(struct ncvisual* ncv, const char* str, int iters){
int total = 0;
char c;
int r;
while( (c = *str++) ){
switch(c){
case 'X':
if(iters > 1){
if( (r = dragonmayer(ncv, "X+YF+", iters - 1)) ){
if((r = dragonmayer(ncv, "X+YF+", iters - 1)) < 0){
return r;
}
total += r;
}
break;
case 'Y':
if(iters > 1){
if( (r = dragonmayer(ncv, "-FX-Y", iters - 1)) ){
if((r = dragonmayer(ncv, "-FX-Y", iters - 1)) < 0){
return r;
}
total += r;
}
break;
case '+': { int tmp = dy; dy = -dx; dx = tmp; break; }
case '-': { int tmp = -dy; dy = dx; dx = tmp; break; }
case 'F': // FIXME want a line
// FIXME some of these will fail...hella lame, check against dims
if(ncvisual_set_yx(ncv, y, x, pixel) < 0){
done = true;
if(ncvisual_set_yx(ncv, y, x, pixel) == 0){
++total;
}
x += dx;
y += dy;
@ -39,7 +41,7 @@ dragonmayer(struct ncvisual* ncv, const char* str, int iters){
return -1;
}
}
return 0;
return total;
}
int dragon_demo(struct notcurses* nc){
@ -78,17 +80,20 @@ int dragon_demo(struct notcurses* nc){
free(rgba);
struct timespec scaled;
timespec_div(&demodelay, 4, &scaled);
int lasttotal = 0;
int iters = 0;
int r = 0;
do{
++iters;
lasttotal = r;
pixel = 0xffffffffull;
ncpixel_set_rgb(&pixel, 0, 0x11 * iters, 0);
ncpixel_set_rgb(&pixel, 0, 0xb * iters, 0);
dx = dxstart;
dy = dystart;
x = dimx / 2;
y = dimy / 2;
int r = dragonmayer(ncv, LINDENSTART, iters);
if(r){
y = dimy / 3;
r = dragonmayer(ncv, LINDENSTART, iters);
if(r < 0){
ncvisual_destroy(ncv);
return r;
}
@ -102,8 +107,7 @@ int dragon_demo(struct notcurses* nc){
}
DEMO_RENDER(nc);
demo_nanosleep(nc, &scaled);
ncplane_erase(n);
}while(!done);
}while(lasttotal != r);
ncvisual_destroy(ncv);
return 0;
}

@ -51,11 +51,11 @@ zoom_map(struct notcurses* nc, const char* map, int* ret){
// we start at the lower left corner of the outzoomed map
int truex, truey; // dimensions of true display
notcurses_term_dim_yx(nc, &truey, &truex);
int delty = yscale;
int deltx = xscale;
int delty = 2;
int deltx = 2;
if(truey > truex){
++delty;
}else if(truex > truey * 2){
}else if(truex > truey){
++deltx;
}
// to zoom in on the map, we're going to scale the full image to a plane

@ -32,9 +32,7 @@ int intro(struct notcurses* nc){
channel_set_rgb(&ccll, 0x88, 0, 0xcc);
channel_set_rgb(&cclr, 0, 0, 0);
// we use full block rather+fg than space+bg to conflict less with the menu
if(ncplane_cursor_move_yx(ncp, 0, 0)){
return -1;
}
ncplane_home(ncp);
if(ncplane_highgradient_sized(ncp, ccul, ccur, ccll, cclr, rows, cols) <= 0){
return -1;
}

@ -199,12 +199,12 @@ int normal_demo(struct notcurses* nc){
}
if(notcurses_canutf8(nc)){
ncplane_erase(nstd);
ncplane_cursor_move_yx(n, 0, 0);
ncplane_home(n);
if(rotate_plane(nc, n)){
goto err;
}
}
ncplane_cursor_move_yx(n, 0, 0);
ncplane_home(n);
uint64_t tl, tr, bl, br;
tl = tr = bl = br = 0;
channels_set_fg_rgb(&tl, 0, 0, 0);

@ -1,20 +1,5 @@
#include "demo.h"
#ifdef USE_QRCODEGEN
#include <sys/random.h>
// FIXME duplicated--ought these just be exported?
#define QR_BASE_SIZE 17
#define PER_QR_VERSION 4
static inline int
qrcode_rows(int version){
return (QR_BASE_SIZE + (version * PER_QR_VERSION)) / 2;
}
static inline int
qrcode_cols(int version){
return QR_BASE_SIZE + (version * PER_QR_VERSION);
}
#endif
int qrcode_demo(struct notcurses* nc){
if(!notcurses_canutf8(nc)){
@ -36,33 +21,19 @@ int qrcode_demo(struct notcurses* nc){
memcpy(data + done, &r, sizeof(r));
done += sizeof(r);
}
if(ncplane_cursor_move_yx(n, 0, 0)){
ncplane_destroy(n);
return -1;
}
int qlen = ncplane_qrcode(n, 0, data, len);
// can fail due to being too large for the terminal (FIXME), or ASCII mode
if(qlen > 0){
ncplane_move_yx(n, dimy / 2 - qrcode_rows(qlen) / 2,
dimx / 2 - qrcode_cols(qlen) / 2);
if(ncplane_cursor_move_yx(n, 0, 0)){
ncplane_destroy(n);
return -1;
}
uint64_t tl = 0, bl = 0, br = 0, tr = 0;
channels_set_fg_rgb(&tl, random() % 255 + 1, random() % 255 + 1, random() % 255 + 1);
channels_set_fg_rgb(&tr, random() % 255 + 1, random() % 255 + 1, random() % 255 + 1);
channels_set_fg_rgb(&bl, random() % 255 + 1, random() % 255 + 1, random() % 255 + 1);
channels_set_fg_rgb(&br, random() % 255 + 1, random() % 255 + 1, random() % 255 + 1);
if(ncplane_stain(n, qrcode_rows(qlen), qrcode_cols(qlen), tl, tr, bl, br) <= 0){
ncplane_destroy(n);
return -1;
}
ncplane_home(n);
int y = dimy, x = dimx;
ncplane_home(n);
int qlen = ncplane_qrcode(n, NCBLIT_DEFAULT, &y, &x, data, len);
if(qlen > 0){ // FIXME can fail due to being too large for display; distinguish this case
ncplane_move_yx(n, (dimy - y) / 2, (dimx - x) / 2);
ncplane_home(n);
ncplane_set_fg_rgb(n, random() % 255 + 1, random() % 255 + 1, random() % 255 + 1);
DEMO_RENDER(nc);
}
}
ncplane_mergedown(n, stdn); // leave the last one on-screen
ncplane_destroy(n);
#endif
DEMO_RENDER(nc);
return 0;
}

@ -47,7 +47,7 @@ draw_block(struct ncplane* nn, uint32_t blockstart){
cell_set_fg_rgb(&vl, 255, 255, 255);
cell_set_bg_rgb(&hl, 0, 0, 0);
cell_set_bg_rgb(&vl, 0, 0, 0);
ncplane_cursor_move_yx(nn, 0, 0);
ncplane_home(nn);
unsigned control = NCBOXGRAD_TOP | NCBOXGRAD_BOTTOM | NCBOXGRAD_LEFT | NCBOXGRAD_RIGHT;
if(ncplane_box_sized(nn, &ul, &ur, &ll, &lr, &hl, &vl, dimy, dimx, control)){
return -1;

@ -534,7 +534,7 @@ int ncplane_rotate_ccw(ncplane* n){
static inline int
qrcode_rows(int version){
return (QR_BASE_SIZE + (version * PER_QR_VERSION)) / 2;
return QR_BASE_SIZE + (version * PER_QR_VERSION);
}
static inline int
@ -542,9 +542,10 @@ qrcode_cols(int version){
return QR_BASE_SIZE + (version * PER_QR_VERSION);
}
int ncplane_qrcode(ncplane* n, int maxversion, const void* data, size_t len){
int ncplane_qrcode(ncplane* n, ncblitter_e blitter, int* ymax,
int* xmax, const void* data, size_t len){
const int MAX_QR_VERSION = 40; // QR library only supports up to 40
if(maxversion < 0){
if(*ymax <= 0 || *xmax <= 0){
return -1;
}
if(len == 0){
@ -552,25 +553,24 @@ int ncplane_qrcode(ncplane* n, int maxversion, const void* data, size_t len){
}
const int starty = n->y;
const int startx = n->x;
const int availx = n->lenx - startx;
const int availy = n->leny - starty;
if(availy < qrcode_rows(1)){
if(*xmax > n->lenx - startx){
return -1;
}
if(availx < qrcode_cols(1)){
if(*ymax > n->leny - starty){
return -1;
}
const int availsquare = availy * 2 < availx ? availy * 2 : availx;
const int roomforver = (availsquare - QR_BASE_SIZE) / 4;
if(maxversion == 0){
maxversion = roomforver;
}else if(maxversion > roomforver){
maxversion = roomforver;
if(*ymax < qrcode_rows(1)){
return -1;
}
if(maxversion > MAX_QR_VERSION){
maxversion = MAX_QR_VERSION;
if(*xmax < qrcode_cols(1)){
return -1;
}
const size_t bsize = qrcodegen_BUFFER_LEN_FOR_VERSION(maxversion);
const int availsquare = *ymax * 2 < *xmax ? *ymax * 2 : *xmax;
int roomforver = (availsquare - QR_BASE_SIZE) / PER_QR_VERSION;
if(roomforver > MAX_QR_VERSION){
roomforver = MAX_QR_VERSION;
}
const size_t bsize = qrcodegen_BUFFER_LEN_FOR_VERSION(roomforver);
if(bsize < len){
return -1;
}
@ -581,40 +581,59 @@ int ncplane_qrcode(ncplane* n, int maxversion, const void* data, size_t len){
free(dst);
return -1;
}
unsigned r, g, b;
// FIXME default might not be all-white
if(ncplane_fg_default_p(n)){
r = g = b = 0xff;
}else{
ncplane_fg_rgb(n, &r, &g, &b);
}
memcpy(src, data, len);
int ret = -1;
if(qrcodegen_encodeBinary(src, len, dst, qrcodegen_Ecc_HIGH, 1, maxversion, qrcodegen_Mask_AUTO, true)){
ret = qrcodegen_getSize(dst);
for(int y = starty ; y < starty + (ret + 1) / 2 ; ++y){
for(int x = startx ; x < startx + ret ; ++x){
const bool top = qrcodegen_getModule(dst, x, y);
const bool bot = qrcodegen_getModule(dst, x, y + 1);
const char* egc;
if(top && bot){
egc = "";
}else if(top){
egc = "";
}else if(bot){
egc = "";
}else{
egc = " ";
int yscale, xscale;
if(qrcodegen_encodeBinary(src, len, dst, qrcodegen_Ecc_HIGH, 1, roomforver, qrcodegen_Mask_AUTO, true)){
const int square = qrcodegen_getSize(dst);
uint32_t* rgba = malloc(square * square * sizeof(uint32_t));
if(rgba){
for(int y = starty ; y < starty + square ; ++y){
for(int x = startx ; x < startx + square ; ++x){
const bool pixel = qrcodegen_getModule(dst, x, y);
ncpixel_set_a(&rgba[y * square + x], 0xff);
ncpixel_set_rgb(&rgba[y * square + x], r * pixel, g * pixel, b * pixel);
}
int sbytes;
if(ncplane_putegc_yx(n, y, x, egc, &sbytes) <= 0){
ret = -1;
break;
}
struct ncvisual* ncv = ncvisual_from_rgba(rgba, square, square * sizeof(uint32_t), square);
free(rgba);
if(ncv){
ret = square;
struct ncvisual_options vopts = {
.n = n,
.blitter = blitter,
};
if(ncvisual_render(n->nc, ncv, &vopts) == n){
ret = square;
}
ncvisual_geom(n->nc, ncv, &vopts, NULL, NULL, &yscale, &xscale);
}
}
}
free(src);
free(dst);
return ret < 0 ? ret : (ret - QR_BASE_SIZE) / PER_QR_VERSION;
if(ret > 0){
ret = (ret - QR_BASE_SIZE) / PER_QR_VERSION;
*ymax = qrcode_rows(ret) / yscale;
*xmax = qrcode_cols(ret) / xscale;
return ret;
}
return -1;
}
#else
int ncplane_qrcode(ncplane* n, int maxversion, const void* data, size_t len){
int ncplane_qrcode(ncplane* n, ncblitter_e blitter, int* ymax, int* xmax,
const void* data, size_t len){
(void)n;
(void)maxversion;
(void)blitter;
(void)ymax;
(void)xmax;
(void)data;
(void)len;
return -1;

@ -340,6 +340,11 @@ ncplane* ncplane_aligned(ncplane* n, int rows, int cols, int yoff,
return ncplane_create(n->nc, n, rows, cols, yoff, ncplane_align(n, align, cols), opaque);
}
void ncplane_home(ncplane* n){
n->x = 0;
n->y = 0;
}
inline int ncplane_cursor_move_yx(ncplane* n, int y, int x){
if(x >= n->lenx){
return -1;

@ -241,9 +241,7 @@ class ncppplot {
}
}
}
if(ncplane_cursor_move_yx(ncp, 0, 0)){
return -1;
}
ncplane_home(ncp);
return 0;
}

@ -142,7 +142,7 @@ int runreels(NotCurses& nc, ncreel_options& nopts){
case 'q':
return 0;
case 'a':{
TabletCtx* tctx = new TabletCtx();
auto tctx = new TabletCtx();
nr->add(nullptr, nullptr, tabletfxn, tctx);
break;
}

@ -0,0 +1,47 @@
#include <unistd.h>
#include <locale.h>
#include <notcurses/notcurses.h>
static int
render_qrcode(struct ncplane* n, int dimy, int dimx, const char* text){
int y = dimy, x = dimx;
ncplane_home(n);
int ver = ncplane_qrcode(n, NCBLIT_DEFAULT, &y, &x, text, strlen(text));
if(ver < 0){
return -1;
}
if(ncplane_putstr_yx(n, y + 1, 0, text) < 0){
return -1;
}
if(notcurses_render(ncplane_notcurses(n))){
return -1;
}
sleep(2);
return 0;
}
int main(int argc, const char** argv){
if(setlocale(LC_ALL, "") == NULL){
return EXIT_FAILURE;
}
struct notcurses_options opts = {
.flags = NCOPTION_INHIBIT_SETLOCALE | NCOPTION_NO_ALTERNATE_SCREEN,
};
struct notcurses* nc = notcurses_init(&opts, NULL);
int dimy, dimx;
struct ncplane* std = notcurses_stddim_yx(nc, &dimy, &dimx);
while(*++argv){
if(render_qrcode(std, dimy, dimx, *argv)){
notcurses_stop(nc);
return EXIT_FAILURE;
}
}
if(argc < 2){
if(render_qrcode(std, dimy, dimx, "https://nick-black.com")){
notcurses_stop(nc);
return EXIT_FAILURE;
}
}
notcurses_stop(nc);
return EXIT_SUCCESS;
}

@ -12,7 +12,7 @@ rotate_grad(struct notcurses* nc){
};
int dimy, dimx;
struct ncplane* n = notcurses_stddim_yx(nc, &dimy, &dimx);
ncplane_cursor_move_yx(n, 0, 0);
ncplane_home(n);
uint32_t tl = 0, tr = 0, bl = 0, br = 0;
channel_set_rgb(&tl, 0xff, 0, 0);
channel_set_rgb(&tr, 0, 0, 0xff);

@ -440,7 +440,9 @@ TEST_CASE("Fills") {
#ifdef USE_QRCODEGEN
SUBCASE("QRCodes") {
const char* qr = "a very simple qr code";
CHECK(0 < ncplane_qrcode(n_, 0, qr, strlen(qr)));
int dimy, dimx;
ncplane_dim_yx(n_, &dimy, &dimx);
CHECK(0 < ncplane_qrcode(n_, NCBLIT_DEFAULT, &dimy, &dimx, qr, strlen(qr)));
CHECK(0 == notcurses_render(nc_));
}
#endif

Loading…
Cancel
Save