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