1 #include <stddef.h>
2 #include <inttypes.h>
3 #include "internal.h"
4 
5 static const uint32_t zeroes32;
6 static const unsigned char zeroes[] = "\x00\x00\x00\x00";
7 
8 // linearly interpolate a 24-bit RGB value along each 8-bit channel
9 static inline uint32_t
lerp(uint32_t c0,uint32_t c1,unsigned nointerpolate)10 lerp(uint32_t c0, uint32_t c1, unsigned nointerpolate){
11   unsigned r0, g0, b0, r1, g1, b1;
12   uint32_t ret = 0;
13   ncchannel_rgb8(c0, &r0, &g0, &b0);
14   if(!nointerpolate){
15     ncchannel_rgb8(c1, &r1, &g1, &b1);
16     ncchannel_set_rgb8(&ret, (r0 + r1 + 1) / 2,
17                           (g0 + g1 + 1) / 2,
18                           (b0 + b1 + 1) / 2);
19   }else{
20     ncchannel_set_rgb8(&ret, r0, g0, b0);
21   }
22   return ret;
23 }
24 
25 // linearly interpolate a 24-bit RGB value along each 8-bit channel
26 static inline uint32_t
trilerp(uint32_t c0,uint32_t c1,uint32_t c2,unsigned nointerpolate)27 trilerp(uint32_t c0, uint32_t c1, uint32_t c2, unsigned nointerpolate){
28   uint32_t ret = 0;
29   unsigned r0, g0, b0, r1, g1, b1, r2, g2, b2;
30   ncchannel_rgb8(c0, &r0, &g0, &b0);
31   if(!nointerpolate){
32     ncchannel_rgb8(c1, &r1, &g1, &b1);
33     ncchannel_rgb8(c2, &r2, &g2, &b2);
34     ncchannel_set_rgb8(&ret, (r0 + r1 + r2 + 2) / 3,
35                           (g0 + g1 + g2 + 2) / 3,
36                           (b0 + b1 + b2 + 2) / 3);
37   }else{
38     ncchannel_set_rgb8(&ret, r0, g0, b0);
39   }
40   return ret;
41 }
42 
43 // take a sum over channels, and the sample count, write back lerped channel
44 static inline uint32_t
generalerp(unsigned rsum,unsigned gsum,unsigned bsum,int count)45 generalerp(unsigned rsum, unsigned gsum, unsigned bsum, int count){
46   if(count == 0){
47     assert(0 == rsum);
48     assert(0 == gsum);
49     assert(0 == bsum);
50     return 0;
51   }
52   return NCCHANNEL_INITIALIZER((rsum + (count - 1)) / count,
53                                (gsum + (count - 1)) / count,
54                                (bsum + (count - 1)) / count);
55 }
56 
57 static inline unsigned
rgba_trans_q(const unsigned char * p,uint32_t transcolor)58 rgba_trans_q(const unsigned char* p, uint32_t transcolor){
59   uint32_t q;
60   memcpy(&q, p, sizeof(q));
61   return rgba_trans_p(q, transcolor);
62 }
63 
64 // Retarded RGBA blitter (ASCII only).
65 static inline int
tria_blit_ascii(ncplane * nc,int linesize,const void * data,int leny,int lenx,const blitterargs * bargs)66 tria_blit_ascii(ncplane* nc, int linesize, const void* data,
67                 int leny, int lenx, const blitterargs* bargs){
68 //fprintf(stderr, "ASCII %d X %d @ %d X %d (%p) place: %d X %d\n", leny, lenx, bargs->begy, bargs->begx, data, bargs->u.cell.placey, bargs->u.cell.placex);
69   const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND;
70   unsigned dimy, dimx, x, y;
71   int total = 0; // number of cells written
72   ncplane_dim_yx(nc, &dimy, &dimx);
73   // FIXME not going to necessarily be safe on all architectures hrmmm
74   const unsigned char* dat = data;
75   int visy = bargs->begy;
76   for(y = bargs->u.cell.placey ; visy < (bargs->begy + leny) && y < dimy ; ++y, ++visy){
77     if(ncplane_cursor_move_yx(nc, y, bargs->u.cell.placex < 0 ? 0 : bargs->u.cell.placex)){
78       return -1;
79     }
80     int visx = bargs->begx;
81     for(x = bargs->u.cell.placex ; visx < (bargs->begx + lenx) && x < dimx ; ++x, ++visx){
82       const unsigned char* rgbbase_up = dat + (linesize * visy) + (visx * 4);
83 //fprintf(stderr, "[%04d/%04d] lsize: %d %02x %02x %02x %02x\n", y, x, linesize, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2], rgbbase_up[3]);
84       nccell* c = ncplane_cell_ref_yx(nc, y, x);
85       // use the default for the background, as that's the only way it's
86       // effective in that case anyway
87       c->channels = 0;
88       c->stylemask = 0;
89       if(blendcolors){
90         nccell_set_bg_alpha(c, NCALPHA_BLEND);
91         nccell_set_fg_alpha(c, NCALPHA_BLEND);
92       }
93       if(rgba_trans_q(rgbbase_up, bargs->transcolor)){
94         nccell_set_bg_alpha(c, NCALPHA_TRANSPARENT);
95         nccell_set_fg_alpha(c, NCALPHA_TRANSPARENT);
96         cell_set_blitquadrants(c, 0, 0, 0, 0);
97       }else{
98         nccell_set_fg_rgb8(c, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2]);
99         nccell_set_bg_rgb8(c, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2]);
100         cell_set_blitquadrants(c, 1, 1, 1, 1);
101         if(pool_blit_direct(&nc->pool, c, " ", 1, 1) <= 0){
102           return -1;
103         }
104         ++total;
105       }
106     }
107   }
108   return total;
109 }
110 
111 // RGBA half-block blitter. Best for most images/videos. Full fidelity
112 // combined with 1:1 pixel aspect ratio.
113 static inline int
tria_blit(ncplane * nc,int linesize,const void * data,int leny,int lenx,const blitterargs * bargs)114 tria_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
115           const blitterargs* bargs){
116   const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND;
117 //fprintf(stderr, "HALF %d X %d @ %d X %d (%p) place: %d X %d\n", leny, lenx, bargs->begy, bargs->begx, data, bargs->u.cell.placey, bargs->u.cell.placex);
118   uint32_t transcolor = bargs->transcolor;
119   unsigned dimy, dimx, x, y;
120   int total = 0; // number of cells written
121   ncplane_dim_yx(nc, &dimy, &dimx);
122   // FIXME not going to necessarily be safe on all architectures hrmmm
123   const unsigned char* dat = data;
124   int visy = bargs->begy;
125   for(y = bargs->u.cell.placey ; visy < (bargs->begy + leny) && y < dimy ; ++y, visy += 2){
126     if(ncplane_cursor_move_yx(nc, y, bargs->u.cell.placex < 0 ? 0 : bargs->u.cell.placex)){
127       return -1;
128     }
129     int visx = bargs->begx;
130     for(x = bargs->u.cell.placex ; visx < (bargs->begx + lenx) && x < dimx ; ++x, ++visx){
131       const unsigned char* rgbbase_up = dat + (linesize * visy) + (visx * 4);
132       const unsigned char* rgbbase_down = zeroes;
133       if(visy < bargs->begy + leny - 1){
134         rgbbase_down = dat + (linesize * (visy + 1)) + (visx * 4);
135       }
136 //fprintf(stderr, "[%04d/%04d] lsize: %d %02x %02x %02x %02x\n", y, x, linesize, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2], rgbbase_up[3]);
137       nccell* c = ncplane_cell_ref_yx(nc, y, x);
138       // use the default for the background, as that's the only way it's
139       // effective in that case anyway
140       c->channels = 0;
141       c->stylemask = 0;
142       if(blendcolors){
143         nccell_set_bg_alpha(c, NCALPHA_BLEND);
144         nccell_set_fg_alpha(c, NCALPHA_BLEND);
145       }
146       if(rgba_trans_q(rgbbase_up, transcolor) || rgba_trans_q(rgbbase_down, transcolor)){
147         nccell_set_bg_alpha(c, NCALPHA_TRANSPARENT);
148         if(rgba_trans_q(rgbbase_up, transcolor) && rgba_trans_q(rgbbase_down, transcolor)){
149           nccell_set_fg_alpha(c, NCALPHA_TRANSPARENT);
150         }else if(rgba_trans_q(rgbbase_up, transcolor)){ // down has the color
151           if(pool_blit_direct(&nc->pool, c, "\u2584", strlen("\u2584"), 1) <= 0){
152             return -1;
153           }
154           nccell_set_fg_rgb8(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]);
155           cell_set_blitquadrants(c, 0, 0, 1, 1);
156           ++total;
157         }else{ // up has the color
158           // upper half block
159           if(pool_blit_direct(&nc->pool, c, "\u2580", strlen("\u2580"), 1) <= 0){
160             return -1;
161           }
162           nccell_set_fg_rgb8(c, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2]);
163           cell_set_blitquadrants(c, 1, 1, 0, 0);
164           ++total;
165         }
166       }else{
167         if(memcmp(rgbbase_up, rgbbase_down, 3) == 0){
168           nccell_set_fg_rgb8(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]);
169           nccell_set_bg_rgb8(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]);
170           cell_set_blitquadrants(c, 0, 0, 0, 0);
171           if(pool_blit_direct(&nc->pool, c, " ", 1, 1) <= 0){
172             return -1;
173           }
174         }else{
175           nccell_set_fg_rgb8(c, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2]);
176           nccell_set_bg_rgb8(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]);
177           cell_set_blitquadrants(c, 1, 1, 1, 1);
178           if(pool_blit_direct(&nc->pool, c, "\u2580", strlen("\u2580"), 1) <= 0){
179             return -1;
180           }
181         }
182         ++total;
183       }
184     }
185   }
186   return total;
187 }
188 
189 // once we find the closest pair of colors, we need look at the other two
190 // colors, and determine whether either belongs with us rather with them.
191 // if so, take the closer, and trilerp it in with us. otherwise, lerp the
192 // two excluded pixels (and retain our original lerp).
193 static const struct qdriver {
194   int pair[2];      // indices of contributing pair
195   int others[2];    // indices of excluded pair
196   const char* egc;  // EGC corresponding to contributing pair
197   const char* oth0egc; // EGC upon absorbing others[0]
198   const char* oth1egc; // EGC upon absorbing others[1]
199 } quadrant_drivers[6] = {
200   { .pair = { 0, 1 }, .others = { 2, 3 }, .egc = "▀", .oth0egc = "▛", .oth1egc = "▜", },
201   { .pair = { 0, 2 }, .others = { 1, 3 }, .egc = "▌", .oth0egc = "▛", .oth1egc = "▙", },
202   { .pair = { 0, 3 }, .others = { 1, 2 }, .egc = "▚", .oth0egc = "▜", .oth1egc = "▙", },
203   { .pair = { 1, 2 }, .others = { 0, 3 }, .egc = "▞", .oth0egc = "▛", .oth1egc = "▟", },
204   { .pair = { 1, 3 }, .others = { 0, 2 }, .egc = "▐", .oth0egc = "▜", .oth1egc = "▟", },
205   { .pair = { 2, 3 }, .others = { 0, 1 }, .egc = "▄", .oth0egc = "▙", .oth1egc = "▟", },
206 };
207 
208 // get the six distances between four colors. diffs must be an array of
209 // at least 6 uint32_t values.
210 static void
rgb_4diff(uint32_t * diffs,uint32_t tl,uint32_t tr,uint32_t bl,uint32_t br)211 rgb_4diff(uint32_t* diffs, uint32_t tl, uint32_t tr, uint32_t bl, uint32_t br){
212   struct rgb {
213     unsigned r, g, b;
214   } colors[4];
215   ncchannel_rgb8(tl, &colors[0].r, &colors[0].g, &colors[0].b);
216   ncchannel_rgb8(tr, &colors[1].r, &colors[1].g, &colors[1].b);
217   ncchannel_rgb8(bl, &colors[2].r, &colors[2].g, &colors[2].b);
218   ncchannel_rgb8(br, &colors[3].r, &colors[3].g, &colors[3].b);
219   for(size_t idx = 0 ; idx < sizeof(quadrant_drivers) / sizeof(*quadrant_drivers) ; ++idx){
220     const struct qdriver* qd = quadrant_drivers + idx;
221     const struct rgb* rgb0 = colors + qd->pair[0];
222     const struct rgb* rgb1 = colors + qd->pair[1];
223     diffs[idx] = rgb_diff(rgb0->r, rgb0->g, rgb0->b,
224                           rgb1->r, rgb1->g, rgb1->b);
225   }
226 }
227 
228 // solve for the EGC and two colors to best represent four colors at top
229 // left, top right, bot left, bot right
230 static inline const char*
quadrant_solver(uint32_t tl,uint32_t tr,uint32_t bl,uint32_t br,uint32_t * fore,uint32_t * back,unsigned nointerpolate)231 quadrant_solver(uint32_t tl, uint32_t tr, uint32_t bl, uint32_t br,
232                 uint32_t* fore, uint32_t* back, unsigned nointerpolate){
233   const uint32_t colors[4] = { tl, tr, bl, br };
234 //fprintf(stderr, "%08x/%08x/%08x/%08x\n", tl, tr, bl, br);
235   uint32_t diffs[sizeof(quadrant_drivers) / sizeof(*quadrant_drivers)];
236   rgb_4diff(diffs, tl, tr, bl, br);
237   // compiler can't verify that we'll always be less than 769 somewhere,
238   // so fuck it, just go ahead and initialize to 0 / diffs[0]
239   size_t mindiffidx = 0;
240   unsigned mindiff = diffs[0]; // 3 * 256 + 1; // max distance is 256 * 3
241   // if all diffs are 0, emit a space
242   bool allzerodiffs = (mindiff == 0);
243   for(size_t idx = 1 ; idx < sizeof(diffs) / sizeof(*diffs) ; ++idx){
244     if(diffs[idx] < mindiff){
245       mindiffidx = idx;
246       mindiff = diffs[idx];
247     }
248     if(diffs[idx]){
249       allzerodiffs = false;
250     }
251   }
252   if(allzerodiffs){
253     *fore = *back = tl;
254     return " ";
255   }
256   // at this point, 0 <= mindiffidx <= 5. foreground color will be the
257   // lerp of this nearest pair. we then check the other two. if they are
258   // closer to one another than either is to our lerp, lerp between them.
259   // otherwise, bring the closer one into our lerped fold.
260   const struct qdriver* qd = &quadrant_drivers[mindiffidx];
261   // the diff of the excluded pair is conveniently located at the inverse
262   // location within diffs[] viz mindiffidx.
263   // const uint32_t otherdiff = diffs[5 - mindiffidx];
264   *fore = lerp(colors[qd->pair[0]], colors[qd->pair[1]], nointerpolate);
265   *back = lerp(colors[qd->others[0]], colors[qd->others[1]], nointerpolate);
266 //fprintf(stderr, "mindiff: %u[%zu] fore: %08x back: %08x %d+%d/%d+%d\n", mindiff, mindiffidx, *fore, *back, qd->pair[0], qd->pair[1], qd->others[0], qd->others[1]);
267   const char* egc = qd->egc;
268   // break down the excluded pair and lerp
269   unsigned r0, r1, r2, g0, g1, g2, b0, b1, b2;
270   unsigned roth, goth, both, rlerp, glerp, blerp;
271   ncchannel_rgb8(*back, &roth, &goth, &both);
272   ncchannel_rgb8(*fore, &rlerp, &glerp, &blerp);
273 //fprintf(stderr, "rgbs: %02x %02x %02x / %02x %02x %02x\n", r0, g0, b0, r1, g1, b1);
274   // get diffs of the excluded two from both lerps
275   ncchannel_rgb8(colors[qd->others[0]], &r0, &g0, &b0);
276   ncchannel_rgb8(colors[qd->others[1]], &r1, &g1, &b1);
277   diffs[0] = rgb_diff(r0, g0, b0, roth, goth, both);
278   diffs[1] = rgb_diff(r1, g1, b1, roth, goth, both);
279   diffs[2] = rgb_diff(r0, g0, b0, rlerp, glerp, blerp);
280   diffs[3] = rgb_diff(r1, g1, b1, rlerp, glerp, blerp);
281   // get diffs of the included two from their lerp
282   ncchannel_rgb8(colors[qd->pair[0]], &r0, &g0, &b0);
283   ncchannel_rgb8(colors[qd->pair[1]], &r1, &g1, &b1);
284   diffs[4] = rgb_diff(r0, g0, b0, rlerp, glerp, blerp);
285   diffs[5] = rgb_diff(r1, g1, b1, rlerp, glerp, blerp);
286   unsigned curdiff = diffs[0] + diffs[1] + diffs[4] + diffs[5];
287   // it might be better to combine three, and leave one totally unchanged.
288   // propose a trilerps; we only need consider the member of the excluded pair
289   // closer to the primary lerp. recalculate total diff; merge if lower.
290   if(diffs[2] < diffs[3]){
291     unsigned tri = trilerp(colors[qd->pair[0]], colors[qd->pair[1]], colors[qd->others[0]],
292                            nointerpolate);
293     ncchannel_rgb8(colors[qd->others[0]], &r2, &g2, &b2);
294     ncchannel_rgb8(tri, &roth, &goth, &both);
295     if(rgb_diff(r0, g0, b0, roth, goth, both) +
296        rgb_diff(r1, g1, b1, roth, goth, both) +
297        rgb_diff(r2, g2, b2, roth, goth, both) < curdiff){
298       egc = qd->oth0egc;
299       *back = colors[qd->others[1]];
300       *fore = tri;
301     }
302 //fprintf(stderr, "quadblitter swap type 1\n");
303   }else{
304     unsigned tri = trilerp(colors[qd->pair[0]], colors[qd->pair[1]], colors[qd->others[1]],
305                            nointerpolate);
306     ncchannel_rgb8(colors[qd->others[1]], &r2, &g2, &b2);
307     ncchannel_rgb8(tri, &roth, &goth, &both);
308     if(rgb_diff(r0, g0, b0, roth, goth, both) +
309        rgb_diff(r1, g1, b1, roth, goth, both) +
310        rgb_diff(r2, g2, b2, roth, goth, both) < curdiff){
311       egc = qd->oth1egc;
312       *back = colors[qd->others[0]];
313       *fore = tri;
314     }
315 //fprintf(stderr, "quadblitter swap type 2\n");
316   }
317   return egc;
318 }
319 
320 // quadrant check for transparency. returns an EGC if we found transparent
321 // quads and have solved for colors (this EGC ought then be loaded into the
322 // cell). returns NULL otherwise. transparency trumps everything else in terms
323 // of priority -- if even one quadrant is transparent, we will have a
324 // transparent background, and lerp the rest together for foreground. we thus
325 // have a 16-way conditional tree in which each EGC must show up exactly once.
326 // FIXME we ought be able to just build up a bitstring and use it as an index!
327 // FIXME pass in rgbas as array of uint32_t ala sexblitter
328 static inline const char*
qtrans_check(nccell * c,unsigned blendcolors,const unsigned char * rgbbase_tl,const unsigned char * rgbbase_tr,const unsigned char * rgbbase_bl,const unsigned char * rgbbase_br,uint32_t transcolor,unsigned nointerpolate)329 qtrans_check(nccell* c, unsigned blendcolors,
330              const unsigned char* rgbbase_tl, const unsigned char* rgbbase_tr,
331              const unsigned char* rgbbase_bl, const unsigned char* rgbbase_br,
332              uint32_t transcolor, unsigned nointerpolate){
333   uint32_t tl = 0, tr = 0, bl = 0, br = 0;
334   ncchannel_set_rgb8(&tl, rgbbase_tl[0], rgbbase_tl[1], rgbbase_tl[2]);
335   ncchannel_set_rgb8(&tr, rgbbase_tr[0], rgbbase_tr[1], rgbbase_tr[2]);
336   ncchannel_set_rgb8(&bl, rgbbase_bl[0], rgbbase_bl[1], rgbbase_bl[2]);
337   ncchannel_set_rgb8(&br, rgbbase_br[0], rgbbase_br[1], rgbbase_br[2]);
338   const char* egc = NULL;
339   if(rgba_trans_q(rgbbase_tl, transcolor)){
340     // top left is transparent
341     if(rgba_trans_q(rgbbase_tr, transcolor)){
342       // all of top is transparent
343       if(rgba_trans_q(rgbbase_bl, transcolor)){
344         // top and left are transparent
345         if(rgba_trans_q(rgbbase_br, transcolor)){
346           // entirety is transparent, load with nul (but not NULL)
347           nccell_set_fg_default(c);
348           cell_set_blitquadrants(c, 0, 0, 0, 0);
349           egc = "";
350         }else{
351           nccell_set_fg_rgb8(c, rgbbase_br[0], rgbbase_br[1], rgbbase_br[2]);
352           cell_set_blitquadrants(c, 0, 0, 0, 1);
353           egc = "▗";
354         }
355       }else{
356         if(rgba_trans_q(rgbbase_br, transcolor)){
357           nccell_set_fg_rgb8(c, rgbbase_bl[0], rgbbase_bl[1], rgbbase_bl[2]);
358           cell_set_blitquadrants(c, 0, 0, 1, 0);
359           egc = "▖";
360         }else{
361           cell_set_fchannel(c, lerp(bl, br, nointerpolate));
362           cell_set_blitquadrants(c, 0, 0, 1, 1);
363           egc = "▄";
364         }
365       }
366     }else{ // top right is foreground, top left is transparent
367       if(rgba_trans_q(rgbbase_bl, transcolor)){
368         if(rgba_trans_q(rgbbase_br, transcolor)){ // entire bottom is transparent
369           nccell_set_fg_rgb8(c, rgbbase_tr[0], rgbbase_tr[1], rgbbase_tr[2]);
370           cell_set_blitquadrants(c, 0, 1, 0, 0);
371           egc = "▝";
372         }else{
373           cell_set_fchannel(c, lerp(tr, br, nointerpolate));
374           cell_set_blitquadrants(c, 0, 1, 0, 1);
375           egc = "▐";
376         }
377       }else if(rgba_trans_q(rgbbase_br, transcolor)){ // only br is transparent
378         cell_set_fchannel(c, lerp(tr, bl, nointerpolate));
379         cell_set_blitquadrants(c, 0, 1, 1, 0);
380         egc = "▞";
381       }else{
382         cell_set_fchannel(c, trilerp(tr, bl, br, nointerpolate));
383         cell_set_blitquadrants(c, 0, 1, 1, 1);
384         egc = "▟";
385       }
386     }
387   }else{ // topleft is foreground for all here
388     if(rgba_trans_q(rgbbase_tr, transcolor)){
389       if(rgba_trans_q(rgbbase_bl, transcolor)){
390         if(rgba_trans_q(rgbbase_br, transcolor)){
391           nccell_set_fg_rgb8(c, rgbbase_tl[0], rgbbase_tl[1], rgbbase_tl[2]);
392           cell_set_blitquadrants(c, 1, 0, 0, 0);
393           egc = "▘";
394         }else{
395           cell_set_fchannel(c, lerp(tl, br, nointerpolate));
396           cell_set_blitquadrants(c, 1, 0, 0, 1);
397           egc = "▚";
398         }
399       }else if(rgba_trans_q(rgbbase_br, transcolor)){
400         cell_set_fchannel(c, lerp(tl, bl, nointerpolate));
401         cell_set_blitquadrants(c, 1, 0, 1, 0);
402         egc = "▌";
403       }else{
404         cell_set_fchannel(c, trilerp(tl, bl, br, nointerpolate));
405         cell_set_blitquadrants(c, 1, 0, 1, 1);
406         egc = "▙";
407       }
408     }else if(rgba_trans_q(rgbbase_bl, transcolor)){
409       if(rgba_trans_q(rgbbase_br, transcolor)){ // entire bottom is transparent
410         cell_set_fchannel(c, lerp(tl, tr, nointerpolate));
411         cell_set_blitquadrants(c, 1, 1, 0, 0);
412         egc = "▀";
413       }else{ // only bl is transparent
414         cell_set_fchannel(c, trilerp(tl, tr, br, nointerpolate));
415         cell_set_blitquadrants(c, 1, 1, 0, 1);
416         egc = "▜";
417       }
418     }else if(rgba_trans_q(rgbbase_br, transcolor)){ // only br is transparent
419       cell_set_fchannel(c, trilerp(tl, tr, bl, nointerpolate));
420       cell_set_blitquadrants(c, 1, 1, 1, 0);
421       egc = "▛";
422     }else{
423       return NULL; // no transparency
424     }
425   }
426   assert(egc);
427   nccell_set_bg_alpha(c, NCALPHA_TRANSPARENT);
428   if(*egc == '\0'){
429     nccell_set_fg_alpha(c, NCALPHA_TRANSPARENT);
430   }else if(blendcolors){
431     nccell_set_fg_alpha(c, NCALPHA_BLEND);
432   }
433 //fprintf(stderr, "QBQ: 0x%x\n", cell_blittedquadrants(c));
434   return egc;
435 }
436 
437 // quadrant blitter. maps 2x2 to each cell. since we only have two colors at
438 // our disposal (foreground and background), we lose some fidelity.
439 static inline int
quadrant_blit(ncplane * nc,int linesize,const void * data,int leny,int lenx,const blitterargs * bargs)440 quadrant_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
441               const blitterargs* bargs){
442   const unsigned nointerpolate = bargs->flags & NCVISUAL_OPTION_NOINTERPOLATE;
443   const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND;
444   unsigned dimy, dimx, x, y;
445   int total = 0; // number of cells written
446   ncplane_dim_yx(nc, &dimy, &dimx);
447 //fprintf(stderr, "quadblitter %dx%d -> %d/%d+%d/%d\n", leny, lenx, dimy, dimx, bargs->u.cell.placey, bargs->u.cell.placex);
448   // FIXME not going to necessarily be safe on all architectures hrmmm
449   const unsigned char* dat = data;
450   int visy = bargs->begy;
451   for(y = bargs->u.cell.placey ; visy < (bargs->begy + leny) && y < dimy ; ++y, visy += 2){
452     if(ncplane_cursor_move_yx(nc, y, bargs->u.cell.placex < 0 ? 0 : bargs->u.cell.placex)){
453       return -1;
454     }
455     int visx = bargs->begx;
456     for(x = bargs->u.cell.placex ; visx < (bargs->begx + lenx) && x < dimx ; ++x, visx += 2){
457       const unsigned char* rgbbase_tl = dat + (linesize * visy) + (visx * 4);
458       const unsigned char* rgbbase_tr = zeroes;
459       const unsigned char* rgbbase_bl = zeroes;
460       const unsigned char* rgbbase_br = zeroes;
461       if(visx < bargs->begx + lenx - 1){
462         rgbbase_tr = dat + (linesize * visy) + ((visx + 1) * 4);
463         if(visy < bargs->begy + leny - 1){
464           rgbbase_br = dat + (linesize * (visy + 1)) + ((visx + 1) * 4);
465         }
466       }
467       if(visy < bargs->begy + leny - 1){
468         rgbbase_bl = dat + (linesize * (visy + 1)) + (visx * 4);
469       }
470 //fprintf(stderr, "[%04d/%04d] lsize: %d %02x %02x %02x %02x\n", y, x, linesize, rgbbase_tl[0], rgbbase_tr[1], rgbbase_bl[2], rgbbase_br[3]);
471       nccell* c = ncplane_cell_ref_yx(nc, y, x);
472       c->channels = 0;
473       c->stylemask = 0;
474       const char* egc = qtrans_check(c, blendcolors, rgbbase_tl, rgbbase_tr,
475                                      rgbbase_bl, rgbbase_br, bargs->transcolor,
476                                      nointerpolate);
477       if(egc == NULL){
478         uint32_t tl = 0, tr = 0, bl = 0, br = 0;
479         ncchannel_set_rgb8(&tl, rgbbase_tl[0], rgbbase_tl[1], rgbbase_tl[2]);
480         ncchannel_set_rgb8(&tr, rgbbase_tr[0], rgbbase_tr[1], rgbbase_tr[2]);
481         ncchannel_set_rgb8(&bl, rgbbase_bl[0], rgbbase_bl[1], rgbbase_bl[2]);
482         ncchannel_set_rgb8(&br, rgbbase_br[0], rgbbase_br[1], rgbbase_br[2]);
483         uint32_t bg, fg;
484 //fprintf(stderr, "qtrans check: %d/%d\n%08x %08x\n%08x %08x\n", y, x, *(const uint32_t*)rgbbase_tl, *(const uint32_t*)rgbbase_tr, *(const uint32_t*)rgbbase_bl, *(const uint32_t*)rgbbase_br);
485         egc = quadrant_solver(tl, tr, bl, br, &fg, &bg, nointerpolate);
486         assert(egc);
487 //fprintf(stderr, "%d/%d %08x/%08x\n", y, x, fg, bg);
488         cell_set_fchannel(c, fg);
489         cell_set_bchannel(c, bg);
490         if(blendcolors){
491           nccell_set_bg_alpha(c, NCALPHA_BLEND);
492           nccell_set_fg_alpha(c, NCALPHA_BLEND);
493         }
494         cell_set_blitquadrants(c, 1, 1, 1, 1);
495       }
496       if(*egc){
497         if(pool_blit_direct(&nc->pool, c, egc, strlen(egc), 1) <= 0){
498           return -1;
499         }
500         ++total;
501       }
502     }
503   }
504   return total;
505 }
506 
507 // Solve for the cell rendered by this 3x2 sample. None of the input pixels may
508 // be transparent (that ought already have been handled). We use exhaustive
509 // search, which might be quite computationally intensive for the worst case
510 // (all six pixels are different colors). We want to solve for the 2-partition
511 // of pixels that minimizes total source distance from the resulting lerps.
512 static const char*
sex_solver(const uint32_t rgbas[6],uint64_t * channels,unsigned blendcolors,unsigned nointerpolate)513 sex_solver(const uint32_t rgbas[6], uint64_t* channels, unsigned blendcolors,
514            unsigned nointerpolate){
515   // each element within the set of 64 has an inverse element within the set,
516   // for which we would calculate the same total differences, so just handle
517   // the first 32. the partition[] bit masks represent combinations of
518   // sextants, and their indices correspond to sex[].
519   static const char* sex[32] = {
520     " ", "��", "��", "��", "��", "��", "��", "��", // 0..7
521     "��", "��", "��", "��", "��", "��", "��", "��", // 8..15
522     "��", "��", "��", "��", "��", "��", "��", "��", // 16..23
523     "��", "��", "��", "▌", "��", "��", "��", "��", // 24..31
524   };
525   static const unsigned partitions[32] = {
526     0, // 1 way to arrange 0
527     1, 2, 4, 8, 16, 32, // 6 ways to arrange 1
528     3, 5, 9, 17, 33, 6, 10, 18, 34, 12, 20, 36, 24, 40, 48, // 15 ways for 2
529     //  16 ways to arrange 3, *but* six of them are inverses, so 10
530     7, 11, 19, 35, 13, 21, 37, 25, 41, 14 //  10 + 15 + 6 + 1 == 32
531   };
532   // we loop over the bitstrings, dividing the pixels into two sets, and then
533   // taking a general lerp over each set. we then compute the sum of absolute
534   // differences, and see if it's the new minimum.
535   int best = -1;
536   uint32_t mindiff = UINT_MAX;
537 //fprintf(stderr, "%06x %06x\n%06x %06x\n%06x %06x\n", rgbas[0], rgbas[1], rgbas[2], rgbas[3], rgbas[4], rgbas[5]);
538   for(size_t glyph = 0 ; glyph < sizeof(partitions) / sizeof(*partitions) ; ++glyph){
539     unsigned rsum0 = 0, rsum1 = 0;
540     unsigned gsum0 = 0, gsum1 = 0;
541     unsigned bsum0 = 0, bsum1 = 0;
542     int insum = 0;
543     int outsum = 0;
544     for(unsigned mask = 0 ; mask < 6 ; ++mask){
545       if(partitions[glyph] & (1u << mask)){
546         if(!nointerpolate || !insum){
547           rsum0 += ncpixel_r(rgbas[mask]);
548           gsum0 += ncpixel_g(rgbas[mask]);
549           bsum0 += ncpixel_b(rgbas[mask]);
550           ++insum;
551         }
552       }else{
553         if(!nointerpolate || !outsum){
554           rsum1 += ncpixel_r(rgbas[mask]);
555           gsum1 += ncpixel_g(rgbas[mask]);
556           bsum1 += ncpixel_b(rgbas[mask]);
557           ++outsum;
558         }
559       }
560     }
561     uint32_t l0 = generalerp(rsum0, gsum0, bsum0, insum);
562     uint32_t l1 = generalerp(rsum1, gsum1, bsum1, outsum);
563 //fprintf(stderr, "sum0: %06x sum1: %06x insum: %d\n", l0 & 0xffffffu, l1 & 0xffffffu, insum);
564     uint32_t totaldiff = 0;
565     for(unsigned mask = 0 ; mask < 6 ; ++mask){
566       unsigned r, g, b;
567       if(partitions[glyph] & (1u << mask)){
568         ncchannel_rgb8(l0, &r, &g, &b);
569       }else{
570         ncchannel_rgb8(l1, &r, &g, &b);
571       }
572       uint32_t rdiff = rgb_diff(ncpixel_r(rgbas[mask]), ncpixel_g(rgbas[mask]),
573                                 ncpixel_b(rgbas[mask]), r, g, b);
574       totaldiff += rdiff;
575 //fprintf(stderr, "mask: %u totaldiff: %u insum: %d (%08x / %08x)\n", mask, totaldiff, insum, l0, l1);
576     }
577 //fprintf(stderr, "bits: %u %zu totaldiff: %f best: %f (%d)\n", partitions[glyph], glyph, totaldiff, mindiff, best);
578     if(totaldiff < mindiff){
579       mindiff = totaldiff;
580       best = glyph;
581       ncchannels_set_fchannel(channels, l0);
582       ncchannels_set_bchannel(channels, l1);
583     }
584     if(totaldiff == 0){ // can't beat that!
585       break;
586     }
587   }
588 //fprintf(stderr, "solved for best: %d (%u)\n", best, mindiff);
589   assert(best >= 0 && best < 32);
590   if(blendcolors){
591     ncchannels_set_fg_alpha(channels, NCALPHA_BLEND);
592     ncchannels_set_bg_alpha(channels, NCALPHA_BLEND);
593   }
594   return sex[best];
595 }
596 
597 static const char*
sex_trans_check(nccell * c,const uint32_t rgbas[6],unsigned blendcolors,uint32_t transcolor,unsigned nointerpolate)598 sex_trans_check(nccell* c, const uint32_t rgbas[6], unsigned blendcolors,
599                 uint32_t transcolor, unsigned nointerpolate){
600   // bit is *set* where sextant *is not*
601   // 32: bottom right 16: bottom left
602   //  8: middle right  4: middle left
603   //  2: upper right   1: upper left
604   static const char* sex[64] = {
605     "█", "��", "��", "��", "��", "��", "��", "��",
606     "��", "��", "��", "��", "��", "��", "��", "��",
607     "��", "��", "��", "��", "��", "▐", "��", "��",
608     "��", "��", "��", "��", "��", "��", "��", "��",
609     "��", "��", "��", "��", "��", "��", "��", "��",
610     "��", "��", "▌", "��", "��", "��", "��", "��",
611     "��", "��", "��", "��", "��", "��", "��", "��",
612     "��", "��", "��", "��", "��", "��", "��", " ",
613   };
614   unsigned transstring = 0;
615   unsigned r = 0, g = 0, b = 0;
616   unsigned div = 0;
617   for(unsigned mask = 0 ; mask < 6 ; ++mask){
618     if(rgba_trans_p(rgbas[mask], transcolor)){
619       transstring |= (1u << mask);
620     }else if(!nointerpolate || !div){
621       r += ncpixel_r(rgbas[mask]);
622       g += ncpixel_g(rgbas[mask]);
623       b += ncpixel_b(rgbas[mask]);
624       ++div;
625     }
626   }
627   if(transstring == 0){ // there was no transparency
628     return NULL;
629   }
630   nccell_set_bg_alpha(c, NCALPHA_TRANSPARENT);
631   // there were some transparent pixels. since they get priority, the foreground
632   // is just a general lerp across non-transparent pixels.
633   const char* egc = sex[transstring];
634   nccell_set_bg_alpha(c, NCALPHA_TRANSPARENT);
635 //fprintf(stderr, "transtring: %u egc: %s\n", transtring, egc);
636   if(*egc == ' '){ // entirely transparent
637     nccell_set_fg_alpha(c, NCALPHA_TRANSPARENT);
638     return "";
639   }else{ // partially transparent, thus div >= 1
640 //fprintf(stderr, "div: %u r: %u g: %u b: %u\n", div, r, g, b);
641     cell_set_fchannel(c, generalerp(r, g, b, div));
642     if(blendcolors){
643       nccell_set_fg_alpha(c, NCALPHA_BLEND);
644     }
645     cell_set_blitquadrants(c, !(transstring & 5u), !(transstring & 10u),
646                               !(transstring & 20u), !(transstring & 40u));
647   }
648 //fprintf(stderr, "SEX-BQ: 0x%x\n", cell_blittedquadrants(c));
649   return egc;
650 }
651 
652 // sextant blitter. maps 3x2 to each cell. since we only have two colors at
653 // our disposal (foreground and background), we lose some fidelity.
654 static inline int
sextant_blit(ncplane * nc,int linesize,const void * data,int leny,int lenx,const blitterargs * bargs)655 sextant_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
656              const blitterargs* bargs){
657   const unsigned nointerpolate = bargs->flags & NCVISUAL_OPTION_NOINTERPOLATE;
658   const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND;
659   unsigned dimy, dimx, x, y;
660   int total = 0; // number of cells written
661   ncplane_dim_yx(nc, &dimy, &dimx);
662 //fprintf(stderr, "sexblitter %dx%d -> %d/%d+%d/%d\n", leny, lenx, dimy, dimx, bargs->u.cell.placey, bargs->u.cell.placex);
663   const unsigned char* dat = data;
664   int visy = bargs->begy;
665   for(y = bargs->u.cell.placey ; visy < (bargs->begy + leny) && y < dimy ; ++y, visy += 3){
666     if(ncplane_cursor_move_yx(nc, y, bargs->u.cell.placex < 0 ? 0 : bargs->u.cell.placex)){
667       return -1;
668     }
669     int visx = bargs->begx;
670     for(x = bargs->u.cell.placex ; visx < (bargs->begx + lenx) && x < dimx ; ++x, visx += 2){
671       uint32_t rgbas[6] = { 0, 0, 0, 0, 0, 0 };
672       memcpy(&rgbas[0], (dat + (linesize * visy) + (visx * 4)), sizeof(*rgbas));
673       if(visx < bargs->begx + lenx - 1){
674         memcpy(&rgbas[1], (dat + (linesize * visy) + ((visx + 1) * 4)), sizeof(*rgbas));
675         if(visy < bargs->begy + leny - 1){
676           memcpy(&rgbas[3], (dat + (linesize * (visy + 1)) + ((visx + 1) * 4)), sizeof(*rgbas));
677           if(visy < bargs->begy + leny - 2){
678             memcpy(&rgbas[5], (dat + (linesize * (visy + 2)) + ((visx + 1) * 4)), sizeof(*rgbas));
679           }
680         }
681       }
682       if(visy < bargs->begy + leny - 1){
683         memcpy(&rgbas[2], (dat + (linesize * (visy + 1)) + (visx * 4)), sizeof(*rgbas));
684         if(visy < bargs->begy + leny - 2){
685           memcpy(&rgbas[4], (dat + (linesize * (visy + 2)) + (visx * 4)), sizeof(*rgbas));
686         }
687       }
688       nccell* c = ncplane_cell_ref_yx(nc, y, x);
689       c->channels = 0;
690       c->stylemask = 0;
691       const char* egc = sex_trans_check(c, rgbas, blendcolors, bargs->transcolor, nointerpolate);
692       if(egc == NULL){ // no transparency; run a full solver
693         egc = sex_solver(rgbas, &c->channels, blendcolors, nointerpolate);
694         cell_set_blitquadrants(c, 1, 1, 1, 1);
695       }
696 //fprintf(stderr, "sex EGC: %s channels: %016lx\n", egc, c->channels);
697       if(*egc){
698         if(pool_blit_direct(&nc->pool, c, egc, strlen(egc), 1) <= 0){
699           return -1;
700         }
701         ++total;
702       }
703     }
704   }
705   return total;
706 }
707 
708 // fold the r, g, and b components of the pixel into *r, *g, and *b, and
709 // increment *foldcount
710 static inline void
fold_rgb8(unsigned * restrict r,unsigned * restrict g,unsigned * restrict b,const uint32_t * pixel,unsigned * foldcount)711 fold_rgb8(unsigned* restrict r, unsigned* restrict g, unsigned* restrict b,
712           const uint32_t* pixel, unsigned* foldcount){
713   *r += ncpixel_r(*pixel);
714   *g += ncpixel_g(*pixel);
715   *b += ncpixel_b(*pixel);
716   ++*foldcount;
717 }
718 
719 // Braille blitter. maps 4x2 to each cell. since we only have one color at
720 // our disposal (foreground), we lose some fidelity. this is optimal for
721 // visuals with only two colors in a given area, as it packs lots of
722 // resolution. always transparent background.
723 static inline int
braille_blit(ncplane * nc,int linesize,const void * data,int leny,int lenx,const blitterargs * bargs)724 braille_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
725              const blitterargs* bargs){
726   const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND;
727   unsigned dimy, dimx, x, y;
728   int total = 0; // number of cells written
729   ncplane_dim_yx(nc, &dimy, &dimx);
730   // FIXME not going to necessarily be safe on all architectures hrmmm
731   const unsigned char* dat = data;
732   int visy = bargs->begy;
733   for(y = bargs->u.cell.placey ; visy < (bargs->begy + leny) && y < dimy ; ++y, visy += 4){
734     if(ncplane_cursor_move_yx(nc, y, bargs->u.cell.placex < 0 ? 0 : bargs->u.cell.placex)){
735       return -1;
736     }
737     int visx = bargs->begx;
738     for(x = bargs->u.cell.placex ; visx < (bargs->begx + lenx) && x < dimx ; ++x, visx += 2){
739       const uint32_t* rgbbase_l0 = (const uint32_t*)(dat + (linesize * visy) + (visx * 4));
740       const uint32_t* rgbbase_r0 = &zeroes32;
741       const uint32_t* rgbbase_l1 = &zeroes32;
742       const uint32_t* rgbbase_r1 = &zeroes32;
743       const uint32_t* rgbbase_l2 = &zeroes32;
744       const uint32_t* rgbbase_r2 = &zeroes32;
745       const uint32_t* rgbbase_l3 = &zeroes32;
746       const uint32_t* rgbbase_r3 = &zeroes32;
747       unsigned r = 0, g = 0, b = 0;
748       unsigned blends = 0;
749       unsigned egcidx = 0;
750       if(visx < bargs->begx + lenx - 1){
751         rgbbase_r0 = (const uint32_t*)(dat + (linesize * visy) + ((visx + 1) * 4));
752         if(visy < bargs->begy + leny - 1){
753           rgbbase_r1 = (const uint32_t*)(dat + (linesize * (visy + 1)) + ((visx + 1) * 4));
754           if(visy < bargs->begy + leny - 2){
755             rgbbase_r2 = (const uint32_t*)(dat + (linesize * (visy + 2)) + ((visx + 1) * 4));
756             if(visy < bargs->begy + leny - 3){
757               rgbbase_r3 = (const uint32_t*)(dat + (linesize * (visy + 3)) + ((visx + 1) * 4));
758             }
759           }
760         }
761       }
762       if(visy < bargs->begy + leny - 1){
763         rgbbase_l1 = (const uint32_t*)(dat + (linesize * (visy + 1)) + (visx * 4));
764         if(visy < bargs->begy + leny - 2){
765           rgbbase_l2 = (const uint32_t*)(dat + (linesize * (visy + 2)) + (visx * 4));
766           if(visy < bargs->begy + leny - 3){
767             rgbbase_l3 = (const uint32_t*)(dat + (linesize * (visy + 3)) + (visx * 4));
768           }
769         }
770       }
771       // braille block is ordered (where 1 is the LSB)
772       //  1 4
773       //  2 5
774       //  3 6
775       //  4 7
776       // FIXME fold this into the above?
777       if(!rgba_trans_p(*rgbbase_l0, bargs->transcolor)){
778         egcidx |= 1u;
779         fold_rgb8(&r, &g, &b, rgbbase_l0, &blends);
780       }
781       if(!rgba_trans_p(*rgbbase_l1, bargs->transcolor)){
782         egcidx |= 2u;
783         fold_rgb8(&r, &g, &b, rgbbase_l1, &blends);
784       }
785       if(!rgba_trans_p(*rgbbase_l2, bargs->transcolor)){
786         egcidx |= 4u;
787         fold_rgb8(&r, &g, &b, rgbbase_l2, &blends);
788       }
789       if(!rgba_trans_p(*rgbbase_r0, bargs->transcolor)){
790         egcidx |= 8u;
791         fold_rgb8(&r, &g, &b, rgbbase_r0, &blends);
792       }
793       if(!rgba_trans_p(*rgbbase_r1, bargs->transcolor)){
794         egcidx |= 16u;
795         fold_rgb8(&r, &g, &b, rgbbase_r1, &blends);
796       }
797       if(!rgba_trans_p(*rgbbase_r2, bargs->transcolor)){
798         egcidx |= 32u;
799         fold_rgb8(&r, &g, &b, rgbbase_r2, &blends);
800       }
801       if(!rgba_trans_p(*rgbbase_l3, bargs->transcolor)){
802         egcidx |= 64u;
803         fold_rgb8(&r, &g, &b, rgbbase_l3, &blends);
804       }
805       if(!rgba_trans_p(*rgbbase_r3, bargs->transcolor)){
806         egcidx |= 128u;
807         fold_rgb8(&r, &g, &b, rgbbase_r3, &blends);
808       }
809 //fprintf(stderr, "[%04d/%04d] lsize: %d %02x %02x %02x %02x\n", y, x, linesize, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2], rgbbase_up[3]);
810       nccell* c = ncplane_cell_ref_yx(nc, y, x);
811       // use the default for the background, as that's the only way it's
812       // effective in that case anyway
813       c->channels = 0;
814       c->stylemask = 0;
815       if(blendcolors){
816         nccell_set_fg_alpha(c, NCALPHA_BLEND);
817       }
818       // FIXME for now, we just sample, color-wise, and always draw crap.
819       // more complicated to do optimally than quadrants, for sure. ideally,
820       // we only get one color in an area.
821       nccell_set_bg_alpha(c, NCALPHA_TRANSPARENT);
822       if(!egcidx){
823           nccell_set_fg_alpha(c, NCALPHA_TRANSPARENT);
824           // FIXME else look for pairs of transparency!
825       }else{
826         if(blends){
827           nccell_set_fg_rgb8(c, r / blends, g / blends, b / blends);
828         }
829         // UTF-8 encodings of the Braille Patterns are always 0xe2 0xaX 0xCC,
830         // where 0 <= X <= 3 and 0x80 <= CC <= 0xbf (4 groups of 64).
831         char egc[4] = { 0xe2, 0xa0, 0x80, 0x00 };
832         egc[2] += egcidx % 64;
833         egc[1] += egcidx / 64;
834         if(pool_blit_direct(&nc->pool, c, egc, strlen(egc), 1) <= 0){
835           return -1;
836         }
837       }
838       ++total;
839     }
840   }
841   return total;
842 }
843 
844 // NCBLIT_DEFAULT is not included, as it has no defined properties. It ought
845 // be replaced with some real blitter implementation by the calling widget.
846 // The order of contents is critical for 'egcs': ncplane_as_rgba() uses these
847 // arrays to map cells to source pixels. Map the upper-left logical bit to
848 // 1, and increase to the right, followed by down. The first egc ought thus
849 // always be space, to indicate an empty cell (all zeroes). These need be
850 // kept in the same order as the enums!
851 static struct blitset notcurses_blitters[] = {
852    { .geom = NCBLIT_1x1,     .width = 1, .height = 1,
853      .egcs = L" █", .plotegcs = L" █",
854      .blit = tria_blit_ascii,.name = "ascii",         .fill = false, },
855    { .geom = NCBLIT_2x1,     .width = 1, .height = 2,
856      .egcs = NCHALFBLOCKS,   .plotegcs = L" ▄█",
857      .blit = tria_blit,      .name = "half",          .fill = false, },
858    { .geom = NCBLIT_2x2,     .width = 2, .height = 2,
859      .egcs = NCQUADBLOCKS,   .plotegcs = L" ▗▐▖▄▟▌▙█",
860      .blit = quadrant_blit,  .name = "quad",          .fill = false, },
861    { .geom = NCBLIT_3x2,     .width = 2, .height = 3,
862      .egcs = NCSEXBLOCKS,    .plotegcs = L" ����▐����������������▌����█",
863      .blit = sextant_blit,   .name = "sex",           .fill = false, },
864    { .geom = NCBLIT_BRAILLE, .width = 2, .height = 4,
865      .egcs = NCBRAILLEEGCS,
866      .plotegcs = L"⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿",
867      .blit = braille_blit,   .name = "braille",       .fill = true,  },
868    { .geom = NCBLIT_PIXEL,   .width = 1, .height = 1,
869      .egcs = L"", .plotegcs = NULL,
870      .blit = NULL,           .name = "pixel",         .fill = true,  },
871    { .geom = NCBLIT_4x1,     .width = 1, .height = 4,
872      .egcs = NULL, .plotegcs = L" ▂▄▆█",
873      .blit = tria_blit,      .name = "fourstep",      .fill = false, },
874    { .geom = NCBLIT_8x1,     .width = 1, .height = 8,
875      .egcs = NULL, .plotegcs = NCEIGHTHSB,
876      .blit = tria_blit,      .name = "eightstep",     .fill = false, },
877    { .geom = 0,              .width = 0, .height = 0,
878      .egcs = NULL, .plotegcs = NULL,
879      .blit = NULL,           .name = NULL,            .fill = false,  },
880 };
881 
set_pixel_blitter(ncblitter blitfxn)882 void set_pixel_blitter(ncblitter blitfxn){
883   struct blitset* b = notcurses_blitters;
884   while(b->geom != NCBLIT_PIXEL){
885     ++b;
886   }
887   b->blit = blitfxn;
888 }
889 
lookup_blitset(const tinfo * tcache,ncblitter_e setid,bool may_degrade)890 const struct blitset* lookup_blitset(const tinfo* tcache, ncblitter_e setid,
891                                      bool may_degrade){
892   if(setid == NCBLIT_DEFAULT){ // ought have resolved NCBLIT_DEFAULT before now
893     return NULL;
894   }
895   // without braille support, NCBLIT_BRAILLE decays to NCBLIT_3x2
896   if(setid == NCBLIT_BRAILLE){
897     if(tcache->caps.braille){
898       return &notcurses_blitters[setid - 1];
899     }else if(!may_degrade){
900       return NULL;
901     }
902     setid = NCBLIT_3x2;
903   }
904   // without bitmap support, NCBLIT_PIXEL decays to NCBLIT_3x2
905   if(setid == NCBLIT_PIXEL){
906     if(tcache->pixel_draw || tcache->pixel_draw_late){
907       return &notcurses_blitters[setid - 1];
908     }else if(!may_degrade){
909       return NULL;
910     }
911     setid = NCBLIT_3x2;
912   }
913   // without eighths support, NCBLIT_8x1 decays to NCBLIT_4x1
914   if(setid == NCBLIT_8x1){ // plotter only
915     if(tcache->caps.quadrants){
916        return &notcurses_blitters[setid - 1];
917     }else if(!may_degrade){
918       return NULL;
919     }
920     setid = NCBLIT_4x1;
921   }
922   // without quarters support, NCBLIT_4x1 decays to NCBLIT_2x1
923   if(setid == NCBLIT_4x1){ // plotter only
924     if(tcache->caps.quadrants){
925        return &notcurses_blitters[setid - 1];
926     }else if(!may_degrade){
927       return NULL;
928     }
929     setid = NCBLIT_2x1;
930   }
931   // without sextant support, NCBLIT_3x2 decays to NCBLIT_2x2
932   if(setid == NCBLIT_3x2){
933     if(tcache->caps.sextants){
934        return &notcurses_blitters[setid - 1];
935     }else if(!may_degrade){
936       return NULL;
937     }
938     setid = NCBLIT_2x2;
939   }
940   // without quadrant support, NCBLIT_2x2 decays to NCBLIT_2x1
941   if(setid == NCBLIT_2x2){
942     if(tcache->caps.quadrants){
943        return &notcurses_blitters[setid - 1];
944     }else if(!may_degrade){
945       return NULL;
946     }
947     setid = NCBLIT_2x1;
948   }
949   // without halfblock support, NCBLIT_2x1 decays to NCBLIT_1x1
950   if(setid == NCBLIT_2x1){
951     if(tcache->caps.halfblocks){
952        return &notcurses_blitters[setid - 1];
953     }else if(!may_degrade){
954       return NULL;
955     }
956     setid = NCBLIT_1x1;
957   }
958   assert(NCBLIT_1x1 == setid);
959   return &notcurses_blitters[setid - 1];
960 }
961 
notcurses_lex_blitter(const char * op,ncblitter_e * blitfxn)962 int notcurses_lex_blitter(const char* op, ncblitter_e* blitfxn){
963   const struct blitset* bset = notcurses_blitters;
964   while(bset->name){
965     if(strcasecmp(bset->name, op) == 0){
966       *blitfxn = bset->geom;
967       return 0;
968     }
969     ++bset;
970   }
971   if(strcasecmp("default", op) == 0){
972     *blitfxn = NCBLIT_DEFAULT;
973     return 0;
974   }
975   return -1;
976 }
977 
notcurses_str_blitter(ncblitter_e blitfxn)978 const char* notcurses_str_blitter(ncblitter_e blitfxn){
979   if(blitfxn == NCBLIT_DEFAULT){
980     return "default";
981   }
982   const struct blitset* bset = notcurses_blitters;
983   while(bset->name){
984     if(bset->geom == blitfxn){
985       return bset->name;
986     }
987     ++bset;
988   }
989   return NULL;
990 }
991 
ncblit_bgrx(const void * data,int linesize,const struct ncvisual_options * vopts)992 int ncblit_bgrx(const void* data, int linesize, const struct ncvisual_options* vopts){
993   if(vopts->leny <= 0 || vopts->lenx <= 0){
994     logerror("invalid lenghts %u %u\n", vopts->leny, vopts->lenx);
995     return -1;
996   }
997   if(vopts->n == NULL){
998     logerror("prohibited null plane\n");
999     return -1;
1000   }
1001   void* rdata = bgra_to_rgba(data, vopts->leny, &linesize, vopts->lenx, 0xff);
1002   if(rdata == NULL){
1003     return -1;
1004   }
1005   int r = ncblit_rgba(rdata, linesize, vopts);
1006   free(rdata);
1007   return r;
1008 }
1009 
ncblit_rgb_loose(const void * data,int linesize,const struct ncvisual_options * vopts,int alpha)1010 int ncblit_rgb_loose(const void* data, int linesize,
1011                      const struct ncvisual_options* vopts, int alpha){
1012   if(vopts->leny <= 0 || vopts->lenx <= 0){
1013     return -1;
1014   }
1015   void* rdata = rgb_loose_to_rgba(data, vopts->leny, &linesize, vopts->lenx, alpha);
1016   if(rdata == NULL){
1017     return -1;
1018   }
1019   int r = ncblit_rgba(rdata, linesize, vopts);
1020   free(rdata);
1021   return r;
1022 }
1023 
ncblit_rgb_packed(const void * data,int linesize,const struct ncvisual_options * vopts,int alpha)1024 int ncblit_rgb_packed(const void* data, int linesize,
1025                       const struct ncvisual_options* vopts, int alpha){
1026   if(vopts->leny <= 0 || vopts->lenx <= 0){
1027     return -1;
1028   }
1029   void* rdata = rgb_packed_to_rgba(data, vopts->leny, &linesize, vopts->lenx, alpha);
1030   if(rdata == NULL){
1031     return -1;
1032   }
1033   int r = ncblit_rgba(rdata, linesize, vopts);
1034   free(rdata);
1035   return r;
1036 }
1037 
ncblit_rgba(const void * data,int linesize,const struct ncvisual_options * vopts)1038 int ncblit_rgba(const void* data, int linesize, const struct ncvisual_options* vopts){
1039   if(vopts->leny <= 0 || vopts->lenx <= 0){
1040     logerror("invalid lenghts %u %u\n", vopts->leny, vopts->lenx);
1041     return -1;
1042   }
1043   if(vopts->n == NULL){
1044     logerror("prohibited null plane\n");
1045     return -1;
1046   }
1047   struct ncvisual* ncv = ncvisual_from_rgba(data, vopts->leny, linesize, vopts->lenx);
1048   if(ncv == NULL){
1049     return -1;
1050   }
1051   if(ncvisual_blit(ncplane_notcurses(vopts->n), ncv, vopts) == NULL){
1052     ncvisual_destroy(ncv);
1053     return -1;
1054   }
1055   ncvisual_destroy(ncv);
1056   return 0;
1057 }
1058 
ncvisual_media_defblitter(const notcurses * nc,ncscale_e scale)1059 ncblitter_e ncvisual_media_defblitter(const notcurses* nc, ncscale_e scale){
1060   return rgba_blitter_default(&nc->tcache, scale);
1061 }
1062