Notcurses 3.0.16
a blingful library for TUIs and character graphics
Loading...
Searching...
No Matches
blit.c
Go to the documentation of this file.
1#include <stddef.h>
2#include <inttypes.h>
3#include "internal.h"
4
5static const uint32_t zeroes32;
6static const unsigned char zeroes[] = "\x00\x00\x00\x00";
7
8// linearly interpolate a 24-bit RGB value along each 8-bit channel
9static inline uint32_t
10lerp(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// trilinearly interpolate a 24-bit RGB value along each 8-bit channel
26static inline uint32_t
27trilerp(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
44static inline uint32_t
45generalerp(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
57static inline unsigned
58rgba_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).
65static inline int
66tria_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 nccell_release(nc, c);
98 }else{
99 nccell_set_fg_rgb8(c, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2]);
100 nccell_set_bg_rgb8(c, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2]);
101 cell_set_blitquadrants(c, 1, 1, 1, 1);
102 if(pool_blit_direct(&nc->pool, c, " ", 1, 1) <= 0){
103 return -1;
104 }
105 ++total;
106 }
107 }
108 }
109 return total;
110}
111
112// RGBA half-block blitter. Best for most images/videos. Full fidelity
113// combined with 1:1 pixel aspect ratio.
114static inline int
115tria_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
116 const blitterargs* bargs){
117 const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND;
118//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);
119 uint32_t transcolor = bargs->transcolor;
120 unsigned dimy, dimx, x, y;
121 int total = 0; // number of cells written
122 ncplane_dim_yx(nc, &dimy, &dimx);
123 // FIXME not going to necessarily be safe on all architectures hrmmm
124 const unsigned char* dat = data;
125 int visy = bargs->begy;
126 for(y = bargs->u.cell.placey ; visy < (bargs->begy + leny) && y < dimy ; ++y, visy += 2){
127 if(ncplane_cursor_move_yx(nc, y, bargs->u.cell.placex < 0 ? 0 : bargs->u.cell.placex)){
128 return -1;
129 }
130 int visx = bargs->begx;
131 for(x = bargs->u.cell.placex ; visx < (bargs->begx + lenx) && x < dimx ; ++x, ++visx){
132 const unsigned char* rgbbase_up = dat + (linesize * visy) + (visx * 4);
133 const unsigned char* rgbbase_down = zeroes;
134 if(visy < bargs->begy + leny - 1){
135 rgbbase_down = dat + (linesize * (visy + 1)) + (visx * 4);
136 }
137//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]);
138 nccell* c = ncplane_cell_ref_yx(nc, y, x);
139 // use the default for the background, as that's the only way it's
140 // effective in that case anyway
141 c->channels = 0;
142 c->stylemask = 0;
143 if(blendcolors){
144 nccell_set_bg_alpha(c, NCALPHA_BLEND);
145 nccell_set_fg_alpha(c, NCALPHA_BLEND);
146 }
147 if(rgba_trans_q(rgbbase_up, transcolor) || rgba_trans_q(rgbbase_down, transcolor)){
148 nccell_set_bg_alpha(c, NCALPHA_TRANSPARENT);
149 if(rgba_trans_q(rgbbase_up, transcolor) && rgba_trans_q(rgbbase_down, transcolor)){
150 nccell_set_fg_alpha(c, NCALPHA_TRANSPARENT);
151 nccell_release(nc, c);
152 }else if(rgba_trans_q(rgbbase_up, transcolor)){ // down has the color
153 if(pool_blit_direct(&nc->pool, c, "\u2584", strlen("\u2584"), 1) <= 0){
154 return -1;
155 }
156 nccell_set_fg_rgb8(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]);
157 cell_set_blitquadrants(c, 0, 0, 1, 1);
158 ++total;
159 }else{ // up has the color
160 // upper half block
161 if(pool_blit_direct(&nc->pool, c, "\u2580", strlen("\u2580"), 1) <= 0){
162 return -1;
163 }
164 nccell_set_fg_rgb8(c, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2]);
165 cell_set_blitquadrants(c, 1, 1, 0, 0);
166 ++total;
167 }
168 }else{
169 if(memcmp(rgbbase_up, rgbbase_down, 3) == 0){
170 nccell_set_fg_rgb8(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]);
171 nccell_set_bg_rgb8(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]);
172 cell_set_blitquadrants(c, 0, 0, 0, 0);
173 if(pool_blit_direct(&nc->pool, c, " ", 1, 1) <= 0){
174 return -1;
175 }
176 }else{
177 nccell_set_fg_rgb8(c, rgbbase_up[0], rgbbase_up[1], rgbbase_up[2]);
178 nccell_set_bg_rgb8(c, rgbbase_down[0], rgbbase_down[1], rgbbase_down[2]);
179 cell_set_blitquadrants(c, 1, 1, 1, 1);
180 if(pool_blit_direct(&nc->pool, c, "\u2580", strlen("\u2580"), 1) <= 0){
181 return -1;
182 }
183 }
184 ++total;
185 }
186 }
187 }
188 return total;
189}
190
191// once we find the closest pair of colors, we need look at the other two
192// colors, and determine whether either belongs with us rather with them.
193// if so, take the closer, and trilerp it in with us. otherwise, lerp the
194// two excluded pixels (and retain our original lerp).
195static const struct qdriver {
196 int pair[2]; // indices of contributing pair
197 int others[2]; // indices of excluded pair
198 const char* egc; // EGC corresponding to contributing pair
199 const char* oth0egc; // EGC upon absorbing others[0]
200 const char* oth1egc; // EGC upon absorbing others[1]
201} quadrant_drivers[6] = {
202 { .pair = { 0, 1 }, .others = { 2, 3 }, .egc = "▀", .oth0egc = "▛", .oth1egc = "▜", },
203 { .pair = { 0, 2 }, .others = { 1, 3 }, .egc = "▌", .oth0egc = "▛", .oth1egc = "▙", },
204 { .pair = { 0, 3 }, .others = { 1, 2 }, .egc = "▚", .oth0egc = "▜", .oth1egc = "▙", },
205 { .pair = { 1, 2 }, .others = { 0, 3 }, .egc = "▞", .oth0egc = "▛", .oth1egc = "▟", },
206 { .pair = { 1, 3 }, .others = { 0, 2 }, .egc = "▐", .oth0egc = "▜", .oth1egc = "▟", },
207 { .pair = { 2, 3 }, .others = { 0, 1 }, .egc = "▄", .oth0egc = "▙", .oth1egc = "▟", },
208};
209
210// get the six distances between four colors. diffs must be an array of
211// at least 6 uint32_t values.
212static void
213rgb_4diff(uint32_t* diffs, uint32_t tl, uint32_t tr, uint32_t bl, uint32_t br){
214 struct rgb {
215 unsigned r, g, b;
216 } colors[4];
217 ncchannel_rgb8(tl, &colors[0].r, &colors[0].g, &colors[0].b);
218 ncchannel_rgb8(tr, &colors[1].r, &colors[1].g, &colors[1].b);
219 ncchannel_rgb8(bl, &colors[2].r, &colors[2].g, &colors[2].b);
220 ncchannel_rgb8(br, &colors[3].r, &colors[3].g, &colors[3].b);
221 for(size_t idx = 0 ; idx < sizeof(quadrant_drivers) / sizeof(*quadrant_drivers) ; ++idx){
222 const struct qdriver* qd = quadrant_drivers + idx;
223 const struct rgb* rgb0 = colors + qd->pair[0];
224 const struct rgb* rgb1 = colors + qd->pair[1];
225 diffs[idx] = rgb_diff(rgb0->r, rgb0->g, rgb0->b,
226 rgb1->r, rgb1->g, rgb1->b);
227 }
228}
229
230// solve for the EGC and two colors to best represent four colors at top
231// left, top right, bot left, bot right
232static inline const char*
233quadrant_solver(uint32_t tl, uint32_t tr, uint32_t bl, uint32_t br,
234 uint32_t* fore, uint32_t* back, unsigned nointerpolate){
235 const uint32_t colors[4] = { tl, tr, bl, br };
236//fprintf(stderr, "%08x/%08x/%08x/%08x\n", tl, tr, bl, br);
237 uint32_t diffs[sizeof(quadrant_drivers) / sizeof(*quadrant_drivers)];
238 rgb_4diff(diffs, tl, tr, bl, br);
239 // compiler can't verify that we'll always be less than 769 somewhere,
240 // so fuck it, just go ahead and initialize to 0 / diffs[0]
241 size_t mindiffidx = 0;
242 unsigned mindiff = diffs[0]; // 3 * 256 + 1; // max distance is 256 * 3
243 // if all diffs are 0, emit a space
244 bool allzerodiffs = (mindiff == 0);
245 for(size_t idx = 1 ; idx < sizeof(diffs) / sizeof(*diffs) ; ++idx){
246 if(diffs[idx] < mindiff){
247 mindiffidx = idx;
248 mindiff = diffs[idx];
249 }
250 if(diffs[idx]){
251 allzerodiffs = false;
252 }
253 }
254 if(allzerodiffs){
255 *fore = *back = tl;
256 return " ";
257 }
258 // at this point, 0 <= mindiffidx <= 5. foreground color will be the
259 // lerp of this nearest pair. we then check the other two. if they are
260 // closer to one another than either is to our lerp, lerp between them.
261 // otherwise, bring the closer one into our lerped fold.
262 const struct qdriver* qd = &quadrant_drivers[mindiffidx];
263 // the diff of the excluded pair is conveniently located at the inverse
264 // location within diffs[] viz mindiffidx.
265 // const uint32_t otherdiff = diffs[5 - mindiffidx];
266 *fore = lerp(colors[qd->pair[0]], colors[qd->pair[1]], nointerpolate);
267 *back = lerp(colors[qd->others[0]], colors[qd->others[1]], nointerpolate);
268//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]);
269 const char* egc = qd->egc;
270 // break down the excluded pair and lerp
271 unsigned r0, r1, r2, g0, g1, g2, b0, b1, b2;
272 unsigned roth, goth, both, rlerp, glerp, blerp;
273 ncchannel_rgb8(*back, &roth, &goth, &both);
274 ncchannel_rgb8(*fore, &rlerp, &glerp, &blerp);
275//fprintf(stderr, "rgbs: %02x %02x %02x / %02x %02x %02x\n", r0, g0, b0, r1, g1, b1);
276 // get diffs of the excluded two from both lerps
277 ncchannel_rgb8(colors[qd->others[0]], &r0, &g0, &b0);
278 ncchannel_rgb8(colors[qd->others[1]], &r1, &g1, &b1);
279 diffs[0] = rgb_diff(r0, g0, b0, roth, goth, both);
280 diffs[1] = rgb_diff(r1, g1, b1, roth, goth, both);
281 diffs[2] = rgb_diff(r0, g0, b0, rlerp, glerp, blerp);
282 diffs[3] = rgb_diff(r1, g1, b1, rlerp, glerp, blerp);
283 // get diffs of the included two from their lerp
284 ncchannel_rgb8(colors[qd->pair[0]], &r0, &g0, &b0);
285 ncchannel_rgb8(colors[qd->pair[1]], &r1, &g1, &b1);
286 diffs[4] = rgb_diff(r0, g0, b0, rlerp, glerp, blerp);
287 diffs[5] = rgb_diff(r1, g1, b1, rlerp, glerp, blerp);
288 unsigned curdiff = diffs[0] + diffs[1] + diffs[4] + diffs[5];
289 // it might be better to combine three, and leave one totally unchanged.
290 // propose a trilerps; we only need consider the member of the excluded pair
291 // closer to the primary lerp. recalculate total diff; merge if lower.
292 if(diffs[2] < diffs[3]){
293 unsigned tri = trilerp(colors[qd->pair[0]], colors[qd->pair[1]], colors[qd->others[0]],
294 nointerpolate);
295 ncchannel_rgb8(colors[qd->others[0]], &r2, &g2, &b2);
296 ncchannel_rgb8(tri, &roth, &goth, &both);
297 if(rgb_diff(r0, g0, b0, roth, goth, both) +
298 rgb_diff(r1, g1, b1, roth, goth, both) +
299 rgb_diff(r2, g2, b2, roth, goth, both) < curdiff){
300 egc = qd->oth0egc;
301 *back = colors[qd->others[1]];
302 *fore = tri;
303 }
304//fprintf(stderr, "quadblitter swap type 1\n");
305 }else{
306 unsigned tri = trilerp(colors[qd->pair[0]], colors[qd->pair[1]], colors[qd->others[1]],
307 nointerpolate);
308 ncchannel_rgb8(colors[qd->others[1]], &r2, &g2, &b2);
309 ncchannel_rgb8(tri, &roth, &goth, &both);
310 if(rgb_diff(r0, g0, b0, roth, goth, both) +
311 rgb_diff(r1, g1, b1, roth, goth, both) +
312 rgb_diff(r2, g2, b2, roth, goth, both) < curdiff){
313 egc = qd->oth1egc;
314 *back = colors[qd->others[0]];
315 *fore = tri;
316 }
317//fprintf(stderr, "quadblitter swap type 2\n");
318 }
319 return egc;
320}
321
322// quadrant check for transparency. returns an EGC if we found transparent
323// quads and have solved for colors (this EGC ought then be loaded into the
324// cell). returns NULL otherwise. transparency trumps everything else in terms
325// of priority -- if even one quadrant is transparent, we will have a
326// transparent background, and lerp the rest together for foreground. we thus
327// have a 16-way conditional tree in which each EGC must show up exactly once.
328// FIXME we ought be able to just build up a bitstring and use it as an index!
329// FIXME pass in rgbas as array of uint32_t ala sexblitter
330static inline const char*
331qtrans_check(nccell* c, unsigned blendcolors,
332 const unsigned char* rgbbase_tl, const unsigned char* rgbbase_tr,
333 const unsigned char* rgbbase_bl, const unsigned char* rgbbase_br,
334 uint32_t transcolor, unsigned nointerpolate){
335 uint32_t tl = 0, tr = 0, bl = 0, br = 0;
336 ncchannel_set_rgb8(&tl, rgbbase_tl[0], rgbbase_tl[1], rgbbase_tl[2]);
337 ncchannel_set_rgb8(&tr, rgbbase_tr[0], rgbbase_tr[1], rgbbase_tr[2]);
338 ncchannel_set_rgb8(&bl, rgbbase_bl[0], rgbbase_bl[1], rgbbase_bl[2]);
339 ncchannel_set_rgb8(&br, rgbbase_br[0], rgbbase_br[1], rgbbase_br[2]);
340 const char* egc = NULL;
341 if(rgba_trans_q(rgbbase_tl, transcolor)){
342 // top left is transparent
343 if(rgba_trans_q(rgbbase_tr, transcolor)){
344 // all of top is transparent
345 if(rgba_trans_q(rgbbase_bl, transcolor)){
346 // top and left are transparent
347 if(rgba_trans_q(rgbbase_br, transcolor)){
348 // entirety is transparent, load with nul (but not NULL)
349 nccell_set_fg_default(c);
350 cell_set_blitquadrants(c, 0, 0, 0, 0);
351 egc = "";
352 }else{
353 nccell_set_fg_rgb8(c, rgbbase_br[0], rgbbase_br[1], rgbbase_br[2]);
354 cell_set_blitquadrants(c, 0, 0, 0, 1);
355 egc = "▗";
356 }
357 }else{
358 if(rgba_trans_q(rgbbase_br, transcolor)){
359 nccell_set_fg_rgb8(c, rgbbase_bl[0], rgbbase_bl[1], rgbbase_bl[2]);
360 cell_set_blitquadrants(c, 0, 0, 1, 0);
361 egc = "▖";
362 }else{
363 cell_set_fchannel(c, lerp(bl, br, nointerpolate));
364 cell_set_blitquadrants(c, 0, 0, 1, 1);
365 egc = "▄";
366 }
367 }
368 }else{ // top right is foreground, top left is transparent
369 if(rgba_trans_q(rgbbase_bl, transcolor)){
370 if(rgba_trans_q(rgbbase_br, transcolor)){ // entire bottom is transparent
371 nccell_set_fg_rgb8(c, rgbbase_tr[0], rgbbase_tr[1], rgbbase_tr[2]);
372 cell_set_blitquadrants(c, 0, 1, 0, 0);
373 egc = "▝";
374 }else{
375 cell_set_fchannel(c, lerp(tr, br, nointerpolate));
376 cell_set_blitquadrants(c, 0, 1, 0, 1);
377 egc = "▐";
378 }
379 }else if(rgba_trans_q(rgbbase_br, transcolor)){ // only br is transparent
380 cell_set_fchannel(c, lerp(tr, bl, nointerpolate));
381 cell_set_blitquadrants(c, 0, 1, 1, 0);
382 egc = "▞";
383 }else{
384 cell_set_fchannel(c, trilerp(tr, bl, br, nointerpolate));
385 cell_set_blitquadrants(c, 0, 1, 1, 1);
386 egc = "▟";
387 }
388 }
389 }else{ // topleft is foreground for all here
390 if(rgba_trans_q(rgbbase_tr, transcolor)){
391 if(rgba_trans_q(rgbbase_bl, transcolor)){
392 if(rgba_trans_q(rgbbase_br, transcolor)){
393 nccell_set_fg_rgb8(c, rgbbase_tl[0], rgbbase_tl[1], rgbbase_tl[2]);
394 cell_set_blitquadrants(c, 1, 0, 0, 0);
395 egc = "▘";
396 }else{
397 cell_set_fchannel(c, lerp(tl, br, nointerpolate));
398 cell_set_blitquadrants(c, 1, 0, 0, 1);
399 egc = "▚";
400 }
401 }else if(rgba_trans_q(rgbbase_br, transcolor)){
402 cell_set_fchannel(c, lerp(tl, bl, nointerpolate));
403 cell_set_blitquadrants(c, 1, 0, 1, 0);
404 egc = "▌";
405 }else{
406 cell_set_fchannel(c, trilerp(tl, bl, br, nointerpolate));
407 cell_set_blitquadrants(c, 1, 0, 1, 1);
408 egc = "▙";
409 }
410 }else if(rgba_trans_q(rgbbase_bl, transcolor)){
411 if(rgba_trans_q(rgbbase_br, transcolor)){ // entire bottom is transparent
412 cell_set_fchannel(c, lerp(tl, tr, nointerpolate));
413 cell_set_blitquadrants(c, 1, 1, 0, 0);
414 egc = "▀";
415 }else{ // only bl is transparent
416 cell_set_fchannel(c, trilerp(tl, tr, br, nointerpolate));
417 cell_set_blitquadrants(c, 1, 1, 0, 1);
418 egc = "▜";
419 }
420 }else if(rgba_trans_q(rgbbase_br, transcolor)){ // only br is transparent
421 cell_set_fchannel(c, trilerp(tl, tr, bl, nointerpolate));
422 cell_set_blitquadrants(c, 1, 1, 1, 0);
423 egc = "▛";
424 }else{
425 return NULL; // no transparency
426 }
427 }
428 assert(egc);
429 nccell_set_bg_alpha(c, NCALPHA_TRANSPARENT);
430 if(*egc == '\0'){
431 nccell_set_fg_alpha(c, NCALPHA_TRANSPARENT);
432 }else if(blendcolors){
433 nccell_set_fg_alpha(c, NCALPHA_BLEND);
434 }
435//fprintf(stderr, "QBQ: 0x%x\n", cell_blittedquadrants(c));
436 return egc;
437}
438
439// quadrant blitter. maps 2x2 to each cell. since we only have two colors at
440// our disposal (foreground and background), we lose some fidelity.
441static inline int
442quadrant_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
443 const blitterargs* bargs){
444 const unsigned nointerpolate = bargs->flags & NCVISUAL_OPTION_NOINTERPOLATE;
445 const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND;
446 unsigned dimy, dimx, x, y;
447 int total = 0; // number of cells written
448 ncplane_dim_yx(nc, &dimy, &dimx);
449//fprintf(stderr, "quadblitter %dx%d -> %d/%d+%d/%d\n", leny, lenx, dimy, dimx, bargs->u.cell.placey, bargs->u.cell.placex);
450 // FIXME not going to necessarily be safe on all architectures hrmmm
451 const unsigned char* dat = data;
452 int visy = bargs->begy;
453 for(y = bargs->u.cell.placey ; visy < (bargs->begy + leny) && y < dimy ; ++y, visy += 2){
454 if(ncplane_cursor_move_yx(nc, y, bargs->u.cell.placex < 0 ? 0 : bargs->u.cell.placex)){
455 return -1;
456 }
457 int visx = bargs->begx;
458 for(x = bargs->u.cell.placex ; visx < (bargs->begx + lenx) && x < dimx ; ++x, visx += 2){
459 const unsigned char* rgbbase_tl = dat + (linesize * visy) + (visx * 4);
460 const unsigned char* rgbbase_tr = zeroes;
461 const unsigned char* rgbbase_bl = zeroes;
462 const unsigned char* rgbbase_br = zeroes;
463 if(visx < bargs->begx + lenx - 1){
464 rgbbase_tr = dat + (linesize * visy) + ((visx + 1) * 4);
465 if(visy < bargs->begy + leny - 1){
466 rgbbase_br = dat + (linesize * (visy + 1)) + ((visx + 1) * 4);
467 }
468 }
469 if(visy < bargs->begy + leny - 1){
470 rgbbase_bl = dat + (linesize * (visy + 1)) + (visx * 4);
471 }
472//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]);
473 nccell* c = ncplane_cell_ref_yx(nc, y, x);
474 c->channels = 0;
475 c->stylemask = 0;
476 const char* egc = qtrans_check(c, blendcolors, rgbbase_tl, rgbbase_tr,
477 rgbbase_bl, rgbbase_br, bargs->transcolor,
478 nointerpolate);
479 if(egc == NULL){
480 uint32_t tl = 0, tr = 0, bl = 0, br = 0;
481 ncchannel_set_rgb8(&tl, rgbbase_tl[0], rgbbase_tl[1], rgbbase_tl[2]);
482 ncchannel_set_rgb8(&tr, rgbbase_tr[0], rgbbase_tr[1], rgbbase_tr[2]);
483 ncchannel_set_rgb8(&bl, rgbbase_bl[0], rgbbase_bl[1], rgbbase_bl[2]);
484 ncchannel_set_rgb8(&br, rgbbase_br[0], rgbbase_br[1], rgbbase_br[2]);
485 uint32_t bg, fg;
486//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);
487 egc = quadrant_solver(tl, tr, bl, br, &fg, &bg, nointerpolate);
488 assert(egc);
489//fprintf(stderr, "%d/%d %08x/%08x\n", y, x, fg, bg);
490 cell_set_fchannel(c, fg);
491 cell_set_bchannel(c, bg);
492 if(blendcolors){
493 nccell_set_bg_alpha(c, NCALPHA_BLEND);
494 nccell_set_fg_alpha(c, NCALPHA_BLEND);
495 }
496 cell_set_blitquadrants(c, 1, 1, 1, 1);
497 }
498 if(*egc){
499 if(pool_blit_direct(&nc->pool, c, egc, strlen(egc), 1) <= 0){
500 return -1;
501 }
502 ++total;
503 }else{
504 nccell_release(nc, c);
505 }
506 }
507 }
508 return total;
509}
510
511// Solve for the cell rendered by this cellheightX2 sample. None of the input
512// pixels may be transparent (that ought already have been handled). We use
513// exhaustive search, which might be quite computationally intensive for the
514// worst case (all pixels are different colors). We want to solve for the
515// 2-partition of pixels that minimizes total source distance from the
516// resulting lerps.
517static const char*
518hires_solver(const uint32_t rgbas[6], uint64_t* channels, unsigned blendcolors,
519 unsigned nointerpolate, unsigned cellheight,
520 const char* const* transegcs, const unsigned* partitions){
521 const unsigned parcount = 1u << (cellheight * 2 - 1);
522 // we loop over the bitstrings, dividing the pixels into two sets, and then
523 // taking a general lerp over each set. we then compute the sum of absolute
524 // differences, and see if it's the new minimum.
525 int best = -1;
526 uint32_t mindiff = UINT_MAX;
527//fprintf(stderr, "%06x %06x\n%06x %06x\n%06x %06x\n", rgbas[0], rgbas[1], rgbas[2], rgbas[3], rgbas[4], rgbas[5]);
528 for(size_t glyph = 0 ; glyph < parcount ; ++glyph){
529 unsigned rsum0 = 0, rsum1 = 0;
530 unsigned gsum0 = 0, gsum1 = 0;
531 unsigned bsum0 = 0, bsum1 = 0;
532 int insum = 0;
533 int outsum = 0;
534 for(unsigned mask = 0 ; mask < cellheight * 2 ; ++mask){
535 if(partitions[glyph] & (1u << mask)){
536 if(!nointerpolate || !insum){
537 rsum0 += ncpixel_r(rgbas[mask]);
538 gsum0 += ncpixel_g(rgbas[mask]);
539 bsum0 += ncpixel_b(rgbas[mask]);
540 ++insum;
541 }
542 }else{
543 if(!nointerpolate || !outsum){
544 rsum1 += ncpixel_r(rgbas[mask]);
545 gsum1 += ncpixel_g(rgbas[mask]);
546 bsum1 += ncpixel_b(rgbas[mask]);
547 ++outsum;
548 }
549 }
550 }
551 uint32_t l0 = generalerp(rsum0, gsum0, bsum0, insum);
552 uint32_t l1 = generalerp(rsum1, gsum1, bsum1, outsum);
553//fprintf(stderr, "sum0: %06x sum1: %06x insum: %d\n", l0 & 0xffffffu, l1 & 0xffffffu, insum);
554 uint32_t totaldiff = 0;
555 for(unsigned mask = 0 ; mask < cellheight * 2 ; ++mask){
556 unsigned r, g, b;
557 if(partitions[glyph] & (1u << mask)){
558 ncchannel_rgb8(l0, &r, &g, &b);
559 }else{
560 ncchannel_rgb8(l1, &r, &g, &b);
561 }
562 uint32_t rdiff = rgb_diff(ncpixel_r(rgbas[mask]), ncpixel_g(rgbas[mask]),
563 ncpixel_b(rgbas[mask]), r, g, b);
564 totaldiff += rdiff;
565//fprintf(stderr, "mask: %u totaldiff: %u insum: %d (%08x / %08x)\n", mask, totaldiff, insum, l0, l1);
566 }
567//fprintf(stderr, "bits: %u %zu totaldiff: %f best: %f (%d)\n", partitions[glyph], glyph, totaldiff, mindiff, best);
568 if(totaldiff < mindiff){
569 mindiff = totaldiff;
570 best = glyph;
571 ncchannels_set_fchannel(channels, l0);
572 ncchannels_set_bchannel(channels, l1);
573 }
574 if(totaldiff == 0){ // can't beat that!
575 break;
576 }
577 }
578//fprintf(stderr, "solved for best: %d (%u)\n", best, mindiff);
579 if(blendcolors){
580 ncchannels_set_fg_alpha(channels, NCALPHA_BLEND);
581 ncchannels_set_bg_alpha(channels, NCALPHA_BLEND);
582 }
583 best = parcount * 2 - 1 - partitions[best];
584 return transegcs[best];
585}
586
587// FIXME replace both of these arrays of pointers with fixed-width matrices
588// bit of index is *set* where sextant *is not*
589// 32: bottom right 16: bottom left
590// 8: middle right 4: middle left
591// 2: upper right 1: upper left
592static const char* const sextrans[64] = {
593 "█", "🬻", "🬺", "🬹", "🬸", "🬷", "🬶", "🬵",
594 "🬴", "🬳", "🬲", "🬱", "🬰", "🬯", "🬮", "🬭",
595 "🬬", "🬫", "🬪", "🬩", "🬨", "▐", "🬧", "🬦",
596 "🬥", "🬤", "🬣", "🬢", "🬡", "🬠", "🬟", "🬞",
597 "🬝", "🬜", "🬛", "🬚", "🬙", "🬘", "🬗", "🬖",
598 "🬕", "🬔", "▌", "🬓", "🬒", "🬑", "🬐", "🬏",
599 "🬎", "🬍", "🬌", "🬋", "🬊", "🬉", "🬈", "🬇",
600 "🬆", "🬅", "🬄", "🬃", "🬂", "🬁", "🬀", " ",
601};
602
603// bit of index is *set* where octant *is not*
604// 1: row 0 left 2: row 0 right
605// 4: row 1 left 8: row 1 right
606// 16: row 2 left 32: row 2 right
607// 64: row 3 left 128: row 3 right
608static const char* const octtrans[256] = {
609 u8"\U00002588", // █ 255 all eight set (full)
610 u8"\U0001cde5", // 𜷥 254 missing upper left (o2345678)
611 u8"\U0001cde4", // 𜷤 253 missing upper right (o1345678)
612 u8"\U00002586", // ▆ 252 missing row 0 (lower three quarters)
613 u8"\U0001cde3", // 𜷣 251 missing row 1 left (o1245678)
614 u8"\U0000259f", // ▟ 250 (q upper right and lower left and lower right)
615 u8"\U0001cde2", // 𜷢 249 (o1245678)
616 u8"\U0001cde1", // 𜷡 248 (o45678)
617 u8"\U0001cde0", // 𜷠 247 missing row 1 right (o1235678)
618 u8"\U0001cddf", // 𜷟 246 missing 0 left 1 right (o235678)
619 u8"\U00002599", // ▙ 245 missing 0/1 right (q upper left and lower left and lower right)
620 u8"\U0001cdde", // 𜷞 244
621 u8"\U0001cddd", // 𜷝 243
622 u8"\U0001cddc", // 𜷜 242
623 u8"\U0001cddb", // 𜷛 241 (o15678)
624 u8"\U00002584", // ▄ 240 2/3 full (lower half)
625 u8"\U0001cdda", // 𜷚 239 (o1234678)
626 u8"\U0001cdd9", // 𜷙 238 (o234678)
627 u8"\U0001cdd8", // 𜷘 237 (o134678)
628 u8"\U0001cdd7", // 𜷗 236 (o34678)
629 u8"\U0001cdd6", // 𜷖 235 (o124678)
630 u8"\U0001cdd5", // 𜷕 234 (o24678)
631 u8"\U0001cdd4", // 𜷔 233 (o14678)
632 u8"\U0001cdd3", // 𜷓 232 (o4678)
633 u8"\U0001cdd2", // 𜷒 231
634 u8"\U0001cdd1", // 𜷑 230
635 u8"\U0001cdd0", // 𜷐 229
636 u8"\U0001cdcf", // 𜷏 228 (o145678)
637 u8"\U0001cdce", // 𜷎 227
638 u8"\U0001cdcd", // 𜷍 226
639 u8"\U0001cdcc", // 𜷌 225
640 u8"\U0001cdcb", // 𜷋 224 (o678)
641 u8"\U0001cdca",
642 u8"\U0001cdc9",
643 u8"\U0001cdc8",
644 u8"\U0001cdc7", // 𜷇 220 (o34578)
645 u8"\U0001cdc6",
646 u8"\U0001cdc5",
647 u8"\U0001cdc4",
648 u8"\U0001cdc3", // 𜷃 216 (o4578)
649 u8"\U0001cdc2",
650 u8"\U0001cdc1",
651 u8"\U0001cdc0",
652 u8"\U0001cdbf", // 𜶿 212 (o3578)
653 u8"\U0001cdbe",
654 u8"\U0001cdbd",
655 u8"\U0001cdbc",
656 u8"\U0001cdbb", // 𜶻 208 (o578)
657 u8"\U0001cdba",
658 u8"\U0001cdb9",
659 u8"\U0001cdb8",
660 u8"\U0001cdb7", // 𜶷 204 (o4578)
661 u8"\U0001cdb6",
662 u8"\U0001cdb5",
663 u8"\U0001cdb4",
664 u8"\U0001cdb3", // 𜶳 200 (o478)
665 u8"\U0001cdb2",
666 u8"\U0001cdb1",
667 u8"\U0001cdb0",
668 u8"\U0001cdaf", // 𜶯 196 (o378)
669 u8"\U0001cdae",
670 u8"\U0001cdad",
671 u8"\U0001cdac",
672 u8"\U00002582", // ▂ 192 (lower one quarter)
673 u8"\U0001cdab",
674 u8"\U0001cdaa",
675 u8"\U0001cda9",
676 u8"\U0001cda8", // 𜶨 188 (o34568)
677 u8"\U0001cda7",
678 u8"\U0001cda6",
679 u8"\U0001cda5",
680 u8"\U0001cda4",
681 u8"\U0001cda3",
682 u8"\U0001cda2",
683 u8"\U0001cda1",
684 u8"\U0001cda0",
685 u8"\U0001cd9f",
686 u8"\U0001cd9e",
687 u8"\U0001cd9d",
688 u8"\U0001cd9c",
689 u8"\U0000259c", // ▜ 175 (q upper left and upper right and lower right)
690 u8"\U0001cd9b",
691 u8"\U0001cd9a",
692 u8"\U0001cd99",
693 u8"\U0001cd98",
694 u8"\U00002590", // ▐ 170 (right half)
695 u8"\U0001cd97",
696 u8"\U0001cd96",
697 u8"\U0001cd95",
698 u8"\U0001cd94",
699 u8"\U0000259a", // ▚ 165 (q upper left and lower right)
700 u8"\U0001cd93",
701 u8"\U0001cd92",
702 u8"\U0001cd91",
703 u8"\U0001cd90",
704 u8"\U00002597", // ▗ 160 (q lower right)
705 u8"\U0001cd8f",
706 u8"\U0001cd8e",
707 u8"\U0001cd8d",
708 u8"\U0001cd8c", // 𜶌 156 (u3458)
709 u8"\U0001cd8b",
710 u8"\U0001cd8a",
711 u8"\U0001cd89",
712 u8"\U0001cd88", // 𜶈 152 (u458)
713 u8"\U0001cd87",
714 u8"\U0001cd86",
715 u8"\U0001cd85",
716 u8"\U0001cd84", // 𜶄 148 (u358)
717 u8"\U0001cd83",
718 u8"\U0001cd82",
719 u8"\U0001cd81",
720 u8"\U0001cd80", // 𜶀 144 (u58)
721 u8"\U0001cd7f",
722 u8"\U0001cd7e",
723 u8"\U0001cd7d",
724 u8"\U0001cd7c", // 𜵼 140 (u348)
725 u8"\U0001cd7b",
726 u8"\U0001cd7a",
727 u8"\U0001cd79",
728 u8"\U0001cd78", // 𜵸 136 (u48)
729 u8"\U0001cd77",
730 u8"\U0001cd76",
731 u8"\U0001cd75",
732 u8"\U0001cd74", // 𜵴 132 (u38)
733 u8"\U0001cd73",
734 u8"\U0001cd72",
735 u8"\U0001cd71",
736 u8"\U0001cea0", // 𜺠 128 lower right only (right half lower one quarter)
737 u8"\U0001cd70", // 𜵰 127 missing lower right (u1234567)
738 u8"\U0001cd6f",
739 u8"\U0001cd6e",
740 u8"\U0001cd6d",
741 u8"\U0001cd6c",
742 u8"\U0001cd6b",
743 u8"\U0001cd6a",
744 u8"\U0001cd69",
745 u8"\U0001cd68",
746 u8"\U0001cd67",
747 u8"\U0001cd66",
748 u8"\U0001cd65",
749 u8"\U0001cd64",
750 u8"\U0001cd63",
751 u8"\U0001cd62",
752 u8"\U0001cd61",
753 u8"\U0001cd60",
754 u8"\U0001cd5f",
755 u8"\U0001cd5e",
756 u8"\U0001cd5d",
757 u8"\U0001cd5c",
758 u8"\U0001cd5b",
759 u8"\U0001cd5a",
760 u8"\U0001cd59",
761 u8"\U0001cd58",
762 u8"\U0001cd57",
763 u8"\U0001cd56",
764 u8"\U0001cd55",
765 u8"\U0001cd54",
766 u8"\U0001cd53",
767 u8"\U0001cd52",
768 u8"\U0001cd51",
769 u8"\U0000259b", // ▛ 95 0/1 full 2/3 left (q upper left and upper right and lower left)
770 u8"\U0001cd50",
771 u8"\U0001cd4f",
772 u8"\U0001cd4e",
773 u8"\U0001cd4d",
774 u8"\U0000259e", // ▞ 92 0/1 right 2/3 left (q upper right and lower left)
775 u8"\U0001cd4c",
776 u8"\U0001cd4b",
777 u8"\U0001cd4a",
778 u8"\U0001cd49",
779 u8"\U0000258c", // ▌ 85 0/1/2/3 left (left block)
780 u8"\U0001cd48",
781 u8"\U0001cd47",
782 u8"\U0001cd46",
783 u8"\U0001cd45",
784 u8"\U00002596", // ▖ 80 2/3 left (q lower left)
785 u8"\U0001cd44",
786 u8"\U0001cd43",
787 u8"\U0001cd42",
788 u8"\U0001cd41",
789 u8"\U0001cd40",
790 u8"\U0001cd3f",
791 u8"\U0001cd3e",
792 u8"\U0001cd3d",
793 u8"\U0001cd3c",
794 u8"\U0001cd3b",
795 u8"\U0001cd3a",
796 u8"\U0001cd39",
797 u8"\U0001cd38",
798 u8"\U0001cd37", // 𜴷 66 0 right 3 left (o27)
799 u8"\U0001cd36", // 𜴶 65 0 left 3 left (o17)
800 u8"\U0001cea3", // 𜺣 64 lower left only (left half lower one quarter)
801 u8"\U0001fb85", // 🮅 63 row 0/1/2 full (upper three quarters)
802 u8"\U0001cd35", // 𜴵 62 (o23456)
803 u8"\U0001cd34", // 𜴴 61 (o13456)
804 u8"\U0001cd33", // 𜴳 60 (o3456)
805 u8"\U0001cd32", // 𜴲 59 (o12456)
806 u8"\U0001cd31", // 𜴱 58 (o2456)
807 u8"\U0001cd30", // 𜴰 57 0 left 1 right 2 full (o1456)
808 u8"\U0001cd2f", // 𜴯 56 (o456)
809 u8"\U0001cd2e", // 𜴮 55
810 u8"\U0001cd2d", // 𜴭 54
811 u8"\U0001cd2c", // 𜴬 53
812 u8"\U0001cd2b", // 𜴫 52
813 u8"\U0001cd2a", // 𜴪 51
814 u8"\U0001cd29", // 𜴩 50
815 u8"\U0001cd28", // 𜴨 49
816 u8"\U0001cd27", // 𜴧 48
817 u8"\U0001cd26", // 𜴦 47 (o12346)
818 u8"\U0001cd25", // 𜴥 46 (o2346)
819 u8"\U0001cd24", // 𜴤 45 (o1346)
820 u8"\U0001cd23", // 𜴣 44 (o346)
821 u8"\U0001cd22", // 𜴢 43 (o1246)
822 u8"\U0001cd21", // 𜴡 42 (o246)
823 u8"\U0001cd20", // 𜴠 41 (o146)
824 u8"\U0001fbe7", // 🯧 40 (middle right one quarter)
825 u8"\U0001cd1f", // 𜴟 39 (o1236)
826 u8"\U0001cd1e", // 𜴞 38 (o236)
827 u8"\U0001cd1d", // 𜴝 37 (o136)
828 u8"\U0001cd1c", // 𜴜 36 (o36)
829 u8"\U0001cd1b", // 𜴛 35 (o126)
830 u8"\U0001cd1a", // 𜴚 34 (o26)
831 u8"\U0001cd19", // 𜴙 33 (o16)
832 u8"\U0001cd18", // 𜴘 32 row 2 right only (o6)
833 u8"\U0001cd17", // 𜴗 31 (o12345)
834 u8"\U0001cd16", // 𜴖 30 (o2345)
835 u8"\U0001cd15", // 𜴕 29 (o1345)
836 u8"\U0001cd14", // 𜴔 28 (o345)
837 u8"\U0001cd13", // 𜴓 27 (o1245)
838 u8"\U0001cd12", // 𜴒 26 row 0/1 right row 2 l (o245)
839 u8"\U0001cd11", // 𜴑 25 row 1/2 left row 1 r (o145)
840 u8"\U0001cd10", // 𜴐 24 row 1 right row 2 left (o45)
841 u8"\U0001cd0f", // 𜴏 23 row 0 full row 1/2 l (o1235)
842 u8"\U0001cd0e", // 𜴎 22 row 1 right row 2/3 l (o235)
843 u8"\U0001cd0d", // 𜴍 21 row 0/1/2 left (o135)
844 u8"\U0001fbe6", // 🯦 20 row 1/2 left (middle left one quarter)
845 u8"\U0001cd0c", // 𜴌 19 row 0 full row 2 left (o125)
846 u8"\U0001cd0b", // 𜴋 18 row 0 right row 2 left (o25)
847 u8"\U0001cd0a", // 𜴊 17 row 0 left row 2 left (o15)
848 u8"\U0001cd09", // 𜴉 16 row 2 left only (o5)
849 u8"\U00002580", // ▀ 15 row 0/1 full (upper half)
850 u8"\U0001cd08", // 𜴈 14 row 0 right row 1 full (o234)
851 u8"\U0001cd07", // 𜴇 13 row 0 left row 1 full (o134)
852 u8"\U0001cd06", // 𜴆 12 row 1 full (o34)
853 u8"\U0001cd05", // 𜴅 11 row 0 full row 1 right (o124)
854 u8"\U0000259d", // ▝ 10 row 0/1 right only (upper right quadrant)
855 u8"\U0001cd04", // 𜴄 9 row 0 left row 1 right (o14)
856 u8"\U0001cd03", // 𜴃 8 row 1 right only (o4)
857 u8"\U0001cd02", // 𜴂 7 row 0 full row 1 left (o123)
858 u8"\U0001cd01", // 𜴁 6 row 0 right row 1 left (o23)
859 u8"\U00002598", // ▘ 5 row 0/1 left only (upper left quadrant)
860 u8"\U0001cd00", // 𜴀 4 row 1 left only (o3)
861 u8"\U0001fb82", // 🮂 3 row 0 (upper one quarter)
862 u8"\U0001ceab", // 𜺫 2 upper right only (right half upper one quarter)
863 u8"\U0001cea8", // 𜺨 1 upper left only (left half upper one quarter)
864 u8" " // 0 none set (space)
865};
866
867static const char*
868hires_trans_check(nccell* c, const uint32_t* rgbas, unsigned blendcolors,
869 uint32_t transcolor, unsigned nointerpolate, int cellheight,
870 const char* const* transegcs){
871 unsigned transstring = 0;
872 unsigned r = 0, g = 0, b = 0;
873 unsigned div = 0;
874 // check each pixel for transparency
875 for(int mask = 0 ; mask < cellheight * 2 ; ++mask){
876 if(rgba_trans_p(rgbas[mask], transcolor)){
877 transstring |= (1u << mask);
878 }else if(!nointerpolate || !div){ // force an initialization if nointerpolate
879 r += ncpixel_r(rgbas[mask]);
880 g += ncpixel_g(rgbas[mask]);
881 b += ncpixel_b(rgbas[mask]);
882 ++div;
883 }
884 }
885 if(transstring == 0){ // there was no transparency
886 return NULL;
887 }
888 nccell_set_bg_alpha(c, NCALPHA_TRANSPARENT);
889 // there were some transparent pixels. since they get priority, the foreground
890 // is just a general lerp across non-transparent pixels. transstring can only
891 // have 0x80 and/or 0x40 set if cellheight was 4.
892 const char* egc = transegcs[transstring];
893 nccell_set_bg_alpha(c, NCALPHA_TRANSPARENT);
894//fprintf(stderr, "transtring: %u egc: %s\n", transtring, egc);
895 if(*egc == ' '){ // entirely transparent
896 nccell_set_fg_alpha(c, NCALPHA_TRANSPARENT);
897 return "";
898 }else{ // partially transparent, thus div >= 1
899//fprintf(stderr, "div: %u r: %u g: %u b: %u\n", div, r, g, b);
900 cell_set_fchannel(c, generalerp(r, g, b, div));
901 if(blendcolors){
902 nccell_set_fg_alpha(c, NCALPHA_BLEND);
903 }
904 if(cellheight == 3){
905 cell_set_blitquadrants(c, !(transstring & 0x5), !(transstring & 0xa),
906 !(transstring & 0x14), !(transstring & 0x28));
907 }else{
908 cell_set_blitquadrants(c, !(transstring & 0x5), !(transstring & 0xa),
909 !(transstring & 0x50), !(transstring & 0xa0));
910 }
911 }
912//fprintf(stderr, "SEX-BQ: 0x%x\n", cell_blittedquadrants(c));
913 return egc;
914}
915
916// sextant/octant blitter. maps 3x2 or 4x2 to each cell. since we only have two
917// colors at our disposal (foreground and background), we generally lose some
918// color fidelity.
919static inline int
920hires_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
921 const blitterargs* bargs, int cellheight,
922 const char* const* transegcs, const unsigned* partitions){
923 const unsigned nointerpolate = bargs->flags & NCVISUAL_OPTION_NOINTERPOLATE;
924 const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND;
925 unsigned dimy, dimx, x, y;
926 int total = 0; // number of cells written
927 ncplane_dim_yx(nc, &dimy, &dimx);
928//fprintf(stderr, "hiresblitter %dx%d -> %d/%d+%d/%d\n", leny, lenx, dimy, dimx, bargs->u.cell.placey, bargs->u.cell.placex);
929 const unsigned char* dat = data;
930 int visy = bargs->begy;
931 assert(cellheight <= 4); // due to rgbas[] array below
932 for(y = bargs->u.cell.placey ; visy < (bargs->begy + leny) && y < dimy ; ++y, visy += cellheight){
933 if(ncplane_cursor_move_yx(nc, y, bargs->u.cell.placex < 0 ? 0 : bargs->u.cell.placex)){
934 return -1;
935 }
936 int visx = bargs->begx;
937 for(x = bargs->u.cell.placex ; visx < (bargs->begx + lenx) && x < dimx ; ++x, visx += 2){
938 uint32_t rgbas[8]; // row-major
939 memset(rgbas, 0, sizeof(rgbas));
940 memcpy(&rgbas[0], (dat + (linesize * visy) + (visx * 4)), sizeof(*rgbas));
941 // conditional looks at first column, begininng at the second row
942 for(int yoff = 1 ; yoff < cellheight ; ++yoff){
943 if(visy < bargs->begy + leny - yoff){
944 memcpy(&rgbas[yoff * 2], (dat + (linesize * (visy + yoff)) + (visx * 4)), sizeof(*rgbas));
945 }
946 }
947 // conditional looks at second column, beginning at second row
948 if(visx < bargs->begx + lenx - 1){
949 memcpy(&rgbas[1], (dat + (linesize * visy) + ((visx + 1) * 4)), sizeof(*rgbas));
950 for(int yoff = 1 ; yoff < cellheight ; ++yoff){
951 if(visy < bargs->begy + leny - yoff){
952 memcpy(&rgbas[1 + yoff * 2], (dat + (linesize * (visy + yoff)) + ((visx + 1) * 4)), sizeof(*rgbas));
953 }
954 }
955 }
956 nccell* c = ncplane_cell_ref_yx(nc, y, x);
957 c->channels = 0;
958 c->stylemask = 0;
959 const char* egc = hires_trans_check(c, rgbas, blendcolors, bargs->transcolor,
960 nointerpolate, cellheight, transegcs);
961 if(egc == NULL){ // no transparency; run a full solver
962 egc = hires_solver(rgbas, &c->channels, blendcolors, nointerpolate,
963 cellheight, transegcs, partitions);
964 cell_set_blitquadrants(c, 1, 1, 1, 1);
965 }
966//fprintf(stderr, "hires EGC: %s channels: %016lx\n", egc, c->channels);
967 if(*egc){
968 if(pool_blit_direct(&nc->pool, c, egc, strlen(egc), 1) <= 0){
969 return -1;
970 }
971 ++total;
972 }else{
973 nccell_release(nc, c);
974 }
975 }
976 }
977 return total;
978}
979
980static inline int
981sextant_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
982 const blitterargs* bargs){
983 // each element within the set of 64 has an inverse element within the set,
984 // for which we would calculate the same total differences, so just handle
985 // the first 32. the sextition[] bit masks represent combinations of
986 // sextants, and their indices correspond to inverse sextrans[].
987 static const unsigned sextitions[32] = {
988 0, // 1 way to arrange 0
989 1, 2, 4, 8, 16, 32, // 6 ways to arrange 1
990 3, 5, 9, 17, 33, 6, 10, 18, 34, 12, 20, 36, 24, 40, 48, // 15 ways for 2
991 // 16 ways to arrange 3, *but* six of them are inverses, so 10
992 7, 11, 19, 35, 13, 21, 37, 25, 41, 49 // 10 + 15 + 6 + 1 == 32
993 };
994 return hires_blit(nc, linesize, data, leny, lenx, bargs, 3, sextrans, sextitions);
995}
996
997static inline int
998octant_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
999 const blitterargs* bargs){
1000 // each element within the set of 256 has an inverse element within the set,
1001 // for which we would calculate the same total differences, so just handle
1002 // the first 128. the octition[] bit masks represent combinations of
1003 // octants, and their indices correspond to inverse octtrans[].
1004#define E(bits) (1u << (bits))
1005 static const unsigned octitions[128] = {
1006 0,
1007 // one set
1008 E(0), E(1), E(2), E(3), E(4), E(5), E(6), E(7),
1009 // two set (7 + 6 + 5 + 4 + 3 + 2 + 1 = 28)
1010 E(0) + E(1), E(0) + E(2), E(0) + E(3), E(0) + E(4), E(0) + E(5), E(0) + E(6), E(0) + E(7),
1011 E(1) + E(2), E(1) + E(3), E(1) + E(4), E(1) + E(5), E(1) + E(6), E(1) + E(7),
1012 E(2) + E(3), E(2) + E(4), E(2) + E(5), E(2) + E(6), E(2) + E(7),
1013 E(3) + E(4), E(3) + E(5), E(3) + E(6), E(3) + E(7),
1014 E(4) + E(5), E(4) + E(6), E(4) + E(7),
1015 E(5) + E(6), E(5) + E(7),
1016 E(6) + E(7),
1017 // three set (21 + 15 + 10 + 6 + 3 + 1 = 56)
1018 E(0) + E(1) + E(2), E(0) + E(1) + E(3), E(0) + E(1) + E(4), E(0) + E(1) + E(5),
1019 E(0) + E(1) + E(6), E(0) + E(1) + E(7), E(0) + E(2) + E(3), E(0) + E(2) + E(4),
1020 E(0) + E(2) + E(5), E(0) + E(2) + E(6), E(0) + E(2) + E(7), E(0) + E(3) + E(4),
1021 E(0) + E(3) + E(5), E(0) + E(3) + E(6), E(0) + E(3) + E(7), E(0) + E(4) + E(5),
1022 E(0) + E(4) + E(6), E(0) + E(4) + E(7), E(0) + E(5) + E(6), E(0) + E(5) + E(7),
1023 E(0) + E(6) + E(7), // 0 + 6 + 7
1024 E(1) + E(2) + E(3), E(1) + E(2) + E(4), E(1) + E(2) + E(5), E(1) + E(2) + E(6),
1025 E(1) + E(2) + E(7), E(1) + E(3) + E(4), E(1) + E(3) + E(5), E(1) + E(3) + E(6),
1026 E(1) + E(3) + E(7), E(1) + E(4) + E(5), E(1) + E(4) + E(6), E(1) + E(4) + E(7),
1027 E(1) + E(5) + E(6), E(1) + E(5) + E(7), E(1) + E(6) + E(7),
1028 E(2) + E(3) + E(4), E(2) + E(3) + E(5), E(2) + E(3) + E(6), E(2) + E(3) + E(7),
1029 E(2) + E(4) + E(5), E(2) + E(4) + E(6), E(2) + E(4) + E(7), E(2) + E(5) + E(6),
1030 E(2) + E(5) + E(7), E(2) + E(6) + E(7),
1031 E(3) + E(4) + E(5), E(3) + E(4) + E(6), E(3) + E(4) + E(7), E(3) + E(5) + E(6),
1032 E(3) + E(5) + E(7), E(3) + E(6) + E(7),
1033 E(4) + E(5) + E(6), E(4) + E(5) + E(7), E(4) + E(6) + E(7),
1034 E(5) + E(6) + E(7),
1035 // four set (15 + 10 + 6 + 3 + 1 = 35)
1036 E(0) + E(1) + E(2) + E(3), E(0) + E(1) + E(2) + E(4), E(0) + E(1) + E(2) + E(5),
1037 E(0) + E(1) + E(2) + E(6), E(0) + E(1) + E(2) + E(7), E(0) + E(1) + E(3) + E(4),
1038 E(0) + E(1) + E(3) + E(5), E(0) + E(1) + E(3) + E(6), E(0) + E(1) + E(3) + E(7),
1039 E(0) + E(1) + E(4) + E(5), E(0) + E(1) + E(4) + E(6), E(0) + E(1) + E(4) + E(7),
1040 E(0) + E(1) + E(5) + E(6), E(0) + E(1) + E(5) + E(7), E(0) + E(1) + E(6) + E(7),
1041 E(0) + E(2) + E(3) + E(4), E(0) + E(2) + E(3) + E(5), E(0) + E(2) + E(3) + E(6),
1042 E(0) + E(2) + E(3) + E(7), E(0) + E(2) + E(4) + E(5), E(0) + E(2) + E(4) + E(6),
1043 E(0) + E(2) + E(4) + E(7), E(0) + E(2) + E(5) + E(6), E(0) + E(2) + E(5) + E(7),
1044 E(0) + E(2) + E(6) + E(7), E(0) + E(3) + E(4) + E(5), E(0) + E(3) + E(4) + E(6),
1045 E(0) + E(3) + E(4) + E(7), E(0) + E(3) + E(5) + E(6), E(0) + E(3) + E(5) + E(7),
1046 E(0) + E(3) + E(6) + E(7), E(0) + E(4) + E(5) + E(6), E(0) + E(4) + E(5) + E(7),
1047 E(0) + E(4) + E(6) + E(7), E(0) + E(5) + E(6) + E(7),
1048#undef E
1049 };
1050 return hires_blit(nc, linesize, data, leny, lenx, bargs, 4, octtrans, octitions);
1051}
1052
1053// Bit is set where Braille dot is present:
1054// 0 1
1055// 2 3
1056// 4 5
1057// 6 7
1058// Similar to NCBRAILLEEGCS, but in a different order since we number the bits differently
1059static const char braille_egcs[256][5] = {
1060 "\u2800", "\u2801", "\u2808", "\u2809", "\u2802", "\u2803", "\u280a", "\u280b",
1061 "\u2810", "\u2811", "\u2818", "\u2819", "\u2812", "\u2813", "\u281a", "\u281b",
1062 "\u2804", "\u2805", "\u280c", "\u280d", "\u2806", "\u2807", "\u280e", "\u280f",
1063 "\u2814", "\u2815", "\u281c", "\u281d", "\u2816", "\u2817", "\u281e", "\u281f",
1064 "\u2820", "\u2821", "\u2828", "\u2829", "\u2822", "\u2823", "\u282a", "\u282b",
1065 "\u2830", "\u2831", "\u2838", "\u2839", "\u2832", "\u2833", "\u283a", "\u283b",
1066 "\u2824", "\u2825", "\u282c", "\u282d", "\u2826", "\u2827", "\u282e", "\u282f",
1067 "\u2834", "\u2835", "\u283c", "\u283d", "\u2836", "\u2837", "\u283e", "\u283f",
1068 "\u2840", "\u2841", "\u2848", "\u2849", "\u2842", "\u2843", "\u284a", "\u284b",
1069 "\u2850", "\u2851", "\u2858", "\u2859", "\u2852", "\u2853", "\u285a", "\u285b",
1070 "\u2844", "\u2845", "\u284c", "\u284d", "\u2846", "\u2847", "\u284e", "\u284f",
1071 "\u2854", "\u2855", "\u285c", "\u285d", "\u2856", "\u2857", "\u285e", "\u285f",
1072 "\u2860", "\u2861", "\u2868", "\u2869", "\u2862", "\u2863", "\u286a", "\u286b",
1073 "\u2870", "\u2871", "\u2878", "\u2879", "\u2872", "\u2873", "\u287a", "\u287b",
1074 "\u2864", "\u2865", "\u286c", "\u286d", "\u2866", "\u2867", "\u286e", "\u286f",
1075 "\u2874", "\u2875", "\u287c", "\u287d", "\u2876", "\u2877", "\u287e", "\u287f",
1076 "\u2880", "\u2881", "\u2888", "\u2889", "\u2882", "\u2883", "\u288a", "\u288b",
1077 "\u2890", "\u2891", "\u2898", "\u2899", "\u2892", "\u2893", "\u289a", "\u289b",
1078 "\u2884", "\u2885", "\u288c", "\u288d", "\u2886", "\u2887", "\u288e", "\u288f",
1079 "\u2894", "\u2895", "\u289c", "\u289d", "\u2896", "\u2897", "\u289e", "\u289f",
1080 "\u28a0", "\u28a1", "\u28a8", "\u28a9", "\u28a2", "\u28a3", "\u28aa", "\u28ab",
1081 "\u28b0", "\u28b1", "\u28b8", "\u28b9", "\u28b2", "\u28b3", "\u28ba", "\u28bb",
1082 "\u28a4", "\u28a5", "\u28ac", "\u28ad", "\u28a6", "\u28a7", "\u28ae", "\u28af",
1083 "\u28b4", "\u28b5", "\u28bc", "\u28bd", "\u28b6", "\u28b7", "\u28be", "\u28bf",
1084 "\u28c0", "\u28c1", "\u28c8", "\u28c9", "\u28c2", "\u28c3", "\u28ca", "\u28cb",
1085 "\u28d0", "\u28d1", "\u28d8", "\u28d9", "\u28d2", "\u28d3", "\u28da", "\u28db",
1086 "\u28c4", "\u28c5", "\u28cc", "\u28cd", "\u28c6", "\u28c7", "\u28ce", "\u28cf",
1087 "\u28d4", "\u28d5", "\u28dc", "\u28dd", "\u28d6", "\u28d7", "\u28de", "\u28df",
1088 "\u28e0", "\u28e1", "\u28e8", "\u28e9", "\u28e2", "\u28e3", "\u28ea", "\u28eb",
1089 "\u28f0", "\u28f1", "\u28f8", "\u28f9", "\u28f2", "\u28f3", "\u28fa", "\u28fb",
1090 "\u28e4", "\u28e5", "\u28ec", "\u28ed", "\u28e6", "\u28e7", "\u28ee", "\u28ef",
1091 "\u28f4", "\u28f5", "\u28fc", "\u28fd", "\u28f6", "\u28f7", "\u28fe", "\u28ff",
1092};
1093
1094// fold the r, g, and b components of the pixel into *r, *g, and *b, and
1095// increment *foldcount
1096static inline void
1097fold_rgb8(unsigned* restrict r, unsigned* restrict g, unsigned* restrict b,
1098 const uint32_t* pixel, unsigned* foldcount){
1099 *r += ncpixel_r(*pixel);
1100 *g += ncpixel_g(*pixel);
1101 *b += ncpixel_b(*pixel);
1102 ++*foldcount;
1103}
1104
1105// Braille maps 4x2 to each cell, always using a transparent background.
1106static inline int
1107braille_blit(ncplane* nc, int linesize, const void* data, int leny, int lenx,
1108 const blitterargs* bargs){
1109 const bool blendcolors = bargs->flags & NCVISUAL_OPTION_BLEND;
1110 unsigned dimy, dimx, x, y;
1111 int total = 0; // number of cells written
1112 ncplane_dim_yx(nc, &dimy, &dimx);
1113 // FIXME not going to necessarily be safe on all architectures hrmmm
1114 const unsigned char* dat = data;
1115 int visy = bargs->begy;
1116 for(y = bargs->u.cell.placey ; visy < (bargs->begy + leny) && y < dimy ; ++y, visy += 4){
1117 if(ncplane_cursor_move_yx(nc, y, bargs->u.cell.placex < 0 ? 0 : bargs->u.cell.placex)){
1118 return -1;
1119 }
1120 int visx = bargs->begx;
1121 for(x = bargs->u.cell.placex ; visx < (bargs->begx + lenx) && x < dimx ; ++x, visx += 2){
1122 const uint32_t* rgbbase_l0 = (const uint32_t*)(dat + (linesize * visy) + (visx * 4));
1123 const uint32_t* rgbbase_r0 = &zeroes32;
1124 const uint32_t* rgbbase_l1 = &zeroes32;
1125 const uint32_t* rgbbase_r1 = &zeroes32;
1126 const uint32_t* rgbbase_l2 = &zeroes32;
1127 const uint32_t* rgbbase_r2 = &zeroes32;
1128 const uint32_t* rgbbase_l3 = &zeroes32;
1129 const uint32_t* rgbbase_r3 = &zeroes32;
1130 unsigned r = 0, g = 0, b = 0;
1131 unsigned blends = 0;
1132 unsigned egcidx = 0;
1133 if(visx < bargs->begx + lenx - 1){
1134 rgbbase_r0 = (const uint32_t*)(dat + (linesize * visy) + ((visx + 1) * 4));
1135 if(visy < bargs->begy + leny - 1){
1136 rgbbase_r1 = (const uint32_t*)(dat + (linesize * (visy + 1)) + ((visx + 1) * 4));
1137 if(visy < bargs->begy + leny - 2){
1138 rgbbase_r2 = (const uint32_t*)(dat + (linesize * (visy + 2)) + ((visx + 1) * 4));
1139 if(visy < bargs->begy + leny - 3){
1140 rgbbase_r3 = (const uint32_t*)(dat + (linesize * (visy + 3)) + ((visx + 1) * 4));
1141 }
1142 }
1143 }
1144 }
1145 if(visy < bargs->begy + leny - 1){
1146 rgbbase_l1 = (const uint32_t*)(dat + (linesize * (visy + 1)) + (visx * 4));
1147 if(visy < bargs->begy + leny - 2){
1148 rgbbase_l2 = (const uint32_t*)(dat + (linesize * (visy + 2)) + (visx * 4));
1149 if(visy < bargs->begy + leny - 3){
1150 rgbbase_l3 = (const uint32_t*)(dat + (linesize * (visy + 3)) + (visx * 4));
1151 }
1152 }
1153 }
1154 // 4x2 block is ordered (where 0 is the LSB)
1155 // 0 1
1156 // 2 3
1157 // 4 5
1158 // 6 7
1159 // FIXME fold this into the above?
1160 if(!rgba_trans_p(*rgbbase_l0, bargs->transcolor)){
1161 egcidx |= 1u;
1162 fold_rgb8(&r, &g, &b, rgbbase_l0, &blends);
1163 }
1164 if(!rgba_trans_p(*rgbbase_r0, bargs->transcolor)){
1165 egcidx |= 2u;
1166 fold_rgb8(&r, &g, &b, rgbbase_r0, &blends);
1167 }
1168 if(!rgba_trans_p(*rgbbase_l1, bargs->transcolor)){
1169 egcidx |= 4u;
1170 fold_rgb8(&r, &g, &b, rgbbase_l1, &blends);
1171 }
1172 if(!rgba_trans_p(*rgbbase_r1, bargs->transcolor)){
1173 egcidx |= 8u;
1174 fold_rgb8(&r, &g, &b, rgbbase_r1, &blends);
1175 }
1176 if(!rgba_trans_p(*rgbbase_l2, bargs->transcolor)){
1177 egcidx |= 16u;
1178 fold_rgb8(&r, &g, &b, rgbbase_l2, &blends);
1179 }
1180 if(!rgba_trans_p(*rgbbase_r2, bargs->transcolor)){
1181 egcidx |= 32u;
1182 fold_rgb8(&r, &g, &b, rgbbase_r2, &blends);
1183 }
1184 if(!rgba_trans_p(*rgbbase_l3, bargs->transcolor)){
1185 egcidx |= 64u;
1186 fold_rgb8(&r, &g, &b, rgbbase_l3, &blends);
1187 }
1188 if(!rgba_trans_p(*rgbbase_r3, bargs->transcolor)){
1189 egcidx |= 128u;
1190 fold_rgb8(&r, &g, &b, rgbbase_r3, &blends);
1191 }
1192//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]);
1193 nccell* c = ncplane_cell_ref_yx(nc, y, x);
1194 // use the default for the background, as that's the only way it's
1195 // effective in that case anyway
1196 c->channels = 0;
1197 c->stylemask = 0;
1198 if(blendcolors){
1199 nccell_set_fg_alpha(c, NCALPHA_BLEND);
1200 }
1201 // FIXME for now, we just sample, color-wise, and always draw crap.
1202 // more complicated to do optimally than quadrants, for sure. ideally,
1203 // we only get one color in an area.
1204 nccell_set_bg_alpha(c, NCALPHA_TRANSPARENT);
1205 if(!egcidx){
1206 nccell_set_fg_alpha(c, NCALPHA_TRANSPARENT);
1207 // FIXME else look for pairs of transparency!
1208 }else{
1209 if(blends){
1210 nccell_set_fg_rgb8(c, r / blends, g / blends, b / blends);
1211 }
1212 const char* egc = braille_egcs[egcidx];
1213 if(pool_blit_direct(&nc->pool, c, egc, strlen(egc), 1) <= 0){
1214 return -1;
1215 }
1216 }
1217 ++total;
1218 }
1219 }
1220 return total;
1221}
1222
1223// NCBLIT_DEFAULT is not included, as it has no defined properties. It ought
1224// be replaced with some real blitter implementation by the calling widget.
1225// The order of contents is critical for 'egcs': ncplane_as_rgba() uses these
1226// arrays to map cells to source pixels. Map the upper-left logical bit to
1227// 1, and increase to the right, followed by down. The first egc ought thus
1228// always be space, to indicate an empty cell (all zeroes). These need be
1229// kept in the same order as the enums!
1230static struct blitset notcurses_blitters[] = {
1231 { .geom = NCBLIT_1x1, .width = 1, .height = 1,
1232 .egcs = L" █", .plotegcs = L" █",
1233 .blit = tria_blit_ascii,.name = "ascii", .fill = false, },
1234 { .geom = NCBLIT_2x1, .width = 1, .height = 2,
1235 .egcs = NCHALFBLOCKS, .plotegcs = L" ▄█",
1236 .blit = tria_blit, .name = "half", .fill = false, },
1237 { .geom = NCBLIT_2x2, .width = 2, .height = 2,
1238 .egcs = NCQUADBLOCKS, .plotegcs = L" ▗▐▖▄▟▌▙█",
1239 .blit = quadrant_blit, .name = "quad", .fill = false, },
1240 { .geom = NCBLIT_3x2, .width = 2, .height = 3,
1241 .egcs = NCSEXBLOCKS, .plotegcs = L" 🬞🬦▐🬏🬭🬵🬷🬓🬱🬹🬻▌🬲🬺█",
1242 .blit = sextant_blit, .name = "sex", .fill = false, },
1243 { .geom = NCBLIT_4x2, .width = 2, .height = 4,
1244 .egcs = NCOCTBLOCKS,
1245 .plotegcs = (L"\0x20"
1246 L"\U0001cea0"
1247 L"\U00002597"
1248 L"\U0001CD96"
1249 L"\U0001CD91"
1250 L"\U0001CEA3"
1251 L"\U00002582"
1252 L"\U0001CDCB"
1253 L"\U0001CDD3"
1254 L"\U0001CDCD"
1255 L"\U00002596"
1256 L"\U0001CDBB"
1257 L"\U00002584"
1258 L"\U0001CDE1"
1259 L"\U0001CDDC"
1260 L"\U0001CD48"
1261 L"\U0001CDBF"
1262 L"\U0001CDDE"
1263 L"\U00002586"
1264 L"\U0001CDDF"
1265 L"\U0000258C"
1266 L"\U0001CDC0"
1267 L"\U00002599"
1268 L"\U0001CDE4"
1269 L"\U0001CDE0"),
1270 .blit = octant_blit, .name = "oct", .fill = false, },
1271 { .geom = NCBLIT_BRAILLE, .width = 2, .height = 4,
1272 .egcs = NCBRAILLEEGCS,
1273 .plotegcs = L"⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿",
1274 .blit = braille_blit, .name = "braille", .fill = true, },
1275 { .geom = NCBLIT_PIXEL, .width = 1, .height = 1,
1276 .egcs = L"", .plotegcs = NULL,
1277 .blit = NULL, .name = "pixel", .fill = true, },
1278 { .geom = NCBLIT_4x1, .width = 1, .height = 4,
1279 .egcs = NULL, .plotegcs = L" ▂▄▆█",
1280 .blit = tria_blit, .name = "fourstep", .fill = false, },
1281 { .geom = NCBLIT_8x1, .width = 1, .height = 8,
1282 .egcs = NULL, .plotegcs = NCEIGHTHSB,
1283 .blit = tria_blit, .name = "eightstep", .fill = false, },
1284 { .geom = 0, .width = 0, .height = 0,
1285 .egcs = NULL, .plotegcs = NULL,
1286 .blit = NULL, .name = NULL, .fill = false, },
1287};
1288
1290 struct blitset* b = notcurses_blitters;
1291 while(b->geom != NCBLIT_PIXEL){
1292 ++b;
1293 }
1294 b->blit = blitfxn;
1295}
1296
1297const struct blitset* lookup_blitset(const tinfo* tcache, ncblitter_e setid,
1298 bool may_degrade){
1299 if(setid == NCBLIT_DEFAULT){ // ought have resolved NCBLIT_DEFAULT before now
1300 return NULL;
1301 }
1302 // without braille support, NCBLIT_BRAILLE decays to NCBLIT_4x2
1303 if(setid == NCBLIT_BRAILLE){
1304 if(tcache->caps.braille){
1305 return &notcurses_blitters[setid - 1];
1306 }else if(!may_degrade){
1307 return NULL;
1308 }
1309 setid = NCBLIT_4x2;
1310 }
1311 // without octant support, NCBLIT_4x2 decays to NCBLIT_3x2
1312 if(setid == NCBLIT_4x2){
1313 if(tcache->caps.octants){
1314 return &notcurses_blitters[setid - 1];
1315 }else if(!may_degrade){
1316 return NULL;
1317 }
1318 setid = NCBLIT_3x2;
1319 }
1320 // without bitmap support, NCBLIT_PIXEL decays to NCBLIT_3x2
1321 if(setid == NCBLIT_PIXEL){
1322 if(tcache->pixel_draw || tcache->pixel_draw_late){
1323 return &notcurses_blitters[setid - 1];
1324 }else if(!may_degrade){
1325 return NULL;
1326 }
1327 setid = NCBLIT_3x2;
1328 }
1329 // without eighths support, NCBLIT_8x1 decays to NCBLIT_4x1
1330 if(setid == NCBLIT_8x1){ // plotter only
1331 if(tcache->caps.quadrants){
1332 return &notcurses_blitters[setid - 1];
1333 }else if(!may_degrade){
1334 return NULL;
1335 }
1336 setid = NCBLIT_4x1;
1337 }
1338 // without quarters support, NCBLIT_4x1 decays to NCBLIT_2x1
1339 if(setid == NCBLIT_4x1){ // plotter only
1340 if(tcache->caps.quadrants){
1341 return &notcurses_blitters[setid - 1];
1342 }else if(!may_degrade){
1343 return NULL;
1344 }
1345 setid = NCBLIT_2x1;
1346 }
1347 // without sextant support, NCBLIT_3x2 decays to NCBLIT_2x2
1348 if(setid == NCBLIT_3x2){
1349 if(tcache->caps.sextants){
1350 return &notcurses_blitters[setid - 1];
1351 }else if(!may_degrade){
1352 return NULL;
1353 }
1354 setid = NCBLIT_2x2;
1355 }
1356 // without quadrant support, NCBLIT_2x2 decays to NCBLIT_2x1
1357 if(setid == NCBLIT_2x2){
1358 if(tcache->caps.quadrants){
1359 return &notcurses_blitters[setid - 1];
1360 }else if(!may_degrade){
1361 return NULL;
1362 }
1363 setid = NCBLIT_2x1;
1364 }
1365 // without halfblock support, NCBLIT_2x1 decays to NCBLIT_1x1
1366 if(setid == NCBLIT_2x1){
1367 if(tcache->caps.halfblocks){
1368 return &notcurses_blitters[setid - 1];
1369 }else if(!may_degrade){
1370 return NULL;
1371 }
1372 setid = NCBLIT_1x1;
1373 }
1374 assert(NCBLIT_1x1 == setid);
1375 return &notcurses_blitters[setid - 1];
1376}
1377
1378int notcurses_lex_blitter(const char* op, ncblitter_e* blitfxn){
1379 const struct blitset* bset = notcurses_blitters;
1380 while(bset->name){
1381 if(strcasecmp(bset->name, op) == 0){
1382 *blitfxn = bset->geom;
1383 return 0;
1384 }
1385 ++bset;
1386 }
1387 if(strcasecmp("default", op) == 0){
1388 *blitfxn = NCBLIT_DEFAULT;
1389 return 0;
1390 }
1391 return -1;
1392}
1393
1395 if(blitfxn == NCBLIT_DEFAULT){
1396 return "default";
1397 }
1398 const struct blitset* bset = notcurses_blitters;
1399 while(bset->name){
1400 if(bset->geom == blitfxn){
1401 return bset->name;
1402 }
1403 ++bset;
1404 }
1405 return NULL;
1406}
1407
1408int ncblit_bgrx(const void* data, int linesize, const struct ncvisual_options* vopts){
1409 if(vopts->leny <= 0 || vopts->lenx <= 0){
1410 logerror("invalid lengths %u %u", vopts->leny, vopts->lenx);
1411 return -1;
1412 }
1413 if(vopts->n == NULL){
1414 logerror("prohibited null plane");
1415 return -1;
1416 }
1417 void* rdata = bgra_to_rgba(data, vopts->leny, &linesize, vopts->lenx, 0xff);
1418 if(rdata == NULL){
1419 return -1;
1420 }
1421 int r = ncblit_rgba(rdata, linesize, vopts);
1422 free(rdata);
1423 return r;
1424}
1425
1426int ncblit_rgb_loose(const void* data, int linesize,
1427 const struct ncvisual_options* vopts, int alpha){
1428 if(vopts->leny <= 0 || vopts->lenx <= 0){
1429 return -1;
1430 }
1431 void* rdata = rgb_loose_to_rgba(data, vopts->leny, &linesize, vopts->lenx, alpha);
1432 if(rdata == NULL){
1433 return -1;
1434 }
1435 int r = ncblit_rgba(rdata, linesize, vopts);
1436 free(rdata);
1437 return r;
1438}
1439
1440int ncblit_rgb_packed(const void* data, int linesize,
1441 const struct ncvisual_options* vopts, int alpha){
1442 if(vopts->leny <= 0 || vopts->lenx <= 0){
1443 return -1;
1444 }
1445 void* rdata = rgb_packed_to_rgba(data, vopts->leny, &linesize, vopts->lenx, alpha);
1446 if(rdata == NULL){
1447 return -1;
1448 }
1449 int r = ncblit_rgba(rdata, linesize, vopts);
1450 free(rdata);
1451 return r;
1452}
1453
1454int ncblit_rgba(const void* data, int linesize, const struct ncvisual_options* vopts){
1455 if(vopts->leny <= 0 || vopts->lenx <= 0){
1456 logerror("invalid lengths %u %u", vopts->leny, vopts->lenx);
1457 return -1;
1458 }
1459 if(vopts->n == NULL){
1460 logerror("prohibited null plane");
1461 return -1;
1462 }
1463 struct ncvisual* ncv = ncvisual_from_rgba(data, vopts->leny, linesize, vopts->lenx);
1464 if(ncv == NULL){
1465 return -1;
1466 }
1469 return -1;
1470 }
1472 return 0;
1473}
1474
1476 return rgba_blitter_default(&nc->tcache, scale);
1477}
int ncblit_rgba(const void *data, int linesize, const struct ncvisual_options *vopts)
Definition blit.c:1454
ncblitter_e ncvisual_media_defblitter(const notcurses *nc, ncscale_e scale)
Definition blit.c:1475
int ncblit_rgb_loose(const void *data, int linesize, const struct ncvisual_options *vopts, int alpha)
Definition blit.c:1426
const char * notcurses_str_blitter(ncblitter_e blitfxn)
Definition blit.c:1394
int notcurses_lex_blitter(const char *op, ncblitter_e *blitfxn)
Definition blit.c:1378
#define E(bits)
const struct blitset * lookup_blitset(const tinfo *tcache, ncblitter_e setid, bool may_degrade)
Definition blit.c:1297
int ncblit_bgrx(const void *data, int linesize, const struct ncvisual_options *vopts)
Definition blit.c:1408
int ncblit_rgb_packed(const void *data, int linesize, const struct ncvisual_options *vopts, int alpha)
Definition blit.c:1440
void set_pixel_blitter(ncblitter blitfxn)
Definition blit.c:1289
int(* ncblitter)(struct ncplane *n, int linesize, const void *data, int scaledy, int scaledx, const struct blitterargs *bargs)
Definition blit.h:14
const char * egc
Definition egcpool.h:162
const nccell * c
Definition egcpool.h:207
uint32_t idx
Definition egcpool.h:209
int r
Definition fbuf.h:226
assert(r >=0)
ALLOC void * rgb_loose_to_rgba(const void *data, int rows, int *rowstride, int cols, int alpha)
Definition visual.c:461
ALLOC void * bgra_to_rgba(const void *data, int rows, int *rowstride, int cols, int alpha)
Definition visual.c:508
ALLOC void * rgb_packed_to_rgba(const void *data, int rows, int *rowstride, int cols, int alpha)
Definition visual.c:486
#define logerror(fmt,...)
Definition logging.h:32
#define NCOCTBLOCKS
Definition ncseqs.h:59
#define NCBRAILLEEGCS
Definition ncseqs.h:92
#define NCSEXBLOCKS
Definition ncseqs.h:58
#define NCHALFBLOCKS
Definition ncseqs.h:56
#define NCQUADBLOCKS
Definition ncseqs.h:57
#define NCEIGHTHSB
Definition ncseqs.h:52
int ncplane_cursor_move_yx(ncplane *n, int y, int x)
Definition notcurses.c:723
notcurses * ncplane_notcurses(const ncplane *n)
Definition notcurses.c:2631
void ncplane_dim_yx(const ncplane *n, unsigned *rows, unsigned *cols)
Definition notcurses.c:304
ncscale_e
Definition notcurses.h:96
int y
Definition notcurses.h:1905
const struct ncplane_options struct ncvisual * ncv
Definition notcurses.h:3488
#define NCCHANNEL_INITIALIZER(r, g, b)
Definition notcurses.h:128
const struct ncplane_options struct ncvisual struct ncvisual_options * vopts
Definition notcurses.h:3488
ncblitter_e
Definition notcurses.h:65
@ NCBLIT_PIXEL
Definition notcurses.h:73
@ NCBLIT_4x2
Definition notcurses.h:71
@ NCBLIT_DEFAULT
Definition notcurses.h:66
@ NCBLIT_4x1
Definition notcurses.h:75
@ NCBLIT_2x2
Definition notcurses.h:69
@ NCBLIT_3x2
Definition notcurses.h:70
@ NCBLIT_1x1
Definition notcurses.h:67
@ NCBLIT_BRAILLE
Definition notcurses.h:72
@ NCBLIT_2x1
Definition notcurses.h:68
@ NCBLIT_8x1
Definition notcurses.h:76
#define NCVISUAL_OPTION_NOINTERPOLATE
Definition notcurses.h:3347
#define NCALPHA_TRANSPARENT
Definition notcurses.h:106
#define NCALPHA_BLEND
Definition notcurses.h:107
int int x
Definition notcurses.h:1905
#define NCVISUAL_OPTION_BLEND
Definition notcurses.h:3342
void nccell_release(ncplane *n, nccell *c)
Definition render.c:128
ncblitter_e geom
Definition internal.h:399
ncblitter blit
Definition internal.h:410
bool fill
Definition internal.h:412
const char * name
Definition internal.h:411
struct blitterargs::@3::@4 cell
uint64_t flags
Definition internal.h:379
union blitterargs::@3 u
uint32_t transcolor
Definition internal.h:380
uint64_t channels
Definition notcurses.h:723
uint16_t stylemask
Definition notcurses.h:703
egcpool pool
Definition internal.h:87
uint32_t * data
tinfo tcache
Definition internal.h:360
int(* pixel_draw_late)(const struct tinfo *, struct sprixel *s, int yoff, int xoff)
Definition termdesc.h:149
nccapabilities caps
Definition termdesc.h:111
int(* pixel_draw)(const struct tinfo *, const struct ncpile *p, struct sprixel *s, fbuf *f, int y, int x)
Definition termdesc.h:147
return NULL
Definition termdesc.h:229
ncvisual * ncvisual_from_rgba(const void *rgba, int rows, int rowstride, int cols)
Definition visual.c:779
void ncvisual_destroy(ncvisual *ncv)
Definition visual.c:1231
ncplane * ncvisual_blit(notcurses *nc, ncvisual *ncv, const struct ncvisual_options *vopts)
Definition visual.c:1142