1 #include "main.h"
2 #include "lib/visual-details.h"
3 #include <vector>
4 #include <cmath>
5 
6 // verify results for extrinsic geometries with NULL or default vopts
default_visual_extrinsics(const notcurses * nc,const ncvgeom & g)7 void default_visual_extrinsics(const notcurses* nc, const ncvgeom& g) {
8   CHECK(0 == g.pixy);
9   CHECK(0 == g.pixx);
10   CHECK(nc->tcache.cellpxy == g.cdimy);
11   CHECK(nc->tcache.cellpxx == g.cdimx);
12   CHECK(1 <= g.scaley);
13   CHECK(1 <= g.scalex);
14   CHECK(0 == g.rpixy);
15   CHECK(0 == g.rpixx);
16   CHECK(0 <= g.maxpixely);
17   CHECK(0 <= g.maxpixelx);
18   // we never use pixel by default, and must not revolve to default
19   CHECK(NCBLIT_PIXEL != g.blitter);
20   CHECK(NCBLIT_DEFAULT != g.blitter);
21 }
22 
23 TEST_CASE("Visual") {
24   auto nc_ = testing_notcurses();
25   REQUIRE(nullptr != nc_);
26   ncplane* ncp_ = notcurses_stdplane(nc_);
27   REQUIRE(ncp_);
28   auto n_ = notcurses_stdplane(nc_);
29   REQUIRE(n_);
30 
31   // check that we properly populate RGB + A -> RGBA
32   SUBCASE("VisualFromRGBPacked") {
33     unsigned char rgb[] = "\x88\x77\x66\x55\x44\x33\x22\x11\x00\x99\xaa\xbb";
34     unsigned char alpha = 0xff;
35     auto ncv = ncvisual_from_rgb_packed(rgb, 2, 6, 2, alpha);
36     REQUIRE(nullptr != ncv);
37     for(int y = 0 ; y < 2 ; ++y){
38       for(int x = 0 ; x < 2 ; ++x){
39         uint32_t p;
40         CHECK(0 == ncvisual_at_yx(ncv, y, x, &p));
41         CHECK(ncpixel_r(p) == rgb[y * 6 + x * 3]);
42         CHECK(ncpixel_g(p) == rgb[y * 6 + x * 3 + 1]);
43         CHECK(ncpixel_b(p) == rgb[y * 6 + x * 3 + 2]);
44         CHECK(ncpixel_a(p) == alpha);
45       }
46     }
47     ncvisual_destroy(ncv);
48   }
49 
50   // ncvisual_geom() with a NULL nc
51   SUBCASE("VisualIntrinsicGeometry") {
52     std::vector<uint32_t> v(20, 0xfffffffflu);
53     auto ncv = ncvisual_from_rgba(v.data(), 2, 10 * sizeof(decltype(v)::value_type), 10);
54     REQUIRE(nullptr != ncv);
55     ncvgeom g{};
56     CHECK(0 == ncvisual_geom(nullptr, ncv, nullptr, &g));
57     ncvisual_destroy(ncv);
58     CHECK(2 == g.pixy);
59     CHECK(10 == g.pixx);
60     CHECK(0 == g.cdimy);
61     CHECK(0 == g.cdimx);
62     CHECK(0 == g.rpixy);
63     CHECK(0 == g.rpixx);
64     CHECK(0 == g.scaley);
65     CHECK(0 == g.scalex);
66     CHECK(0 == g.maxpixely);
67     CHECK(0 == g.maxpixelx);
68     CHECK(NCBLIT_DEFAULT == g.blitter);
69   }
70 
71   // ncvisual_geom() with a NULL ncvisual and NULL visual_options
72   SUBCASE("VisualExtrinsicGeometryNULL") {
73     ncvgeom g{};
74     CHECK(0 == ncvisual_geom(nc_, nullptr, nullptr, &g));
75     default_visual_extrinsics(nc_, g);
76   }
77 
78   // ncvisual_geom() with a NULL ncvisual and default visual_options
79   SUBCASE("VisualExtrinsicGeometryDefault") {
80     ncvgeom g{};
81     struct ncvisual_options vopts{};
82     CHECK(0 == ncvisual_geom(nc_, nullptr, &vopts, &g));
83     default_visual_extrinsics(nc_, g);
84   }
85 
86   // ncvisual_geom() with a NULL ncvisual and NCBLIT_PIXEL requested
87   SUBCASE("VisualExtrinsicGeometryPixel") {
88     ncvgeom g{};
89     struct ncvisual_options vopts{};
90     vopts.blitter = NCBLIT_PIXEL;
91     CHECK(0 == ncvisual_geom(nc_, nullptr, &vopts, &g));
92     CHECK(0 == g.pixy);
93     CHECK(0 == g.pixx);
94     CHECK(nc_->tcache.cellpxy == g.cdimy);
95     CHECK(nc_->tcache.cellpxx == g.cdimx);
96     if(notcurses_canpixel(nc_)){
97       CHECK(g.cdimy == g.scaley);
98       CHECK(g.cdimx == g.scalex);
99       CHECK(nc_->tcache.sixel_maxy <= g.maxpixely);
100       CHECK(nc_->tcache.sixel_maxx <= g.maxpixelx);
101     }else{
102       CHECK(1 <= g.scaley);
103       CHECK(1 <= g.scalex);
104       CHECK(0 == g.maxpixely);
105       CHECK(0 == g.maxpixelx);
106     }
107     CHECK(0 == g.rpixy);
108     CHECK(0 == g.rpixx);
109     CHECK(NCBLIT_DEFAULT != g.blitter); // we must not revolve to default
110   }
111 
112   // build a simple ncvisual and check the calculated geometries for 1x1
113   // cell blitting in the absence of scaling
114   SUBCASE("VisualCellGeometryNoScaling") {
115     std::vector<uint32_t> v(80, 0xfffffffflu);
116     auto ncv = ncvisual_from_rgba(v.data(), 8, 10 * sizeof(decltype(v)::value_type), 10);
117     REQUIRE(nullptr != ncv);
118     struct ncvisual_options vopts{};
119     ncvgeom g{};
120     vopts.blitter = NCBLIT_1x1;
121     CHECK(0 == ncvisual_geom(nc_, ncv, &vopts, &g));
122     ncvisual_destroy(ncv);
123     CHECK(8 == g.pixy);
124     CHECK(10 == g.pixx);
125     CHECK(1 == g.scaley);
126     CHECK(1 == g.scalex);
127     CHECK(8 == g.rpixy);
128     CHECK(10 == g.rpixx);
129     CHECK(8 == g.rcelly);
130     CHECK(10 == g.rcellx);
131     CHECK(NCBLIT_1x1 == g.blitter);
132   }
133 
134   // build a square ncvisual and check the calculated geometries for 1x1
135   // cell blitting with scaling
136   SUBCASE("VisualCellGeometryScaling") {
137     std::vector<uint32_t> v(100, 0xfffffffflu);
138     auto ncv = ncvisual_from_rgba(v.data(), 10, 10 * sizeof(decltype(v)::value_type), 10);
139     REQUIRE(nullptr != ncv);
140     struct ncvisual_options vopts{};
141     ncvgeom g{};
142     vopts.blitter = NCBLIT_1x1;
143     vopts.scaling = NCSCALE_SCALE;
144     CHECK(0 == ncvisual_geom(nc_, ncv, &vopts, &g));
145     ncvisual_destroy(ncv);
146     unsigned dimy, dimx;
147     ncplane_dim_yx(n_, &dimy, &dimx);
148     unsigned mindim = dimy < dimx ? dimy : dimx;
149     CHECK(10 == g.pixy);
150     CHECK(10 == g.pixx);
151     CHECK(1 == g.scaley);
152     CHECK(1 == g.scalex);
153     CHECK(mindim == g.rpixy);
154     CHECK(mindim == g.rpixx);
155     CHECK(mindim == g.rcelly);
156     CHECK(mindim == g.rcellx);
157     CHECK(NCBLIT_1x1 == g.blitter);
158   }
159 
160   // build a square ncvisual and check the calculated geometries for 1x1
161   // cell blitting with stretching
162   SUBCASE("VisualCellGeometryStretching") {
163     std::vector<uint32_t> v(100, 0xfffffffflu);
164     auto ncv = ncvisual_from_rgba(v.data(), 10, 10 * sizeof(decltype(v)::value_type), 10);
165     REQUIRE(nullptr != ncv);
166     struct ncvisual_options vopts{};
167     ncvgeom g{};
168     vopts.blitter = NCBLIT_1x1;
169     vopts.scaling = NCSCALE_STRETCH;
170     CHECK(0 == ncvisual_geom(nc_, ncv, &vopts, &g));
171     ncvisual_destroy(ncv);
172     unsigned dimy, dimx;
173     ncplane_dim_yx(n_, &dimy, &dimx);
174     CHECK(10 == g.pixy);
175     CHECK(10 == g.pixx);
176     CHECK(1 == g.scaley);
177     CHECK(1 == g.scalex);
178     CHECK(dimy == g.rpixy);
179     CHECK(dimx == g.rpixx);
180     CHECK(dimy == g.rcelly);
181     CHECK(dimx == g.rcellx);
182     CHECK(NCBLIT_1x1 == g.blitter);
183   }
184 
185   // check that we properly populate RGB + A -> RGBA from 35x4 (see #1806)
186   SUBCASE("VisualFromRGBPacked35x4") {
187     unsigned char rgb[4 * 35 * 3] = "";
188     unsigned char alpha = 0xff;
189     auto ncv = ncvisual_from_rgb_packed(rgb, 4, 35 * 3, 35, alpha);
190     REQUIRE(nullptr != ncv);
191     for(int y = 0 ; y < 4 ; ++y){
192       for(int x = 0 ; x < 35 ; ++x){
193         uint32_t p;
194         CHECK(0 == ncvisual_at_yx(ncv, y, x, &p));
195         CHECK(ncpixel_r(p) == rgb[y * 6 + x * 3]);
196         CHECK(ncpixel_g(p) == rgb[y * 6 + x * 3 + 1]);
197         CHECK(ncpixel_b(p) == rgb[y * 6 + x * 3 + 2]);
198         CHECK(ncpixel_a(p) == alpha);
199       }
200     }
201     ncvisual_destroy(ncv);
202   }
203 
204   // check that we properly populate RGBx + A -> RGBA
205   SUBCASE("VisualFromRGBxPacked") {
206     unsigned char rgb[] = "\x88\x77\x66\x12\x55\x44\x33\x10\x22\x11\x00\xdd\x99\xaa\xbb\xcc";
207     unsigned char alpha = 0xff;
208     auto ncv = ncvisual_from_rgb_loose(rgb, 2, 8, 2, alpha);
209     REQUIRE(nullptr != ncv);
210     for(int y = 0 ; y < 2 ; ++y){
211       for(int x = 0 ; x < 2 ; ++x){
212         uint32_t p;
213         CHECK(0 == ncvisual_at_yx(ncv, y, x, &p));
214         CHECK(ncpixel_r(p) == rgb[y * 8 + x * 4]);
215         CHECK(ncpixel_g(p) == rgb[y * 8 + x * 4 + 1]);
216         CHECK(ncpixel_b(p) == rgb[y * 8 + x * 4 + 2]);
217         CHECK(ncpixel_a(p) == alpha);
218       }
219     }
220     ncvisual_destroy(ncv);
221   }
222 
223   // resize followed by rotate, see #1800
224   SUBCASE("ResizeThenRotateFromMemory") {
225     unsigned char rgb[90];
226     memset(rgb, 0, sizeof(rgb));
227     auto ncv = ncvisual_from_rgb_packed(rgb, 3, 10 * 3, 10, 0xff);
228     REQUIRE(nullptr != ncv);
229     struct ncvisual_options vopts{};
230     vopts.x = NCALIGN_CENTER;
231     vopts.y = NCALIGN_CENTER;
232     vopts.n = n_;
233     vopts.flags |= NCVISUAL_OPTION_HORALIGNED | NCVISUAL_OPTION_VERALIGNED
234                     | NCVISUAL_OPTION_CHILDPLANE;
235     auto p = ncvisual_blit(nc_, ncv, &vopts);
236     REQUIRE(nullptr != p);
237     CHECK(0 == notcurses_render(nc_));
238     CHECK(0 == ncplane_destroy(p));
239     CHECK(0 == ncvisual_resize(ncv, 20, 20));
240     p = ncvisual_blit(nc_, ncv, &vopts);
241     REQUIRE(nullptr != p);
242     CHECK(0 == notcurses_render(nc_));
243     CHECK(0 == ncplane_destroy(p));
244     CHECK(0 == ncvisual_rotate(ncv, M_PI / 2));
245     p = ncvisual_blit(nc_, ncv, &vopts);
246     REQUIRE(nullptr != p);
247     CHECK(0 == notcurses_render(nc_));
248     CHECK(0 == ncplane_destroy(p));
249     ncvisual_destroy(ncv);
250   }
251 
252   // check that NCVISUAL_OPTION_HORALIGNED works in all three cases
253   SUBCASE("VisualAligned") {
254     const uint32_t pixels[4] = { htole(0xffff0000), htole(0xff00ff00), htole(0xff0000ff), htole(0xffffffff) };
255     ncvisual_options vopts = {
256       .n = n_,
257       .scaling = NCSCALE_NONE,
258       .y = 0,
259       .x = NCALIGN_LEFT,
260       .begy = 0,
261       .begx = 0,
262       .leny = 2,
263       .lenx = 2,
264       .blitter = NCBLIT_1x1,
265       .flags = NCVISUAL_OPTION_HORALIGNED | NCVISUAL_OPTION_CHILDPLANE,
266       .transcolor = 0,
267       .pxoffy = 0, .pxoffx = 0,
268     };
269     auto ncv = ncvisual_from_rgba(pixels, 2, 2 * sizeof(*pixels), 2);
270     REQUIRE(nullptr != ncv);
271     auto n = ncvisual_blit(nc_, ncv, &vopts);
272     REQUIRE(nullptr != n);
273     CHECK(0 == notcurses_render(nc_));
274     ncvisual_destroy(ncv);
275     CHECK(0 == ncplane_destroy(n));
276   }
277 
278   // check that leny/lenx properly limit the output, new plane
279   SUBCASE("Partial") {
280     auto y = 10;
281     auto x = 10;
282     std::vector<uint32_t> v(x * y, htole(0xe61c28ff));
283     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
284     REQUIRE(nullptr != ncv);
285     struct ncvisual_options vopts = {
286       .n = n_,
287       .scaling = NCSCALE_NONE,
288       .y = 0, .x = 0,
289       .begy = 0, .begx = 0,
290       .leny = 5, .lenx = 8,
291       .blitter = NCBLIT_1x1,
292       .flags = NCVISUAL_OPTION_CHILDPLANE,
293       .transcolor = 0,
294       .pxoffy = 0, .pxoffx = 0,
295     };
296     auto n = ncvisual_blit(nc_, ncv, &vopts);
297     REQUIRE(nullptr != n);
298     CHECK(5 == ncplane_dim_y(n));
299     CHECK(8 == ncplane_dim_x(n));
300     ncvisual_destroy(ncv);
301     CHECK(0 == ncplane_destroy(n));
302   }
303 
304   // ensure that NCSCALE_STRETCH gives us a full plane, and that we write
305   // everywhere within that plane
306   SUBCASE("Stretch") {
307     std::vector<uint32_t> v(1, htole(0xff1c28ff));
308     unsigned dimy, dimx;
309     ncplane_dim_yx(ncp_, &dimy, &dimx);
310     auto ncv = ncvisual_from_rgba(v.data(), 1, sizeof(decltype(v)::value_type), 1);
311     REQUIRE(nullptr != ncv);
312     struct ncvisual_options vopts{};
313     vopts.n = n_;
314     vopts.scaling = NCSCALE_STRETCH;
315     vopts.blitter = NCBLIT_1x1;
316     vopts.flags = NCVISUAL_OPTION_CHILDPLANE;
317     auto n = ncvisual_blit(nc_, ncv, &vopts);
318     CHECK(0 == notcurses_render(nc_));
319     REQUIRE(nullptr != n);
320     CHECK(dimy == ncplane_dim_y(n));
321     CHECK(dimx == ncplane_dim_x(n));
322     ncvisual_destroy(ncv);
323     for(unsigned y = 0 ; y < dimy ; ++y){
324       for(unsigned x = 0 ; x < dimx ; ++x){
325         uint16_t stylemask;
326         uint64_t channels;
327         auto c = ncplane_at_yx(n, y, x, &stylemask, &channels);
328         CHECK(0 == strcmp(c, " "));
329         free(c);
330         CHECK(ncchannels_bg_rgb(channels) == 0xff281c);
331         CHECK(stylemask == 0);
332       }
333     }
334     CHECK(0 == ncplane_destroy(n));
335   }
336 
337   // partial limit via len, offset via y/x, new plane
338   SUBCASE("PartialOffset") {
339     auto y = 10;
340     auto x = 10;
341     std::vector<uint32_t> v(x * y, htole(0xe61c28ff));
342     auto ncv = ncvisual_from_rgba(v.data(), y, sizeof(decltype(v)::value_type) * x, x);
343     REQUIRE(nullptr != ncv);
344     struct ncvisual_options vopts = {
345       .n = nullptr,
346       .scaling = NCSCALE_NONE,
347       .y = 2, .x = 4,
348       .begy = 0, .begx = 0,
349       .leny = 5, .lenx = 8,
350       .blitter = NCBLIT_1x1,
351       .flags = 0, .transcolor = 0,
352       .pxoffy = 0, .pxoffx = 0,
353     };
354     auto n = ncvisual_blit(nc_, ncv, &vopts);
355     REQUIRE(nullptr != n);
356     CHECK(5 == ncplane_dim_y(n));
357     CHECK(8 == ncplane_dim_x(n));
358     CHECK(2 == ncplane_y(n));
359     CHECK(4 == ncplane_x(n));
360     ncvisual_destroy(ncv);
361     CHECK(0 == ncplane_destroy(n));
362   }
363 
364   SUBCASE("InflateBitmap") {
365     const uint32_t pixels[4] = { htole(0xffff00ff), htole(0xff00ffff), htole(0xff0000ff), htole(0xffffffff) };
366     auto ncv = ncvisual_from_rgba(pixels, 2, 8, 2);
367     REQUIRE(ncv);
368     ncvisual_options vopts = {
369       .n = nullptr,
370       .scaling = NCSCALE_NONE,
371       .y = 0, .x = 0,
372       .begy = 0, .begx = 0,
373       .leny = 0, .lenx = 0,
374       .blitter = NCBLIT_1x1,
375       .flags = 0, .transcolor = 0,
376       .pxoffy = 0, .pxoffx = 0,
377     };
378     auto newn = ncvisual_blit(nc_, ncv, &vopts);
379     CHECK(0 == notcurses_render(nc_));
380     CHECK(0 == ncvisual_resize_noninterpolative(ncv, ncv->pixy * 3, ncv->pixx * 3));
381     CHECK(6 == ncv->pixy);
382     CHECK(6 == ncv->pixx);
383     for(int y = 0 ; y < 3 ; ++y){
384       for(int x = 0 ; x < 3 ; ++x){
385         CHECK(pixels[0] == ncv->data[y * ncv->rowstride / 4 + x]);
386       }
387       for(int x = 3 ; x < 6 ; ++x){
388         CHECK(pixels[1] == ncv->data[y * ncv->rowstride / 4 + x]);
389       }
390     }
391     for(int y = 3 ; y < 6 ; ++y){
392       for(int x = 0 ; x < 3 ; ++x){
393         CHECK(pixels[2] == ncv->data[y * ncv->rowstride / 4 + x]);
394       }
395       for(int x = 3 ; x < 6 ; ++x){
396         CHECK(pixels[3] == ncv->data[y * ncv->rowstride / 4 + x]);
397       }
398     }
399     REQUIRE(newn);
400     auto enewn = ncvisual_blit(nc_, ncv, &vopts);
401     unsigned newy, newx;
402     ncplane_dim_yx(enewn, &newy, &newx);
403     CHECK(6 == newy);
404     CHECK(6 == newx);
405     CHECK(0 == notcurses_render(nc_));
406     CHECK(0 == ncplane_destroy(newn));
407     CHECK(0 == ncplane_destroy(enewn));
408     ncvisual_destroy(ncv);
409   }
410 
411   SUBCASE("LoadRGBAFromMemory") {
412     unsigned dimy, dimx;
413     ncplane_dim_yx(ncp_, &dimy, &dimx);
414     // alpha, then b, g, r
415     std::vector<uint32_t> rgba(dimx * dimy * 2, htole(0xff88bbccull));
416     auto ncv = ncvisual_from_rgba(rgba.data(), dimy * 2, dimx * 4, dimx);
417     REQUIRE(ncv);
418     struct ncvisual_options opts{};
419     opts.blitter = NCBLIT_1x1;
420     opts.n = ncp_;
421     CHECK(ncp_ == ncvisual_blit(nc_, ncv, &opts));
422     CHECK(0 == notcurses_render(nc_));
423     for(unsigned y = 0 ; y < dimy ; ++y){
424       for(unsigned x = 0 ; x < dimx ; ++x){
425         uint16_t stylemask;
426         uint64_t channels;
427         auto c = ncplane_at_yx(ncp_, y, x, &stylemask, &channels);
428         CHECK(0 == strcmp(c, " "));
429         free(c);
430         CHECK(htole(ncchannels_bg_rgb(channels)) == htole(0xccbb88));
431         CHECK(stylemask == 0);
432       }
433     }
434     ncvisual_destroy(ncv);
435     CHECK(0 == notcurses_render(nc_));
436   }
437 
438   SUBCASE("LoadBGRAFromMemory") {
439     unsigned dimy, dimx;
440     ncplane_dim_yx(ncp_, &dimy, &dimx);
441     // A should be at the highest memory address, which would be the most
442     // significant byte on little-endian. then r, g, b.
443     std::vector<uint32_t> rgba(dimx * dimy * 2, htole(0xff88bbcc));
444     auto ncv = ncvisual_from_bgra(rgba.data(), dimy * 2, dimx * 4, dimx);
445     REQUIRE(ncv);
446     struct ncvisual_options opts{};
447     opts.blitter = NCBLIT_1x1;
448     opts.n = ncp_;
449     CHECK(nullptr != ncvisual_blit(nc_, ncv, &opts));
450     CHECK(0 == notcurses_render(nc_));
451     for(unsigned y = 0 ; y < dimy ; ++y){
452       for(unsigned x = 0 ; x < dimx ; ++x){
453         uint16_t stylemask;
454         uint64_t channels;
455         auto c = ncplane_at_yx(ncp_, y, x, &stylemask, &channels);
456         CHECK(0 == strcmp(c, " "));
457         free(c);
458         CHECK(ncchannels_bg_rgb(channels) == 0x88bbcc);
459         CHECK(stylemask == 0);
460       }
461     }
462     ncvisual_destroy(ncv);
463     CHECK(0 == notcurses_render(nc_));
464   }
465 
466   // write a checkerboard pattern and verify the NCBLIT_2x1 output
467   SUBCASE("Dualblitter") {
468     if(notcurses_canutf8(nc_)){
469       constexpr int DIMY = 10;
470       constexpr int DIMX = 11; // odd number to get checkerboard effect
471       auto rgba = new uint32_t[DIMY * DIMX];
472       for(int i = 0 ; i < DIMY * DIMX ; ++i){
473         CHECK(0 == ncpixel_set_a(&rgba[i], 0xff));
474         if(i % 2){
475           CHECK(0 == ncpixel_set_b(&rgba[i], 0xff));
476           CHECK(0 == ncpixel_set_r(&rgba[i], 0));
477         }else{
478           CHECK(0 == ncpixel_set_r(&rgba[i], 0xff));
479           CHECK(0 == ncpixel_set_b(&rgba[i], 0));
480         }
481         CHECK(0 == ncpixel_set_g(&rgba[i], 0));
482       }
483       auto ncv = ncvisual_from_rgba(rgba, DIMY, DIMX * sizeof(uint32_t), DIMX);
484       REQUIRE(nullptr != ncv);
485       struct ncvisual_options vopts{};
486       vopts.n = n_;
487       vopts.blitter = NCBLIT_2x1;
488       vopts.flags = NCVISUAL_OPTION_NODEGRADE;
489       CHECK(n_ == ncvisual_blit(nc_, ncv, &vopts));
490       CHECK(0 == notcurses_render(nc_));
491       for(int y = 0 ; y < DIMY / 2 ; ++y){
492         for(int x = 0 ; x < DIMX ; ++x){
493           uint16_t stylemask;
494           uint64_t channels;
495           char* egc = notcurses_at_yx(nc_, y, x, &stylemask, &channels);
496           REQUIRE(nullptr != egc);
497           CHECK((htole(rgba[y * 2 * DIMX + x]) & 0xffffff) == ncchannels_bg_rgb(channels));
498           CHECK((htole(rgba[(y * 2 + 1) * DIMX + x]) & 0xffffff) == ncchannels_fg_rgb(channels));
499           free(egc);
500         }
501       }
502       delete[] rgba;
503       ncvisual_destroy(ncv);
504     }
505   }
506 
507   // write a checkerboard pattern and verify the NCBLIT_2x2 output
508   SUBCASE("Quadblitter") {
509     if(notcurses_canquadrant(nc_)){
510       constexpr int DIMY = 10;
511       constexpr int DIMX = 11; // odd number to get checkerboard effect
512       auto rgba = new uint32_t[DIMY * DIMX];
513       for(int i = 0 ; i < DIMY * DIMX ; ++i){
514         CHECK(0 == ncpixel_set_a(&rgba[i], 0xff));
515         if(i % 2){
516           CHECK(0 == ncpixel_set_b(&rgba[i], 0xff));
517           CHECK(0 == ncpixel_set_g(&rgba[i], 0));
518         }else{
519           CHECK(0 == ncpixel_set_g(&rgba[i], 0xff));
520           CHECK(0 == ncpixel_set_b(&rgba[i], 0));
521         }
522         CHECK(0 == ncpixel_set_r(&rgba[i], 0));
523       }
524       auto ncv = ncvisual_from_rgba(rgba, DIMY, DIMX * sizeof(uint32_t), DIMX);
525       REQUIRE(nullptr != ncv);
526       struct ncvisual_options vopts{};
527       vopts.n = n_;
528       vopts.blitter = NCBLIT_2x2;
529       vopts.flags = NCVISUAL_OPTION_NODEGRADE;
530       CHECK(n_ == ncvisual_blit(nc_, ncv, &vopts));
531       CHECK(0 == notcurses_render(nc_));
532       for(int y = 0 ; y < DIMY / 2 ; ++y){
533         for(int x = 0 ; x < DIMX / 2 ; ++x){
534           uint16_t stylemask;
535           uint64_t channels;
536           char* egc = notcurses_at_yx(nc_, y, x, &stylemask, &channels);
537           REQUIRE(nullptr != egc);
538           CHECK((htole(rgba[(y * 2 * DIMX) + (x * 2)]) & 0xffffff) == ncchannels_fg_rgb(channels));
539           CHECK((htole(rgba[(y * 2 + 1) * DIMX + (x * 2) + 1]) & 0xffffff) == ncchannels_fg_rgb(channels));
540           free(egc);
541         }
542       }
543       delete[] rgba;
544       ncvisual_destroy(ncv);
545     }
546   }
547 
548   // close-in verification of each quadblitter output EGC
549   SUBCASE("QuadblitterEGCs") {
550     if(notcurses_canquadrant(nc_)){
551       // there are 16 configurations, each mapping four (2x2) pixels
552       int DIMX = 32;
553       int DIMY = 2;
554       auto rgba = new uint32_t[DIMY * DIMX];
555       memset(rgba, 0, sizeof(*rgba) * DIMY * DIMX);
556       // the top has 4 configurations of 4 each, each being 2 columns
557       for(int top = 0 ; top < 4 ; ++top){
558         for(int idx = 0 ; idx < 4 ; ++idx){
559           const int itop = (top * 4 + idx) * 2; // index of first column
560           CHECK(0 == ncpixel_set_a(&rgba[itop], 0xff));
561           CHECK(0 == ncpixel_set_a(&rgba[itop + 1], 0xff));
562           if(top == 1 || top == 3){
563             CHECK(0 == ncpixel_set_r(&rgba[itop], 0xff));
564           }
565           if(top == 2 || top == 3){
566             CHECK(0 == ncpixel_set_r(&rgba[itop + 1], 0xff));
567           }
568         }
569       }
570       for(int bot = 0 ; bot < 4 ; ++bot){
571         for(int idx = 0 ; idx < 4 ; ++idx){
572           const int ibot = (bot * 4 + idx) * 2 + DIMX;
573           CHECK(0 == ncpixel_set_a(&rgba[ibot], 0xff));
574           CHECK(0 == ncpixel_set_a(&rgba[ibot + 1], 0xff));
575           if(idx == 1 || idx == 3){
576             CHECK(0 == ncpixel_set_r(&rgba[ibot], 0xff));
577           }
578           if(idx == 2 || idx == 3){
579             CHECK(0 == ncpixel_set_r(&rgba[ibot + 1], 0xff));
580           }
581         }
582       }
583       auto ncv = ncvisual_from_rgba(rgba, DIMY, DIMX * sizeof(uint32_t), DIMX);
584       REQUIRE(nullptr != ncv);
585       struct ncvisual_options vopts{};
586       vopts.n = n_;
587       vopts.blitter = NCBLIT_2x2;
588       vopts.flags = NCVISUAL_OPTION_NODEGRADE;
589       CHECK(n_ == ncvisual_blit(nc_, ncv, &vopts));
590       CHECK(0 == notcurses_render(nc_));
591       for(int y = 0 ; y < DIMY / 2 ; ++y){
592         for(int x = 0 ; x < DIMX / 2 ; ++x){
593           uint16_t stylemask;
594           uint64_t channels;
595           char* egc = notcurses_at_yx(nc_, y, x, &stylemask, &channels);
596           REQUIRE(nullptr != egc);
597   /* FIXME need to match
598   [▀] 00000000 00000000
599   [▜] 00000000 00ff0000
600   [▛] 00000000 00ff0000
601   [▀] 00000000 00ff0000
602   [▟] 00000000 00ff0000
603   [▋] 00ff0000 00000000
604   [▚] 00ff0000 00000000
605   [▙] 00ff0000 00000000
606   [▙] 00000000 00ff0000
607   [▚] 00000000 00ff0000
608   [▋] 00000000 00ff0000
609   [▟] 00ff0000 00000000
610   [▀] 00ff0000 00000000
611   [▛] 00ff0000 00000000
612   [▜] 00ff0000 00000000
613   [▀] 00ff0000 00ff0000
614   */
615           free(egc);
616         }
617       }
618       delete[] rgba;
619       ncvisual_destroy(ncv);
620     }
621   }
622 
623   // quadblitter with all 4 colors equal ought generate space
624   SUBCASE("Quadblitter4Same") {
625     if(notcurses_canquadrant(nc_)){
626       const uint32_t pixels[4] = { htole(0xff605040), htole(0xff605040), htole(0xff605040), htole(0xff605040) };
627       auto ncv = ncvisual_from_rgba(pixels, 2, 2 * sizeof(*pixels), 2);
628       REQUIRE(nullptr != ncv);
629       struct ncvisual_options vopts = {
630         .n = n_,
631         .scaling = NCSCALE_NONE,
632         .y = 0,
633         .x = 0,
634         .begy = 0,
635         .begx = 0,
636         .leny = 0,
637         .lenx = 0,
638         .blitter = NCBLIT_2x2,
639         .flags = NCVISUAL_OPTION_CHILDPLANE,
640         .transcolor = 0,
641         .pxoffy = 0, .pxoffx = 0,
642       };
643       auto ncvp = ncvisual_blit(nc_, ncv, &vopts);
644       REQUIRE(nullptr != ncvp);
645       unsigned dimy, dimx;
646       ncplane_dim_yx(ncvp, &dimy, &dimx);
647       CHECK(1 == dimy);
648       CHECK(1 == dimx);
649       uint16_t stylemask;
650       uint64_t channels;
651       auto egc = ncplane_at_yx(ncvp, 0, 0, &stylemask, &channels);
652       CHECK(0 == strcmp(" ", egc));
653       CHECK(0 == stylemask);
654       CHECK(0x405060 == ncchannels_fg_rgb(channels));
655       CHECK(0x405060 == ncchannels_bg_rgb(channels));
656       free(egc);
657       ncvisual_destroy(ncv);
658       CHECK(0 == notcurses_render(nc_));
659     }
660   }
661 
662   // quadblitter with three pixels equal ought generate three-quarter block
663   SUBCASE("Quadblitter3Same") {
664     if(notcurses_canquadrant(nc_)){
665       const uint32_t pixels[4][4] = {
666         { htole(0xffcccccc), htole(0xff605040), htole(0xff605040), htole(0xff605040) },
667         { htole(0xff605040), htole(0xffcccccc), htole(0xff605040), htole(0xff605040) },
668         { htole(0xff605040), htole(0xff605040), htole(0xffcccccc), htole(0xff605040) },
669         { htole(0xff605040), htole(0xff605040), htole(0xff605040), htole(0xffcccccc) } };
670       const char* egcs[] = { "▟", "▙", "▜", "▛" };
671       for(int i = 0 ; i < 4 ; ++i){
672         auto ncv = ncvisual_from_rgba(pixels[i], 2, 2 * sizeof(**pixels), 2);
673         REQUIRE(nullptr != ncv);
674         struct ncvisual_options vopts = {
675           .n = n_,
676           .scaling = NCSCALE_NONE,
677           .y = 0,
678           .x = 0,
679           .begy = 0,
680           .begx = 0,
681           .leny = 0,
682           .lenx = 0,
683           .blitter = NCBLIT_2x2,
684           .flags = NCVISUAL_OPTION_CHILDPLANE,
685           .transcolor = 0,
686           .pxoffy = 0, .pxoffx = 0,
687         };
688         auto ncvp = ncvisual_blit(nc_, ncv, &vopts);
689         REQUIRE(nullptr != ncvp);
690         unsigned dimy, dimx;
691         ncplane_dim_yx(ncvp, &dimy, &dimx);
692         CHECK(1 == dimy);
693         CHECK(1 == dimx);
694         uint16_t stylemask;
695         uint64_t channels;
696         auto egc = ncplane_at_yx(ncvp, 0, 0, &stylemask, &channels);
697         CHECK(0 == strcmp(egcs[i], egc));
698         CHECK(0 == stylemask);
699         CHECK(0x405060 == ncchannels_fg_rgb(channels));
700         CHECK(0xcccccc == ncchannels_bg_rgb(channels));
701         free(egc);
702         ncvisual_destroy(ncv);
703         CHECK(0 == notcurses_render(nc_));
704       }
705     }
706   }
707 
708   // quadblitter with two sets of two equal pixels
709   SUBCASE("Quadblitter2Pairs") {
710     if(notcurses_canquadrant(nc_)){
711       const uint32_t pixels[6][4] = {
712         { htole(0xffcccccc), htole(0xffcccccc), htole(0xff605040), htole(0xff605040) },
713         { htole(0xffcccccc), htole(0xff605040), htole(0xffcccccc), htole(0xff605040) },
714         { htole(0xffcccccc), htole(0xff605040), htole(0xff605040), htole(0xffcccccc) },
715         { htole(0xff605040), htole(0xffcccccc), htole(0xffcccccc), htole(0xff605040) },
716         { htole(0xff605040), htole(0xffcccccc), htole(0xff605040), htole(0xffcccccc) },
717         { htole(0xff605040), htole(0xff605040), htole(0xffcccccc), htole(0xffcccccc) } };
718       const char* egcs[] = { "▀", "▌", "▚", "▚", "▌", "▀" };
719       for(size_t i = 0 ; i < sizeof(egcs) / sizeof(*egcs) ; ++i){
720         auto ncv = ncvisual_from_rgba(pixels[i], 2, 2 * sizeof(**pixels), 2);
721         REQUIRE(nullptr != ncv);
722         struct ncvisual_options vopts = {
723           .n = n_,
724           .scaling = NCSCALE_NONE,
725           .y = 0,
726           .x = 0,
727           .begy = 0,
728           .begx = 0,
729           .leny = 0,
730           .lenx = 0,
731           .blitter = NCBLIT_2x2,
732           .flags = NCVISUAL_OPTION_CHILDPLANE,
733           .transcolor = 0,
734           .pxoffy = 0, .pxoffx = 0,
735         };
736         auto ncvp = ncvisual_blit(nc_, ncv, &vopts);
737         REQUIRE(nullptr != ncvp);
738         unsigned dimy, dimx;
739         ncplane_dim_yx(ncvp, &dimy, &dimx);
740         CHECK(1 == dimy);
741         CHECK(1 == dimx);
742         uint16_t stylemask;
743         uint64_t channels;
744         auto egc = ncplane_at_yx(ncvp, 0, 0, &stylemask, &channels);
745         CHECK(0 == strcmp(egcs[i], egc));
746         CHECK(0 == stylemask);
747         if(i >= 3){
748           CHECK(0x405060 == ncchannels_fg_rgb(channels));
749           CHECK(0xcccccc == ncchannels_bg_rgb(channels));
750         }else{
751           CHECK(0x405060 == ncchannels_bg_rgb(channels));
752           CHECK(0xcccccc == ncchannels_fg_rgb(channels));
753         }
754         free(egc);
755         ncvisual_destroy(ncv);
756         CHECK(0 == notcurses_render(nc_));
757       }
758     }
759   }
760 
761   // quadblitter with one pair plus two split
762   SUBCASE("Quadblitter1Pair") {
763     if(notcurses_canquadrant(nc_)){
764       const uint32_t pixels[6][4] = {
765         { htole(0xffcccccc), htole(0xff444444), htole(0xff605040), htole(0xff605040) },
766         { htole(0xff444444), htole(0xff605040), htole(0xffcccccc), htole(0xff605040) },
767         { htole(0xffcccccc), htole(0xff605040), htole(0xff605040), htole(0xff444444) },
768         { htole(0xff605040), htole(0xffcccccc), htole(0xff444444), htole(0xff605040) },
769         { htole(0xff605040), htole(0xffeeeeee), htole(0xff605040), htole(0xffcccccc) },
770         { htole(0xff605040), htole(0xff605040), htole(0xffeeeeee), htole(0xffcccccc) } };
771       const char* egcs[] = { "▟", "▜", "▟", "▙", "▌", "▀" };
772       for(size_t i = 0 ; i < sizeof(egcs) / sizeof(*egcs) ; ++i){
773         auto ncv = ncvisual_from_rgba(pixels[i], 2, 2 * sizeof(**pixels), 2);
774         REQUIRE(nullptr != ncv);
775         struct ncvisual_options vopts = {
776           .n = n_,
777           .scaling = NCSCALE_NONE,
778           .y = 0,
779           .x = 0,
780           .begy = 0,
781           .begx = 0,
782           .leny = 0,
783           .lenx = 0,
784           .blitter = NCBLIT_2x2,
785           .flags = NCVISUAL_OPTION_CHILDPLANE,
786           .transcolor = 0,
787           .pxoffy = 0, .pxoffx = 0,
788         };
789         auto ncvp = ncvisual_blit(nc_, ncv, &vopts);
790         REQUIRE(nullptr != ncvp);
791         unsigned dimy, dimx;
792         ncplane_dim_yx(ncvp, &dimy, &dimx);
793         CHECK(1 == dimy);
794         CHECK(1 == dimx);
795         uint16_t stylemask;
796         uint64_t channels;
797         auto egc = ncplane_at_yx(ncvp, 0, 0, &stylemask, &channels);
798         CHECK(0 == strcmp(egcs[i], egc));
799         CHECK(0 == stylemask);
800         if(i > 3){
801           CHECK(0x405060 == ncchannels_fg_rgb(channels));
802           CHECK(0xdddddd == ncchannels_bg_rgb(channels));
803         }else{
804           CHECK(0x424c57 == ncchannels_fg_rgb(channels));
805           CHECK(0xcccccc == ncchannels_bg_rgb(channels));
806         }
807         free(egc);
808         ncvisual_destroy(ncv);
809         CHECK(0 == notcurses_render(nc_));
810       }
811     }
812   }
813 
814   // quadblitter with one pair plus two split
815   SUBCASE("QuadblitterAllDifferent") {
816     if(notcurses_canquadrant(nc_)){
817       const uint32_t pixels[6][4] = {
818         { htole(0xffdddddd), htole(0xff000000), htole(0xff111111), htole(0xff222222) },
819         { htole(0xff000000), htole(0xff111111), htole(0xffdddddd), htole(0xff222222) },
820         { htole(0xff111111), htole(0xffdddddd), htole(0xff000000), htole(0xff222222) },
821         { htole(0xff000000), htole(0xffcccccc), htole(0xff222222), htole(0xffeeeeee) },
822         { htole(0xff222222), htole(0xff000000), htole(0xffeeeeee), htole(0xffcccccc), } };
823       const char* egcs[] = { "▟", "▜", "▙", "▌", "▀" };
824       for(size_t i = 0 ; i < sizeof(egcs) / sizeof(*egcs) ; ++i){
825         auto ncv = ncvisual_from_rgba(pixels[i], 2, 2 * sizeof(**pixels), 2);
826         REQUIRE(nullptr != ncv);
827         struct ncvisual_options vopts = {
828           .n = n_,
829           .scaling = NCSCALE_NONE,
830           .y = 0,
831           .x = 0,
832           .begy = 0,
833           .begx = 0,
834           .leny = 0,
835           .lenx = 0,
836           .blitter = NCBLIT_2x2,
837           .flags = NCVISUAL_OPTION_CHILDPLANE,
838           .transcolor = 0,
839           .pxoffy = 0, .pxoffx = 0,
840         };
841         auto ncvp = ncvisual_blit(nc_, ncv, &vopts);
842         REQUIRE(nullptr != ncvp);
843         unsigned dimy, dimx;
844         ncplane_dim_yx(ncvp, &dimy, &dimx);
845         CHECK(1 == dimy);
846         CHECK(1 == dimx);
847         uint16_t stylemask;
848         uint64_t channels;
849         auto egc = ncplane_at_yx(ncvp, 0, 0, &stylemask, &channels);
850         CHECK(0 == strcmp(egcs[i], egc));
851         CHECK(0 == stylemask);
852         CHECK(0x111111 == ncchannels_fg_rgb(channels));
853         CHECK(0xdddddd == ncchannels_bg_rgb(channels));
854         free(egc);
855         ncvisual_destroy(ncv);
856         CHECK(0 == notcurses_render(nc_));
857       }
858     }
859   }
860 
861   // test NCVISUAL_OPTION_CHILDPLANE + stretch + null alignment
862   SUBCASE("ImageChildScaling") {
863     struct ncplane_options opts = {
864       .y = 0, .x = 0,
865       .rows = 20, .cols = 20,
866       .userptr = nullptr,
867       .name = "parent",
868       .resizecb = nullptr,
869       .flags = 0,
870       .margin_b = 0,
871       .margin_r = 0,
872     };
873     auto parent = ncplane_create(n_, &opts);
874     REQUIRE(parent);
875     struct ncvisual_options vopts = {
876       .n = n_,
877       .scaling = NCSCALE_NONE,
878       .y = 0,
879       .x = 0,
880       .begy = 0, .begx = 0,
881       .leny = 0, .lenx = 0,
882       .blitter = NCBLIT_1x1,
883       .flags = NCVISUAL_OPTION_CHILDPLANE,
884       .transcolor = 0,
885       .pxoffy = 0, .pxoffx = 0,
886     };
887     const uint32_t pixels[16] = {
888       htole(0xffffffff), htole(0xffffffff), htole(0xffc0ffff), htole(0xffffc0ff),
889       htole(0xffc0c0ff), htole(0xffc0c0ff), htole(0xff80c0ff), htole(0xffc080ff),
890       htole(0xff8080ff), htole(0xff8080ff), htole(0xff4080ff), htole(0xff8040ff),
891       htole(0xff4040ff), htole(0xff4040ff), htole(0xffff40ff), htole(0xff40ffff),
892     };
893     auto ncv = ncvisual_from_rgba(pixels, 4, 16, 4);
894     REQUIRE(ncv);
895     auto child = ncvisual_blit(nc_, ncv, &vopts);
896     REQUIRE(child);
897     CHECK(4 == ncplane_dim_y(child));
898     CHECK(4 == ncplane_dim_x(child));
899     CHECK(0 == ncplane_y(child));
900     CHECK(0 == ncplane_x(child));
901     CHECK(0 == notcurses_render(nc_));
902     CHECK(0 == ncplane_destroy(child));
903     vopts.n = parent,
904     vopts.scaling = NCSCALE_STRETCH,
905     vopts.flags = NCVISUAL_OPTION_CHILDPLANE;
906     child = ncvisual_blit(nc_, ncv, &vopts);
907     REQUIRE(child);
908     CHECK(20 == ncplane_dim_y(child));
909     CHECK(20 == ncplane_dim_x(child));
910     CHECK(0 == notcurses_render(nc_));
911     CHECK(0 == ncplane_destroy(parent));
912     CHECK(0 == ncplane_destroy(child));
913     ncvisual_destroy(ncv);
914   }
915 
916   SUBCASE("ImageChildAlignment") {
917     struct ncplane_options opts = {
918       .y = 0, .x = 0,
919       .rows = 5, .cols = 5,
920       .userptr = nullptr,
921       .name = "parent",
922       .resizecb = nullptr,
923       .flags = 0,
924       .margin_b = 0,
925       .margin_r = 0,
926     };
927     auto parent = ncplane_create(n_, &opts);
928     REQUIRE(parent);
929     struct ncvisual_options vopts = {
930       .n = parent,
931       .scaling = NCSCALE_NONE,
932       .y = NCALIGN_CENTER,
933       .x = NCALIGN_CENTER,
934       .begy = 0, .begx = 0,
935       .leny = 0, .lenx = 0,
936       .blitter = NCBLIT_1x1,
937       .flags = NCVISUAL_OPTION_CHILDPLANE |
938                NCVISUAL_OPTION_HORALIGNED |
939                NCVISUAL_OPTION_VERALIGNED,
940       .transcolor = 0,
941       .pxoffy = 0, .pxoffx = 0,
942     };
943     const uint32_t pixels[1] = { htole(0xffffffff) };
944     auto ncv = ncvisual_from_rgba(pixels, 1, 4, 1);
945     REQUIRE(ncv);
946     auto child = ncvisual_blit(nc_, ncv, &vopts);
947     REQUIRE(child);
948     CHECK(1 == ncplane_dim_y(child));
949     CHECK(1 == ncplane_dim_x(child));
950     CHECK(2 == ncplane_y(child));
951     CHECK(2 == ncplane_x(child));
952     ncvisual_destroy(ncv);
953     CHECK(0 == notcurses_render(nc_));
954     CHECK(0 == ncplane_destroy(parent));
955     CHECK(0 == ncplane_destroy(child));
956   }
957 
958   CHECK(!notcurses_stop(nc_));
959 }
960