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 ¬curses_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 ¬curses_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 ¬curses_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 ¬curses_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 ¬curses_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 ¬curses_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 ¬curses_blitters[setid - 1];
953 }else if(!may_degrade){
954 return NULL;
955 }
956 setid = NCBLIT_1x1;
957 }
958 assert(NCBLIT_1x1 == setid);
959 return ¬curses_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