1 #include "internal.h"
2 
ncplane_greyscale(ncplane * n)3 void ncplane_greyscale(ncplane *n){
4   for(unsigned y = 0 ; y < n->leny ; ++y){
5     for(unsigned x = 0 ; x < n->lenx ; ++x){
6       nccell* c = &n->fb[nfbcellidx(n, y, x)];
7       unsigned r, g, b;
8       nccell_fg_rgb8(c, &r, &g, &b);
9       int gy = rgb_greyscale(r, g, b);
10       nccell_set_fg_rgb8(c, gy, gy, gy);
11       nccell_bg_rgb8(c, &r, &g, &b);
12       gy = rgb_greyscale(r, g, b);
13       nccell_set_bg_rgb8(c, gy, gy, gy);
14     }
15   }
16 }
17 
18 // if this is not polyfillable cell, we return 0. if it is, we attempt to fill
19 // it, then recurse out. return -1 on error, or number of cells filled on
20 // success. so a return of 0 means there's no work to be done here, and N means
21 // we did some work here, filling everything we could reach. out-of-plane is 0.
22 static int
ncplane_polyfill_inner(ncplane * n,unsigned y,unsigned x,const nccell * c,const char * filltarg)23 ncplane_polyfill_inner(ncplane* n, unsigned y, unsigned x, const nccell* c, const char* filltarg){
24   struct topolyfill* stack = NULL;
25   if(create_polyfill_op(y, x, &stack) == NULL){
26     return -1;
27   }
28   int ret = 0;
29   struct topolyfill* s;
30   do{
31     s = stack;
32     stack = stack->next;
33     y = s->y;
34     x = s->x;
35     nccell* cur = &n->fb[nfbcellidx(n, y, x)];
36     const char* glust = nccell_extended_gcluster(n, cur);
37 //fprintf(stderr, "checking %d/%d (%s) for [%s]\n", y, x, glust, filltarg);
38     if(strcmp(glust, filltarg) == 0){
39       ++ret;
40       if(nccell_duplicate(n, cur, c) < 0){
41         goto err;
42       }
43 //fprintf(stderr, "blooming from %d/%d ret: %d\n", y, x, ret);
44       if(y){
45         if(create_polyfill_op(y - 1, x, &stack) == NULL){
46           goto err;
47         }
48       }
49       if(y + 1 < n->leny){
50         if(create_polyfill_op(y + 1, x, &stack) == NULL){
51           goto err;
52         }
53       }
54       if(x){
55         if(create_polyfill_op(y, x - 1, &stack) == NULL){
56           goto err;
57         }
58       }
59       if(x + 1 < n->lenx){
60         if(create_polyfill_op(y, x + 1, &stack) == NULL){
61           goto err;
62         }
63       }
64     }
65     free(s);
66   }while(stack);
67   return ret;
68 
69 err:
70   free(s);
71   while(stack){
72     struct topolyfill* tmp = stack->next;
73     free(stack);
74     stack = tmp;
75   }
76   return -1;
77 }
78 
79 // at the initial step only, invalid ystart, xstart is an error, so explicitly check.
ncplane_polyfill_yx(ncplane * n,int ystart,int xstart,const nccell * c)80 int ncplane_polyfill_yx(ncplane* n, int ystart, int xstart, const nccell* c){
81   if(ystart < 0){
82     if(ystart != -1){
83       logerror("invalid y: %d\n", ystart);
84       return -1;
85     }
86     ystart = n->y;
87   }
88   unsigned y = ystart;
89   if(xstart < 0){
90     if(xstart != -1){
91       logerror("invalid x: %d\n", xstart);
92       return -1;
93     }
94     xstart = n->x;
95   }
96   unsigned x = xstart;
97   if(y >= n->leny || x >= n->lenx){
98     logerror("invalid start: %u/%u (%u/%u)\n", y, x, n->leny, n->lenx);
99     return -1;
100   }
101   const nccell* cur = &n->fb[nfbcellidx(n, y, x)];
102   const char* targ = nccell_extended_gcluster(n, cur);
103   const char* fillegc = nccell_extended_gcluster(n, c);
104 //fprintf(stderr, "checking %d/%d (%s) for [%s]\n", y, x, targ, fillegc);
105   if(strcmp(fillegc, targ) == 0){
106     return 0;
107   }
108   int ret = -1;
109   // we need an external copy of this, since we'll be writing to it on
110   // the first call into ncplane_polyfill_inner()
111   char* targcopy = strdup(targ);
112   if(targcopy){
113     ret = ncplane_polyfill_inner(n, y, x, c, targcopy);
114     free(targcopy);
115   }
116   return ret;
117 }
118 
119 static bool
check_gradient_channel_args(uint32_t ul,uint32_t ur,uint32_t bl,uint32_t br)120 check_gradient_channel_args(uint32_t ul, uint32_t ur, uint32_t bl, uint32_t br){
121   if(ncchannel_default_p(ul) || ncchannel_default_p(ur) ||
122      ncchannel_default_p(bl) || ncchannel_default_p(br)){
123     if(!(ncchannel_default_p(ul) && ncchannel_default_p(ur) &&
124          ncchannel_default_p(bl) && ncchannel_default_p(br))){
125       logerror("some (not all) channels were defaults\n");
126       return true;
127     }
128   }
129   if(ncchannel_alpha(ul) != ncchannel_alpha(ur) ||
130      ncchannel_alpha(ur) != ncchannel_alpha(bl) ||
131      ncchannel_alpha(bl) != ncchannel_alpha(br)){
132     logerror("channel alphas didn't match\n");
133     return true;
134   }
135   if(ncchannel_palindex_p(ul) || ncchannel_palindex_p(bl) ||
136      ncchannel_palindex_p(br) || ncchannel_palindex_p(ur)){
137     logerror("can't blend palette-indexed color\n");
138     return true;
139   }
140   return false;
141 }
142 
143 // Given the four channels arguments, verify that:
144 //
145 // - if any is default foreground, all are default foreground
146 // - if any is default background, all are default background
147 // - all foregrounds must have the same alpha
148 // - all backgrounds must have the same alpha
149 // - palette-indexed color must not be used
check_gradient_args(uint64_t ul,uint64_t ur,uint64_t bl,uint64_t br)150 bool check_gradient_args(uint64_t ul, uint64_t ur, uint64_t bl, uint64_t br){
151   if(check_gradient_channel_args(ncchannels_fchannel(ul), ncchannels_fchannel(ur),
152                                  ncchannels_fchannel(bl), ncchannels_fchannel(br))){
153     return true;
154   }
155   if(check_gradient_channel_args(ncchannels_bchannel(ul), ncchannels_bchannel(ur),
156                                  ncchannels_bchannel(bl), ncchannels_bchannel(br))){
157     return true;
158   }
159   return false;
160 }
161 
162 // calculate both channels of a gradient at a particular point, knowing that
163 // we're using double halfblocks, into `c`->channels.
164 static inline void
calc_highgradient(nccell * c,uint32_t ul,uint32_t ur,uint32_t ll,uint32_t lr,unsigned y,unsigned x,unsigned ylen,unsigned xlen)165 calc_highgradient(nccell* c, uint32_t ul, uint32_t ur, uint32_t ll,
166                   uint32_t lr, unsigned y, unsigned x,
167                   unsigned ylen, unsigned xlen){
168   if(!ncchannel_default_p(ul)){
169     cell_set_fchannel(c, calc_gradient_channel(ul, ur, ll, lr,
170                                                y * 2, x, ylen, xlen));
171     cell_set_bchannel(c, calc_gradient_channel(ul, ur, ll, lr,
172                                                y * 2 + 1, x, ylen, xlen));
173   }else{
174     nccell_set_fg_default(c);
175     nccell_set_bg_default(c);
176   }
177 }
178 
ncplane_gradient2x1(ncplane * n,int y,int x,unsigned ylen,unsigned xlen,uint32_t ul,uint32_t ur,uint32_t ll,uint32_t lr)179 int ncplane_gradient2x1(ncplane* n, int y, int x, unsigned ylen, unsigned xlen,
180                         uint32_t ul, uint32_t ur, uint32_t ll, uint32_t lr){
181   if(!notcurses_canutf8(ncplane_notcurses(n))){
182     logerror("highdef gradients require utf8\n");
183     return -1;
184   }
185   if(check_gradient_channel_args(ul, ur, ll, lr)){
186     return -1;
187   }
188   unsigned ystart, xstart;
189   if(check_geometry_args(n, y, x, &ylen, &xlen, &ystart, &xstart)){
190     return -1;
191   }
192   if(xlen == 1){
193     if(ul != ur || ll != lr){
194       logerror("horizontal channel variation in single column\n");
195       return -1;
196     }
197   }
198   int total = 0;
199   for(unsigned yy = ystart ; yy < ystart + ylen ; ++yy){
200     for(unsigned xx = xstart ; xx < xstart + xlen ; ++xx){
201       nccell* targc = ncplane_cell_ref_yx(n, yy, xx);
202       targc->channels = 0;
203       if(pool_blit_direct(&n->pool, targc, "▀", strlen("▀"), 1) <= 0){
204         return -1;
205       }
206       calc_highgradient(targc, ul, ur, ll, lr, yy - ystart, xx - xstart, ylen * 2, xlen);
207       ++total;
208     }
209   }
210   return total;
211 }
212 
213 // FIXME remove in abi3
ncplane_highgradient_sized(ncplane * n,uint32_t ul,uint32_t ur,uint32_t ll,uint32_t lr,int ylen,int xlen)214 int ncplane_highgradient_sized(ncplane* n, uint32_t ul, uint32_t ur,
215                                uint32_t ll, uint32_t lr, int ylen, int xlen){
216   if(ylen < 1 || xlen < 1){
217     return -1;
218   }
219   if(!notcurses_canutf8(ncplane_notcurses_const(n))){
220     // this works because the uin32_ts we pass in will be promoted to uint64_ts
221     // via extension, and the space will employ the background. mwahh!
222     return ncplane_gradient(n, -1, -1, ylen, xlen, " ", 0, ul, ur, ll, lr);
223   }
224   return ncplane_gradient2x1(n, -1, -1, ylen, xlen, ul, ur, ll, lr);
225 }
226 
ncplane_gradient(ncplane * n,int y,int x,unsigned ylen,unsigned xlen,const char * egc,uint16_t stylemask,uint64_t ul,uint64_t ur,uint64_t bl,uint64_t br)227 int ncplane_gradient(ncplane* n, int y, int x, unsigned ylen, unsigned xlen,
228                      const char* egc, uint16_t stylemask,
229                      uint64_t ul, uint64_t ur, uint64_t bl, uint64_t br){
230   if(check_gradient_args(ul, ur, bl, br)){
231     return -1;
232   }
233   unsigned ystart, xstart;
234   if(check_geometry_args(n, y, x, &ylen, &xlen, &ystart, &xstart)){
235     return -1;
236   }
237   if(ylen == 1){
238     if(xlen == 1){
239       if(ul != ur || ur != br || br != bl){
240         logerror("channel variation in 1x1 area\n");
241         return -1;
242       }
243     }else{
244       if(ul != bl || ur != br){
245         logerror("vertical channel variation in single row\n");
246         return -1;
247       }
248     }
249   }else if(xlen == 1){
250     if(ul != ur || bl != br){
251       logerror("horizontal channel variation in single column\n");
252       return -1;
253     }
254   }
255   int total = 0;
256   for(unsigned yy = ystart ; yy < ystart + ylen ; ++yy){
257     for(unsigned xx = xstart ; xx < xstart + xlen ; ++xx){
258       nccell* targc = ncplane_cell_ref_yx(n, yy, xx);
259       targc->channels = 0;
260       if(nccell_load(n, targc, egc) < 0){
261         return -1;
262       }
263       targc->stylemask = stylemask;
264       calc_gradient_channels(&targc->channels, ul, ur, bl, br,
265                              yy - ystart, xx - xstart, ylen, xlen);
266       ++total;
267     }
268   }
269   return total;
270 }
271 
ncplane_stain(ncplane * n,int y,int x,unsigned ylen,unsigned xlen,uint64_t tl,uint64_t tr,uint64_t bl,uint64_t br)272 int ncplane_stain(ncplane* n, int y, int x, unsigned ylen, unsigned xlen,
273                   uint64_t tl, uint64_t tr, uint64_t bl, uint64_t br){
274   // Can't use default or palette-indexed colors in a gradient
275   if(check_gradient_args(tl, tr, bl, br)){
276     return -1;
277   }
278   unsigned ystart, xstart;
279   if(check_geometry_args(n, y, x, &ylen, &xlen, &ystart, &xstart)){
280     return -1;
281   }
282   int total = 0;
283   for(unsigned yy = ystart ; yy < ystart + ylen ; ++yy){
284     for(unsigned xx = xstart ; xx < xstart + xlen ; ++xx){
285       nccell* targc = ncplane_cell_ref_yx(n, yy, xx);
286       if(targc->gcluster){
287         calc_gradient_channels(&targc->channels, tl, tr, bl, br,
288                                yy - ystart, xx - xstart, ylen, xlen);
289       }
290       ++total;
291     }
292   }
293   return total;
294 }
295 
ncplane_format(ncplane * n,int y,int x,unsigned ylen,unsigned xlen,uint16_t stylemask)296 int ncplane_format(ncplane* n, int y, int x, unsigned ylen,
297                    unsigned xlen, uint16_t stylemask){
298   unsigned ystart, xstart;
299   if(check_geometry_args(n, y, x, &ylen, &xlen, &ystart, &xstart)){
300     return -1;
301   }
302   int total = 0;
303   for(unsigned yy = ystart ; yy < ystart + ylen ; ++yy){
304     for(unsigned xx = xstart ; xx < xstart + xlen ; ++xx){
305       nccell* targc = ncplane_cell_ref_yx(n, yy, xx);
306       targc->stylemask = stylemask;
307       ++total;
308     }
309   }
310   return total;
311 }
312 
313 // if we're a half block, reverse the channels. if we're a space, set both to
314 // the background. if we're a full block, set both to the foreground.
315 static int
rotate_channels(ncplane * src,const nccell * c,uint32_t * fchan,uint32_t * bchan)316 rotate_channels(ncplane* src, const nccell* c, uint32_t* fchan, uint32_t* bchan){
317   const char* egc = nccell_extended_gcluster(src, c);
318   if(egc[0] == ' ' || egc[0] == 0){
319     *fchan = *bchan;
320     return 0;
321   }else if(strcmp(egc, "▄") == 0 || strcmp(egc, "▀") == 0){
322     uint32_t tmp = *fchan;
323     *fchan = *bchan;
324     *bchan = tmp;
325     return 0;
326   }else if(strcmp(egc, "█") == 0){
327     *bchan = *fchan;
328     return 0;
329   }
330   logerror("Invalid EGC for rotation [%s]\n", egc);
331   return -1;
332 }
333 
334 static int
rotate_output(ncplane * dst,uint32_t tchan,uint32_t bchan)335 rotate_output(ncplane* dst, uint32_t tchan, uint32_t bchan){
336   dst->channels = ncchannels_combine(tchan, bchan);
337   if(tchan != bchan){
338     return ncplane_putegc(dst, "▀", NULL);
339   }
340   if(ncchannel_default_p(tchan) && ncchannel_default_p(bchan)){
341     return ncplane_putegc(dst, "", NULL);
342   }else if(ncchannel_default_p(tchan)){
343     return ncplane_putegc(dst, " ", NULL);
344   }
345   return ncplane_putegc(dst, "█", NULL);
346 }
347 
348 // rotation works at two levels:
349 //  1) each 1x2 block is rotated into a 1x2 block ala
350 //      ab   cw    ca   ccw   ab   ccw   bd  ccw   dc  ccw   ca  ccw  ab
351 //      cd   -->   db   -->   cd   -->   ac  -->   ba  -->   db  -->  cd
352 //  2) each 1x2 block is rotated into its new location
353 //
354 // Characters which can be rotated must be RGB, to differentiate full blocks,
355 // spaces, and nuls. For clockwise rotations:
356 //
357 //  nul: converts to two half defaults
358 //  space: converts to two half backgrounds
359 //  full: converts to two half foregrounds
360 //  upper: converts to half background + half foreground
361 //  lower: converts to half foreground + half background
362 //
363 // Fore/background carry full channel, including transparency.
364 //
365 // Ideally, rotation through 360 degrees will restore the original 2x1 squre.
366 // Unfortunately, the case where a half block occupies a cell having the same
367 // fore- and background will see it rotated into a single full block. In
368 // addition, lower blocks eventually become upper blocks with their channels
369 // reversed. In general:
370 //
371 //  if a "row" (the bottom or top halves) are the same forechannel, merge to a
372 //    single full block of that color (what is its background?).
373 //  if a "row" is two different channels, they become a upper block (why not
374 //   lower?) having the two channels as fore- and background.
375 static int
rotate_2x1_cw(ncplane * src,ncplane * dst,int srcy,int srcx,int dsty,int dstx)376 rotate_2x1_cw(ncplane* src, ncplane* dst, int srcy, int srcx, int dsty, int dstx){
377   nccell c1 = NCCELL_TRIVIAL_INITIALIZER;
378   nccell c2 = NCCELL_TRIVIAL_INITIALIZER;
379   if(ncplane_at_yx_cell(src, srcy, srcx, &c1) < 0){
380     return -1;
381   }
382   if(ncplane_at_yx_cell(src, srcy, srcx + 1, &c2) < 0){
383     nccell_release(src, &c1);
384     return -1;
385   }
386   // there can be at most 4 colors and 4 transparencies:
387   //  - c1fg, c1bg, c2fg, c2bg, c1ftrans, c2ftrans, c1btrans, c2btrans
388   // but not all are necessarily used:
389   //  - topleft gets lowerleft. if lowerleft is foreground, c1fg c1ftrans.
390   //     otherwise, c1bg c1btrans
391   //  - topright gets upperleft. if upperleft is foreground, c1fg c1ftrans.
392   //     otherwise, c1bg c1btrans
393   //  - botleft get botright. if botright is foreground, c2fg c2ftrans.
394   //     otherwise, c2bg c2btrans
395   //  - botright gets topright. if topright is foreground, c2fg c2ftrans.
396   //     otherwise, c2bg c2btrans
397   uint32_t c1b = cell_bchannel_common(&c1);
398   uint32_t c2b = cell_bchannel_common(&c2);
399   uint32_t c1t = cell_fchannel_common(&c1);
400   uint32_t c2t = cell_fchannel_common(&c2);
401   int ret = 0;
402   ret |= rotate_channels(src, &c1, &c1t, &c1b);
403   ret |= rotate_channels(src, &c2, &c2t, &c2b);
404   // right char comes from two tops. left char comes from two bottoms. if
405   // they're the same channel, they become a:
406   //
407   //  nul if the channel is default
408   //  space if the fore is default
409   //  full if the back is default
410   ncplane_cursor_move_yx(dst, dsty, dstx);
411   rotate_output(dst, c1b, c2b);
412   rotate_output(dst, c1t, c2t);
413   return ret;
414 }
415 
416 static int
rotate_2x1_ccw(ncplane * src,ncplane * dst,int srcy,int srcx,int dsty,int dstx)417 rotate_2x1_ccw(ncplane* src, ncplane* dst, int srcy, int srcx, int dsty, int dstx){
418   nccell c1 = NCCELL_TRIVIAL_INITIALIZER;
419   nccell c2 = NCCELL_TRIVIAL_INITIALIZER;
420   if(ncplane_at_yx_cell(src, srcy, srcx, &c1) < 0){
421     return -1;
422   }
423   if(ncplane_at_yx_cell(src, srcy, srcx + 1, &c2) < 0){
424     nccell_release(src, &c1);
425     return -1;
426   }
427   uint32_t c1b = cell_bchannel_common(&c1);
428   unsigned c2b = cell_bchannel_common(&c2);
429   unsigned c1t = cell_fchannel_common(&c1);
430   unsigned c2t = cell_fchannel_common(&c2);
431   int ret = 0;
432   ret |= rotate_channels(src, &c1, &c1t, &c1b);
433   ret |= rotate_channels(src, &c2, &c2t, &c2b);
434   ncplane_cursor_move_yx(dst, dsty, dstx);
435   rotate_output(dst, c1t, c2t);
436   rotate_output(dst, c1b, c2b);
437   return ret;
438 }
439 
440 // copy 'newp' into 'n' after resizing 'n' to match 'newp'
441 static int
rotate_merge(ncplane * n,ncplane * newp)442 rotate_merge(ncplane* n, ncplane* newp){
443   unsigned dimy, dimx;
444   ncplane_dim_yx(newp, &dimy, &dimx);
445   int ret = ncplane_resize(n, 0, 0, 0, 0, 0, 0, dimy, dimx);
446   if(ret == 0){
447     for(unsigned y = 0 ; y < dimy ; ++y){
448       for(unsigned x = 0 ; x < dimx ; ++x){
449         const nccell* src = &newp->fb[fbcellidx(y, dimx, x)];
450         nccell* targ = &n->fb[fbcellidx(y, dimx, x)];
451         if(cell_duplicate_far(&n->pool, targ, newp, src) < 0){
452           return -1;
453         }
454       }
455     }
456   }
457   return ret;
458 }
459 
460 // generate a temporary plane that can hold the contents of n, rotated 90°
461 static ncplane*
rotate_plane(ncplane * n)462 rotate_plane(ncplane* n){
463   int absy, absx;
464   ncplane_yx(n, &absy, &absx);
465   unsigned dimy, dimx;
466   ncplane_dim_yx(n, &dimy, &dimx);
467   if(dimx % 2 != 0){
468     return NULL;
469   }
470   const int newy = dimx / 2;
471   const int newx = dimy * 2;
472   struct ncplane_options nopts = {
473     .y = absy,
474     .x = absx,
475     .rows = newy,
476     .cols = newx,
477     .userptr = n->userptr,
478     .name = "copy",
479   };
480   ncplane* newp = ncplane_create(n, &nopts);
481   return newp;
482 }
483 
ncplane_rotate_cw(ncplane * n)484 int ncplane_rotate_cw(ncplane* n){
485   ncplane* newp = rotate_plane(n);
486   if(newp == NULL){
487     return -1;
488   }
489   unsigned dimy, dimx;
490   ncplane_dim_yx(n, &dimy, &dimx);
491   int centy, centx;
492   ncplane_center_abs(n, &centy, &centx);
493   // the topmost row consists of the leftmost two columns. the rightmost column
494   // of the topmost row consists of the top half of the top two leftmost cells.
495   // the penultimate column of the topmost row consists of the bottom half of
496   // the top two leftmost cells. work from the bottom up on the source, so we
497   // can copy to the top row from the left to the right.
498   int targx, targy = 0;
499   for(unsigned x = 0 ; x < dimx ; x += 2){
500     targx = 0;
501     for(int y = (int)dimy - 1 ; y >= 0 ; --y){
502       if(rotate_2x1_cw(n, newp, y, x, targy, targx)){
503         ncplane_destroy(newp);
504         return -1;
505       }
506       targx += 2;
507     }
508     ++targy;
509   }
510   int ret = rotate_merge(n, newp);
511   ret |= ncplane_destroy(newp);
512   return ret;
513 }
514 
ncplane_rotate_ccw(ncplane * n)515 int ncplane_rotate_ccw(ncplane* n){
516   ncplane* newp = rotate_plane(n);
517   if(newp == NULL){
518     return -1;
519   }
520   unsigned dimy, dimx, targdimy, targdimx;
521   ncplane_dim_yx(n, &dimy, &dimx);
522   ncplane_dim_yx(newp, &targdimy, &targdimx);
523   int x = (int)dimx - 2;
524   int y;
525   // Each row of the target plane is taken from a column of the source plane.
526   // As the target row grows (down), the source column shrinks (moves left).
527   for(unsigned targy = 0 ; targy < targdimy ; ++targy){
528     y = 0;
529     for(unsigned targx = 0 ; targx < targdimx ; targx += 2){
530       if(rotate_2x1_ccw(n, newp, y, x, targy, targx)){
531         ncplane_destroy(newp);
532         return -1;
533       }
534       ++y;
535     }
536     x -= 2;
537   }
538   int ret = rotate_merge(n, newp);
539   ret |= ncplane_destroy(newp);
540   return ret;
541 }
542 
543 #ifdef USE_QRCODEGEN
544 #include <qrcodegen/qrcodegen.h>
545 #define QR_BASE_SIZE 17
546 #define PER_QR_VERSION 4
547 
548 static inline unsigned
qrcode_rows(unsigned version)549 qrcode_rows(unsigned version){
550   return QR_BASE_SIZE + (version * PER_QR_VERSION);
551 }
552 
553 static inline unsigned
qrcode_cols(unsigned version)554 qrcode_cols(unsigned version){
555   return QR_BASE_SIZE + (version * PER_QR_VERSION);
556 }
557 
ncplane_qrcode(ncplane * n,unsigned * ymax,unsigned * xmax,const void * data,size_t len)558 int ncplane_qrcode(ncplane* n, unsigned* ymax, unsigned* xmax, const void* data, size_t len){
559   const ncblitter_e blitfxn = NCBLIT_2x1;
560   const int MAX_QR_VERSION = 40; // QR library only supports up to 40
561   if(*ymax <= 0 || *xmax <= 0){
562     return -1;
563   }
564   if(len == 0){
565     return -1;
566   }
567   const int starty = n->y;
568   const int startx = n->x;
569   if(*xmax > n->lenx - startx){
570     return -1;
571   }
572   if(*ymax > n->leny - starty){
573     return -1;
574   }
575   if(*ymax < qrcode_rows(1)){
576     return -1;
577   }
578   if(*xmax < qrcode_cols(1)){
579     return -1;
580   }
581   const int availsquare = *ymax * 2 < *xmax ? *ymax * 2 : *xmax;
582   int roomforver = (availsquare - QR_BASE_SIZE) / PER_QR_VERSION;
583   if(roomforver > MAX_QR_VERSION){
584     roomforver = MAX_QR_VERSION;
585   }
586   const size_t bsize = qrcodegen_BUFFER_LEN_FOR_VERSION(roomforver);
587   if(bsize < len){
588     return -1;
589   }
590   uint8_t* src = malloc(bsize);
591   uint8_t* dst = malloc(bsize);
592   if(src == NULL || dst == NULL){
593     free(src);
594     free(dst);
595     return -1;
596   }
597   unsigned r, g, b;
598   // FIXME default might not be all-white
599   if(ncplane_fg_default_p(n)){
600     r = g = b = 0xff;
601   }else{
602     ncplane_fg_rgb8(n, &r, &g, &b);
603   }
604   memcpy(src, data, len);
605   int ret = -1;
606   int yscale, xscale;
607   if(qrcodegen_encodeBinary(src, len, dst, qrcodegen_Ecc_HIGH, 1, roomforver, qrcodegen_Mask_AUTO, true)){
608     const int square = qrcodegen_getSize(dst);
609     uint32_t* rgba = malloc(square * square * sizeof(uint32_t));
610     if(rgba){
611       for(int y = starty ; y < starty + square ; ++y){
612         for(int x = startx ; x < startx + square ; ++x){
613           const bool pixel = qrcodegen_getModule(dst, x, y);
614           ncpixel_set_a(&rgba[y * square + x], 0xff);
615           ncpixel_set_rgb8(&rgba[y * square + x], r * pixel, g * pixel, b * pixel);
616         }
617       }
618       struct ncvisual* ncv = ncvisual_from_rgba(rgba, square, square * sizeof(uint32_t), square);
619       free(rgba);
620       if(ncv){
621         ret = square;
622         struct ncvisual_options vopts = {
623           .n = n,
624           .blitter = blitfxn,
625         };
626         if(ncvisual_blit(ncplane_notcurses(n), ncv, &vopts) == n){
627           ret = square;
628         }
629         ncvgeom geom;
630         ncvisual_geom(ncplane_notcurses(n), NULL, &vopts, &geom);
631         yscale = geom.scaley;
632         xscale = geom.scalex;
633       }
634       ncvisual_destroy(ncv);
635     }
636   }
637   free(src);
638   free(dst);
639   if(ret > 0){
640     ret = (ret - QR_BASE_SIZE) / PER_QR_VERSION;
641     *ymax = qrcode_rows(ret) / yscale;
642     *xmax = qrcode_cols(ret) / xscale;
643     return ret;
644   }
645   return -1;
646 }
647 #else
ncplane_qrcode(ncplane * n,unsigned * ymax,unsigned * xmax,const void * data,size_t len)648 int ncplane_qrcode(ncplane* n, unsigned* ymax, unsigned* xmax, const void* data, size_t len){
649   (void)n;
650   (void)ymax;
651   (void)xmax;
652   (void)data;
653   (void)len;
654   return -1;
655 }
656 #endif
657