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, ¢y, ¢x);
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