Notcurses 3.0.13
a blingful library for TUIs and character graphics
Loading...
Searching...
No Matches
sixel.c
Go to the documentation of this file.
1#include <math.h>
2#include <stdatomic.h>
3#include "internal.h"
4#include "fbuf.h"
5
6#define RGBSIZE 3
7
8// number of worker threads
9// FIXME fit to local machine, but more than 3 never seems to help
10#define POPULATION 3
11
12// a worker can have up to three qstates enqueued for work
13#define WORKERDEPTH 3
14
15// this palette entry is a sentinel for a transparent pixel (and thus caps
16// the palette at 65535 other entries).
17#define TRANS_PALETTE_ENTRY 65535
18
19// bytes per element in the auxiliary vector
20#define AUXVECELEMSIZE 2
21
22// three scaled sixel [0..100x3] components plus a population count.
23typedef struct qsample {
24 unsigned char comps[RGBSIZE];
25 uint32_t pop;
27
28// lowest samples for each node. first-order nodes track 1000 points in
29// sixelspace (10x10x10). there are eight possible second-order nodes from a
30// fractured first-order node, covering 125 points each (5x5x5).
31typedef struct qnode {
33 // cidx plays two roles. during merge, we select the active set, and extract
34 // them (since they'll be sorted, we can't operate directly on the octree).
35 // here, we use cidx to map back to the initial octree entry, as we need
36 // update them (from the active set) at the end of merging. afterwards, the
37 // high bit indicates that it was chosen, and the cidx is a valid index into
38 // the final color table. it is otherwise a link to the merged qnode.
39 // during initial filtering, qlink determines whether a node has fractured:
40 // if qlink is non-zero, it is a one-biased index to an onode.
41 // FIXME combine these once more, but for now to keep it easy, we have two.
42 // qlink links back into the octree.
43 uint16_t qlink;
44 uint16_t cidx;
46
47// an octree-style node, used for fractured first-order nodes. the first
48// bit is whether we're on the top or bottom of the R, then G, then B.
49typedef struct onode {
50 qnode* q[8];
52
53// we set P2 based on whether there is any transparency in the sixel. if not,
54// use SIXEL_P2_ALLOPAQUE (0), for faster drawing in certain terminals.
59
60// data for a single sixelband. a vector of sixel rows, one for each color
61// represented within the band. we initially create a vector for every
62// possible (quantized) color, and then collapse it.
63typedef struct sixelband {
64 int size; // capacity FIXME if same for all, eliminate this
65 char** vecs; // array of vectors, many of which can be NULL
67
68// across the life of the sixel, we'll need to wipe and restore cells, without
69// recourse to the original RGBA data. this is prohibitively expensive to do on
70// the encoded data, since it might require expanding or collapsing sections in
71// the middle (we could use a rope, but it would be annoying). instead, we keep
72// for each sixelrow (i.e. for every 6 rows) a vector of colors and distinct
73// encoded sections (i.e. *not* from some common long single allocation). this
74// way, the encoded sections can be easily and cheaply changed (since they're
75// small, and quickly indexed by sixelrow * color)). whenever we want to emit
76// the sixel, we just gather all these dynamic sections and write them
77// successively into the fbuf. this table can be built up in parallel, since
78// it's isolated among sixelrows -- the sixelrow is then the natural work unit.
79// this sixelmap is kept across the life of the sprixel; any longlived state
80// must be here, whereas state necessary only for rendering ought be in qstate.
81typedef struct sixelmap {
82 int colors;
84 sixelband* bands; // |sixelbands| collections of sixel vectors
85 sixel_p2_e p2; // set to SIXEL_P2_TRANS if we have transparent pixels
87
88typedef struct qstate {
89 int refcount; // initialized to worker count
90 atomic_int bandbuilder; // threads take bands as their work unit
91 // we always work in terms of quantized colors (quantization is the first
92 // step of rendering), using indexes into the derived palette. the actual
93 // palette need only be stored during the initial render, since the sixel
94 // header can be preserved, and the palette is unchanged by wipes/restores.
95 unsigned char* table; // |colors| x RGBSIZE components
98 unsigned dynnodes_free;
100 unsigned onodes_free;
101 unsigned onodes_total;
102 const struct blitterargs* bargs;
103 const uint32_t* data;
106 // these are the leny and lenx passed to sixel_blit(), which are likely
107 // different from those reachable through bargs->len{y,x}!
108 int leny, lenx;
110
111// a work_queue per worker thread. if used == WORKERDEPTH, this thread is
112// backed up, and we cannot enqueue to it. writeto wraps around the array.
119
120// we keep a few worker threads (POPULATION) spun up to assist with
121// quantization. each has an array of up to WORKERDEPTH qstates to work on.
122typedef struct sixel_engine {
123 pthread_mutex_t lock;
124 pthread_cond_t cond;
126 pthread_t tids[POPULATION];
127 bool done;
129
130// enqueue |qs| to any workers with available space. the number of workers with
131// a reference will be stored in |qs|->refcount.
132static void
133enqueue_to_workers(sixel_engine* eng, qstate* qs){
134 if(eng == NULL){
135 return;
136 }
137 int usecount = 0;
138 pthread_mutex_lock(&eng->lock);
139 for(int i = 0 ; i < POPULATION ; ++i){
140 work_queue* wq = &eng->queues[i];
141 if(wq->used < WORKERDEPTH){
142 wq->qstates[wq->writeto] = qs;
143 ++wq->used;
144 ++usecount;
145 if(++wq->writeto == WORKERDEPTH){
146 wq->writeto = 0;
147 }
148 }
149 }
150 qs->refcount = usecount;
151 pthread_mutex_unlock(&eng->lock);
152 if(usecount){
153 pthread_cond_broadcast(&eng->cond);
154 }
155}
156
157// block until all workers have finished up with |qs|
158static void
159block_on_workers(sixel_engine* eng, qstate* qs){
160 if(eng == NULL){
161 return;
162 }
163 pthread_mutex_lock(&eng->lock);
164 while(qs->refcount){
165 pthread_cond_wait(&eng->cond, &eng->lock);
166 }
167 pthread_mutex_unlock(&eng->lock);
168}
169
170// returns the number of individual sixels necessary to represent the specified
171// pixel geometry. these might encompass more pixel rows than |dimy| would
172// suggest, up to the next multiple of 6 (i.e. a single row becomes a 6-row
173// bitmap; as do two, three, four, five, or six rows). input is scaled geometry.
174static inline int
175sixelcount(int dimy, int dimx){
176 return (dimy + 5) / 6 * dimx;
177}
178
179// returns the number of sixel bands (horizontal series of sixels, aka 6 rows)
180// for |dimy| source rows. sixels are encoded as a series of sixel bands.
181static inline int
182sixelbandcount(int dimy){
183 return sixelcount(dimy, 1);
184}
185
186// whip up a sixelmap sans data for the specified pixel geometry and color
187// register count.
188static sixelmap*
189sixelmap_create(int dimy){
190 sixelmap* ret = malloc(sizeof(*ret));
191 if(ret){
192 ret->p2 = SIXEL_P2_ALLOPAQUE;
193 // they'll be initialized by their workers, possibly in parallel
194 ret->sixelbands = sixelbandcount(dimy);
195 ret->bands = malloc(sizeof(*ret->bands) * ret->sixelbands);
196 if(ret->bands == NULL){
197 free(ret);
198 return NULL;
199 }
200 for(int i = 0 ; i < ret->sixelbands ; ++i){
201 ret->bands[i].size = 0;
202 }
203 ret->colors = 0;
204 }
205 return ret;
206}
207
208static inline void
209sixelband_free(sixelband* s){
210 for(int j = 0 ; j < s->size ; ++j){
211 free(s->vecs[j]);
212 }
213 free(s->vecs);
214}
215
217 if(s){
218 for(int i = 0 ; i < s->sixelbands ; ++i){
219 sixelband_free(&s->bands[i]);
220 }
221 free(s->bands);
222 free(s);
223 }
224}
225
226// convert rgb [0..255] to sixel [0..99]
227static inline unsigned
228ss(unsigned c){
229 unsigned r = round(c * 100.0 / 255); // use real [0..100] scaling
230 return r > 99 ? 99: r;
231}
232
233// get the keys for an rgb point. the returned value is on [0..999], and maps
234// to a static qnode. the second value is on [0..7], and maps within the
235// fractured onode (if necessary).
236static inline unsigned
237qnode_keys(unsigned r, unsigned g, unsigned b, unsigned *skey){
238 unsigned ssr = ss(r);
239 unsigned ssg = ss(g);
240 unsigned ssb = ss(b);
241 unsigned ret = ssr / 10 * 100 + ssg / 10 * 10 + ssb / 10;
242 *skey = (((ssr % 10) / 5) << 2u) +
243 (((ssg % 10) / 5) << 1u) +
244 ((ssb % 10) / 5);
245//fprintf(stderr, "0x%02x 0x%02x 0x%02x %02u %02u %02u %u %u\n", r, g, b, ssr, ssg, ssb, ret, *skey);
246 return ret;
247}
248
249// have we been chosen for the color table?
250static inline bool
251chosen_p(const qnode* q){
252 return q->cidx & 0x8000u;
253}
254
255static inline unsigned
256make_chosen(unsigned cidx){
257 return cidx | 0x8000u;
258}
259
260// get the cidx without the chosen bit
261static inline unsigned
262qidx(const qnode* q){
263 return q->cidx & ~0x8000u;
264}
265
266#define QNODECOUNT 1000
267
268// create+zorch an array of QNODECOUNT qnodes. this is 1000 entries covering
269// 1000 sixel colors each (we pretend 100 doesn't exist, working on [0..99],
270// heh). in addition, at the end we allocate |colorregs| qnodes, to be used
271// dynamically in "fracturing". the original QNODECOUNT nodes are a static
272// octree, flattened into an array; the latter are used as an actual octree.
273// we must have 8 dynnodes available for every onode we create, or we can run
274// into a situation where we don't have an available dynnode
275// (see insert_color()).
276static qstate*
277alloc_qstate(unsigned colorregs){
278 qstate* qs = malloc(sizeof(*qs));
279 if(qs){
280 qs->dynnodes_free = colorregs;
282 if((qs->qnodes = malloc((QNODECOUNT + qs->dynnodes_total) * sizeof(qnode))) == NULL){
283 free(qs);
284 return NULL;
285 }
286 qs->onodes_free = qs->dynnodes_total / 8;
287 qs->onodes_total = qs->onodes_free;
288 if((qs->onodes = malloc(qs->onodes_total * sizeof(*qs->onodes))) == NULL){
289 free(qs->qnodes);
290 free(qs);
291 return NULL;
292 }
293 // don't technically need to clear the components, as we could
294 // check the pop, but it's hidden under the compulsory cache misses.
295 // we only initialize the static nodes, not the dynamic ones--we know
296 // when we pull a dynamic one that it needs its popcount initialized.
297 memset(qs->qnodes, 0, sizeof(qnode) * QNODECOUNT);
298 qs->table = NULL;
299 }
300 return qs;
301}
302
303// free internals of qstate object
304static void
305free_qstate(qstate *qs){
306 if(qs){
307 loginfo("freeing qstate");
308 free(qs->qnodes);
309 free(qs->onodes);
310 free(qs->table);
311 free(qs);
312 }
313}
314
315// insert a color from the source image into the octree.
316static inline int
317insert_color(qstate* qs, uint32_t pixel){
318 const unsigned r = ncpixel_r(pixel);
319 const unsigned g = ncpixel_g(pixel);
320 const unsigned b = ncpixel_b(pixel);
321 unsigned skey;
322 const unsigned key = qnode_keys(r, g, b, &skey);
323 assert(key < QNODECOUNT);
324 assert(skey < 8);
325 qnode* q = &qs->qnodes[key];
326 if(q->q.pop == 0 && q->qlink == 0){ // previously-unused node
327 q->q.comps[0] = r;
328 q->q.comps[1] = g;
329 q->q.comps[2] = b;
330 q->q.pop = 1;
331 ++qs->smap->colors;
332 return 0;
333 }
334 onode* o;
335 // it's not a fractured node, but it's been used. check to see if we
336 // match the secondary key of what's here.
337 if(q->qlink == 0){
338 unsigned skeynat;
339 qnode_keys(q->q.comps[0], q->q.comps[1], q->q.comps[2], &skeynat);
340 if(skey == skeynat){
341 ++q->q.pop; // pretty good match
342 return 0;
343 }
344 // we want to fracture. if we have no onodes, though, we can't.
345 // we also need at least one dynamic qnode. note that this means we might
346 // open an onode just to fail to insert our current lookup; that's fine;
347 // it's a symmetry between creation and extension.
348 if(qs->dynnodes_free == 0 || qs->onodes_free == 0){
349//fprintf(stderr, "NO FREE ONES %u\n", key);
350 ++q->q.pop; // not a great match, but we're already scattered
351 return 0;
352 }
353 // get the next free onode and zorch it out
354 o = qs->onodes + qs->onodes_total - qs->onodes_free;
355//fprintf(stderr, "o: %p obase: %p %u\n", o, qs->onodes, qs->onodes_total - qs->onodes_free);
356 memset(o, 0, sizeof(*o));
357 // get the next free dynnode and assign it to o, account for dnode
358 o->q[skeynat] = &qs->qnodes[QNODECOUNT + qs->dynnodes_total - qs->dynnodes_free];
359 --qs->dynnodes_free;
360 // copy over our own details
361 memcpy(o->q[skeynat], q, sizeof(*q));
362 // set qlink to one-biased index of the onode, and account for onode
363 q->qlink = qs->onodes_total - qs->onodes_free + 1;
364 --qs->onodes_free;
365 // reset our own population count
366 q->q.pop = 0;
367 }else{
368 // the node has already been fractured
369 o = qs->onodes + (q->qlink - 1);
370 }
371 if(o->q[skey]){
372 // our subnode is already present, huzzah. increase its popcount.
373 ++o->q[skey]->q.pop;
374 return 0;
375 }
376 // we try otherwise to insert ourselves into o. this requires a free dynnode.
377 if(qs->dynnodes_free == 0){
378//fprintf(stderr, "NO DYNFREE %u\n", key);
379 // this should never happen, because we always ought have 8 dynnodes for
380 // every possible onode.
381 return -1;
382 }
383 // get the next free dynnode and assign it to o, account for dnode
384 o->q[skey] = &qs->qnodes[QNODECOUNT + qs->dynnodes_total - qs->dynnodes_free];
385 --qs->dynnodes_free;
386 o->q[skey]->q.pop = 1;
387 o->q[skey]->q.comps[0] = r;
388 o->q[skey]->q.comps[1] = g;
389 o->q[skey]->q.comps[2] = b;
390 o->q[skey]->qlink = 0;
391 o->q[skey]->cidx = 0;
392 ++qs->smap->colors;
393//fprintf(stderr, "INSERTED[%u]: %u %u %u\n", key, q->q.comps[0], q->q.comps[1], q->q.comps[2]);
394 return 0;
395}
396
397// resolve the input color to a color table index following any postprocessing
398// of the octree.
399static inline int
400find_color(const qstate* qs, uint32_t pixel){
401 const unsigned r = ncpixel_r(pixel);
402 const unsigned g = ncpixel_g(pixel);
403 const unsigned b = ncpixel_b(pixel);
404 unsigned skey;
405 const unsigned key = qnode_keys(r, g, b, &skey);
406 const qnode* q = &qs->qnodes[key];
407 if(q->qlink && q->q.pop == 0){
408 if(qs->onodes[q->qlink - 1].q[skey]){
409 q = qs->onodes[q->qlink - 1].q[skey];
410 }else{
411 logpanic("internal error: no color for 0x%016x", pixel);
412 return -1;
413 }
414 }
415 return qidx(q);
416}
417
418// the P2 parameter on a sixel specifies how unspecified pixels are drawn.
419// if P2 is 1, unspecified pixels are transparent. otherwise, they're drawn
420// as something else. some terminals (e.g. foot) can draw more quickly if
421// P2 is 0, so we set that when we have no transparent pixels -- i.e. when
422// all TAM entries are 0. P2 is at a fixed location in the sixel header.
423// obviously, the sixel must already exist.
424static inline void
425change_p2(char* sixel, sixel_p2_e value){
426 sixel[4] = value + '0';
427}
428
429static inline void
430write_rle(char* vec, int* voff, int rle, int rep){
431 if(rle > 2){
432 *voff += sprintf(vec + *voff, "!%d", rle);
433 }else if(rle == 2){
434 vec[(*voff)++] = rep;
435 }
436 if(rle){
437 vec[(*voff)++] = rep;
438 }
439 vec[*voff] = '\0';
440}
441
442// one for each color in the band we're building. |rle| tracks the number of
443// consecutive unwritten instances of the current non-0 rep, which is itself
444// tracked in |rep|. |wrote| tracks the number of sixels written out for this
445// color. whenever we get a new rep (this only happens for non-zero reps),
446// we must write any old rle rep, plus any zero-reps since then.
448 int length; // current length of the vector
449 int rle; // current rep count of non-zero sixel for this color
450 int wrote; // number of sixels we've written out
451 int rep; // representation, 0..63
452};
453
454// add the supplied rle section to the appropriate vector, which might
455// need to be created. we are writing out [bes->wrote, curx) (i.e. curx
456// ought *not* describe the |bes| element, and ought equal |dimx| when
457// finalizing the band). caller must update bes->wrote afterwards!
458static inline char*
459sixelband_extend(char* vec, struct band_extender* bes, int dimx, int curx){
460 assert(dimx >= bes->rle);
461 assert(0 <= bes->rle);
462 assert(0 <= bes->rep);
463 assert(64 > bes->rep);
464 if(vec == NULL){
465 // FIXME for now we make it as big as it could possibly need to be. ps,
466 // don't try to just base it off how far in we are; wipe/restore could
467 // change that!
468 if((vec = malloc(dimx + 1)) == NULL){
469 return NULL;
470 }
471 }
472 // rle will equal 0 if this is our first non-zero rep, at a non-zero x;
473 // in that case, rep is guaranteed to be 0; catch it at the bottom.
474 write_rle(vec, &bes->length, bes->rle, bes->rep + 63);
475 int clearlen = curx - (bes->rle + bes->wrote);
476 write_rle(vec, &bes->length, clearlen, '?');
477 return vec;
478}
479
480// write to this cell's auxvec, backing up the pixels cleared by a wipe. wipes
481// are requested at cell granularity, broken down into sixelbands, broken down
482// by color, and then finally effected at the sixel RLE level. we're thus in
483// any given call handling a horizontal contiguous range of sixels for a single
484// color. the x range is wholly within the cell to be wiped, but the y range
485// might not be, since cells and bands don't necessarily line up. |y| ought be
486// the row of the first pixel of the *band*.
487//
488// we thus need:
489// - the starting and ending true x positions for the *portion of this sixel
490// contained within the wiped cell*.
491// - the true y position at which the sixel starts.
492// - the previous sixel rep and the masked sixel rep--the difference between
493// the two tells us which rows (offset from y) need be written. they ought
494// be the binary forms, not the presentation forms (i.e. [0..63]).
495// - the cell-pixel geometry, necessary for computing offset into the auxvec.
496// - the color.
497//
498// precondition: mask is a bitwise proper subset of rep
499//
500// we find which [1..6] of six rows are affected by examining the difference
501// between |rep| and |masked|, the sixel's row within the cell by taking |y|
502// modulo |cellpxy|, and the position within the auxvec by multiplying that
503// result by |cellpxx| and adding |x| modulo |cellpxx|. we set |len| pixels.
504static inline void
505write_auxvec(uint8_t* auxvec, uint16_t color, int endy, int y, int x, int len,
506 char rep, char masked, int cellpxy, int cellpxx){
507 rep -= 63;
508 masked -= 63;
509 const char diff = rep ^ masked;
510//fprintf(stderr, "AUXVEC WRITE[%hu] ey: %d y/x: %d/%d:%d r: 0x%x m: 0x%x d: 0x%x total %d\n", color, endy, y, x, len, rep, masked, diff, cellpxy * cellpxx);
511 const int xoff = x % cellpxx;
512 const int yoff = y % cellpxy;
513 int dy = 0;
514 for(char bitselector = 1 ; bitselector < 0x40 ; bitselector <<= 1u, ++dy){
515 if((diff & bitselector) == 0){
516//if(diff == 0x20)fprintf(stderr, "diff: 0x%x bs: %d\n", diff, bitselector);
517 continue;
518 }
519 if(yoff + dy == endy){ // reached the next cell below
520//if(diff == 0x20)fprintf(stderr, "BOUNCING! 0x%x bs: %d %d > %d\n", diff, bitselector, yoff + dy, cellpxy);
521 break;
522 }
523//fprintf(stderr, " writing to auxrow %d (%d)\n", yoff + dy, bitselector);
524 const int idx = (((yoff + dy) % cellpxy) * cellpxx + xoff) * AUXVECELEMSIZE;
525//fprintf(stderr, " xoff: %d yoff: %d dy: %d ydy: %d idx: %d\n", xoff, yoff, dy, yoff + dy, idx);
526 for(int i = 0 ; i < len ; ++i){
527 memcpy(&auxvec[idx + i * AUXVECELEMSIZE], &color, AUXVECELEMSIZE);
528 }
529 }
530}
531
532// wipe the color within this band from startx to endx - 1, from starty to
533// endy - 1 (0-offset in the band, a cell-sized region), writing out the
534// auxvec. mask is the allowable sixel, y-wise. returns a positive number if
535// pixels were wiped.
536static inline int
537wipe_color(sixelband* b, int color, int y, int endy, int startx, int endx,
538 char mask, int dimx, uint8_t* auxvec,
539 int cellpxy, int cellpxx){
540 const char* vec = b->vecs[color];
541 if(vec == NULL){
542 return 0; // no work to be done here
543 }
544 int wiped = 0;
545 char* newvec = malloc(dimx + 1);
546 if(newvec == NULL){
547 return -1;
548 }
549//fprintf(stderr, "color: %d Y: %d-%d X: %d-%d\n", color, starty, endy, startx, endx);
550//fprintf(stderr, "s/e: %d/%d mask: %02x WIPE: [%s]\n", starty, endy, mask, vec);
551 // we decode the color within the sixelband, and rebuild it without the
552 // wiped pixels.
553 int rle = 0; // the repetition number for this element
554 // the x coordinate through which we've checked this band. if x + rle is
555 // less than startx, this element cannot be affected by the wipe.
556 // otherwise, starting at startx, it can be affected. once x >= endx, we
557 // are done, and can copy any remaining elements blindly.
558 int x = 0;
559 int voff = 0;
560 while(*vec){
561 if(isdigit(*vec)){
562 rle *= 10;
563 rle += (*vec - '0');
564 }else if(*vec == '!'){
565 rle = 0;
566 }else{
567 if(rle == 0){
568 rle = 1;
569 }
570 char rep = *vec;
571 char masked = ((rep - 63) & mask) + 63;
572//fprintf(stderr, "X/RLE/ENDX: %d %d %d\n", x, rle, endx);
573 if(x + rle <= startx){ // not wiped material; reproduce as-is
574 write_rle(newvec, &voff, rle, rep);
575 x += rle;
576 }else if(masked == rep){ // not changed by wipe; reproduce as-is
577 write_rle(newvec, &voff, rle, rep);
578 x += rle;
579 }else{ // changed by wipe; might have to break it up
580 wiped = 1;
581 if(x < startx){
582 write_rle(newvec, &voff, startx - x, rep);
583 rle -= startx - x;
584 x = startx;
585 }
586 if(x + rle >= endx){
587 // FIXME this might equal the prev/next rep, and we ought combine
588//fprintf(stderr, "************************* %d %d %d\n", endx - x, x, rle);
589 write_rle(newvec, &voff, endx - x, masked);
590 write_auxvec(auxvec, color, endy, y, x, endx - x, rep, masked, cellpxy, cellpxx);
591 rle -= endx - x;
592 x = endx;
593 }else{
594 write_rle(newvec, &voff, rle, masked);
595 write_auxvec(auxvec, color, endy, y, x, rle, rep, masked, cellpxy, cellpxx);
596 x += rle;
597 rle = 0;
598 }
599 if(rle){
600 write_rle(newvec, &voff, rle, rep);
601 x += rle;
602 }
603 }
604 rle = 0;
605 }
606 ++vec;
607 if(x >= endx){
608 strcpy(newvec + voff, vec); // there is always room
609 break;
610 }
611 }
612//if(strcmp(newvec, b->vecs[color])) fprintf(stderr, "WIPED %d y [%d..%d) x [%d..%d) mask: %d [%s]\n", color, starty, endy, startx, endx, mask, newvec);
613 free(b->vecs[color]);
614 if(voff == 0){
615 // FIXME check for other null vectors; free such, and assign NULL
616 free(newvec);
617 newvec = NULL;
618 }
619 b->vecs[color] = newvec;
620 return wiped;
621}
622
623// wipe the band from startx to endx - 1, from starty to endy - 1. returns the
624// number of pixels actually wiped.
625static inline int
626wipe_band(sixelmap* smap, int band, int startx, int endx,
627 int starty, int endy, int dimx, int cellpxy, int cellpxx,
628 uint8_t* auxvec){
629 int wiped = 0;
630 // get 0-offset start and end row bounds for our band.
631 const int sy = band * 6 < starty ? starty - band * 6 : 0;
632 const int ey = (band + 1) * 6 > endy ? 6 - ((band + 1) * 6 - endy) : 6;
633 // we've got a mask that we'll AND with the decoded sixels; set it to
634 // 0 wherever we're wiping.
635 unsigned char mask = 63;
636 // knock out a bit for each row we're wiping within the band
637 for(int i = 0 ; i < 6 ; ++i){
638 if(i >= sy && i < ey){
639 mask &= ~(1u << i);
640 }
641 }
642//fprintf(stderr, "******************** BAND %d MASK 0x%x ********************8\n", band, mask);
643 sixelband* b = &smap->bands[band];
644 // offset into map->data where our color starts
645 for(int i = 0 ; i < b->size ; ++i){
646 wiped += wipe_color(b, i, band * 6, endy, startx, endx, mask,
647 dimx, auxvec, cellpxy, cellpxx);
648 }
649 return wiped;
650}
651
652// we return -1 because we're not doing a proper wipe -- that's not possible
653// using sixel. we just mark it as partially transparent, so that if it's
654// redrawn, it's redrawn using P2=1.
655int sixel_wipe(sprixel* s, int ycell, int xcell){
656//fprintf(stderr, "WIPING %d/%d\n", ycell, xcell);
657 uint8_t* auxvec = sixel_trans_auxvec(ncplane_pile(s->n));
658 if(auxvec == NULL){
659 return -1;
660 }
661 const int cellpxy = ncplane_pile(s->n)->cellpxy;
662 const int cellpxx = ncplane_pile(s->n)->cellpxx;
663 sixelmap* smap = s->smap;
664 const int startx = xcell * cellpxx;
665 const int starty = ycell * cellpxy;
666 int endx = ((xcell + 1) * cellpxx);
667 if(endx >= s->pixx){
668 endx = s->pixx;
669 }
670 int endy = ((ycell + 1) * cellpxy);
671 if(endy >= s->pixy){
672 endy = s->pixy;
673 }
674 const int startband = starty / 6;
675 const int endband = (endy - 1) / 6;
676//fprintf(stderr, "y/x: %d/%d bands: %d-%d start: %d/%d end: %d/%d\n", ycell, xcell, startband, endband - 1, starty, startx, endy, endx);
677 // walk through each color, and wipe the necessary sixels from each band
678 int w = 0;
679 for(int b = startband ; b <= endband ; ++b){
680 w += wipe_band(smap, b, startx, endx, starty, endy, s->pixx,
681 cellpxy, cellpxx, auxvec);
682 }
683 if(w){
684 s->wipes_outstanding = true;
685 }
686 change_p2(s->glyph.buf, SIXEL_P2_TRANS);
687 assert(NULL == s->n->tam[s->dimx * ycell + xcell].auxvector);
688 s->n->tam[s->dimx * ycell + xcell].auxvector = auxvec;
689 // FIXME this invalidation ought not be necessary, since we're simply
690 // wiping, and thus a glyph is going to be printed over whatever we've
691 // just destroyed. in alacritty, however, this isn't sufficient to knock
692 // out a graphic; we need repaint with the transparency.
693 // see https://github.com/dankamongmen/notcurses/issues/2142
694 int absx, absy;
695 ncplane_abs_yx(s->n, &absy, &absx);
696 sprixel_invalidate(s, absy, absx);
697 return 0;
698}
699
700// rebuilds the auxiliary vectors, and scrubs the actual pixels, following
701// extraction of the palette. doing so allows the new frame's pixels to
702// contribute to the solved palette, even if they were wiped in the previous
703// frame. pixels ought thus have been set up in sixel_blit(), despite TAM
704// entries in the ANNIHILATED state.
705static int
706scrub_color_table(sprixel* s){
707 if(s->n && s->n->tam){
708 // we use the sprixel cell geometry rather than the plane's because this
709 // is called during our initial blit, before we've resized the plane.
710 for(unsigned y = 0 ; y < s->dimy ; ++y){
711 for(unsigned x = 0 ; x < s->dimx ; ++x){
712 unsigned txyidx = y * s->dimx + x;
713 sprixcell_e state = s->n->tam[txyidx].state;
714 if(state == SPRIXCELL_ANNIHILATED || state == SPRIXCELL_ANNIHILATED_TRANS){
715//fprintf(stderr, "POSTEXTRACT WIPE %d/%d\n", y, x);
716 sixel_wipe(s, y, x);
717 }
718 }
719 }
720 }
721 return 0;
722}
723
724// goes through the needs_refresh matrix, and damages cells needing refreshing.
725void sixel_refresh(const ncpile* p, sprixel* s){
726 if(s->needs_refresh == NULL){
727 return;
728 }
729 int absy, absx;
730 ncplane_abs_yx(s->n, &absy, &absx);
731 for(unsigned y = 0 ; y < s->dimy ; ++y){
732 const unsigned yy = absy + y;
733 for(unsigned x = 0 ; x < s->dimx ; ++x){
734 unsigned idx = y * s->dimx + x;
735 if(s->needs_refresh[idx]){
736 const unsigned xx = absx + x;
737 if(xx < p->dimx && yy < p->dimy){
738 unsigned ridx = yy * p->dimx + xx;
739 struct crender *r = &p->crender[ridx];
740 r->s.damaged = 1;
741 }
742 }
743 }
744 }
745 free(s->needs_refresh);
746 s->needs_refresh = NULL;
747}
748
749// when we first cross into a new cell, we check its old state, and if it
750// was transparent, set the rmatrix low. otherwise, set it high. this should
751// only be called for the first pixel in each cell.
752static inline void
753update_rmatrix(unsigned char* rmatrix, int txyidx, const tament* tam){
754 if(rmatrix == NULL){
755 return;
756 }
757 sprixcell_e state = tam[txyidx].state;
758 if(state == SPRIXCELL_TRANSPARENT || state > SPRIXCELL_ANNIHILATED){
759 rmatrix[txyidx] = 0;
760 }else{
761 rmatrix[txyidx] = 1;
762 }
763}
764
765static int
766qnodecmp(const void* q0, const void* q1){
767 const qnode* qa = q0;
768 const qnode* qb = q1;
769 return qa->q.pop < qb->q.pop ? -1 : qa->q.pop == qb->q.pop ? 0 : 1;
770}
771
772// from the initial set of QNODECOUNT qnodes, extract the number of active
773// ones -- our initial (reduced) color count -- and sort. heap allocation.
774// precondition: colors > 0
775static qnode*
776get_active_set(qstate* qs, uint32_t colors){
777 qnode* act = malloc(sizeof(*act) * colors);
778 unsigned targidx = 0;
779 // filter the initial qnodes for pop != 0
780 unsigned total = QNODECOUNT + (qs->dynnodes_total - qs->dynnodes_free);
781//fprintf(stderr, "TOTAL IS %u WITH %u COLORS\n", total, colors);
782 for(unsigned z = 0 ; z < total && targidx < colors ; ++z){
783//fprintf(stderr, "EXTRACT? [%04u] pop %u\n", z, qs->qnodes[z].q.pop);
784 if(qs->qnodes[z].q.pop){
785 memcpy(&act[targidx], &qs->qnodes[z], sizeof(*act));
786 // link it back to the original node's position in the octree
787//fprintf(stderr, "LINKING %u to %u\n", targidx, z);
788 act[targidx].qlink = z;
789 ++targidx;
790 }else if(qs->qnodes[z].qlink){
791 const struct onode* o = &qs->onodes[qs->qnodes[z].qlink - 1];
792 // FIXME i don't think we need the second conditional? in a perfect world?
793 for(unsigned s = 0 ; s < 8 && targidx < colors ; ++s){
794//fprintf(stderr, "o: %p qlink: %u\n", o, qs->qnodes[z].qlink - 1);
795 if(o->q[s]){
796 memcpy(&act[targidx], o->q[s], sizeof(*act));
797//fprintf(stderr, "O-LINKING %u to %ld[%u]\n", targidx, o->q[s] - qs->qnodes, s);
798 act[targidx].qlink = o->q[s] - qs->qnodes;
799 ++targidx;
800 }
801 }
802 }
803 }
804//fprintf(stderr, "targidx: %u colors: %u\n", targidx, colors);
805 assert(targidx == colors);
806 qsort(act, colors, sizeof(*act), qnodecmp);
807 return act;
808}
809
810static inline int
811find_next_lowest_chosen(const qstate* qs, int z, int i, const qnode** hq){
812//fprintf(stderr, "FIRST CHOSEN: %u %d\n", z, i);
813 do{
814 const qnode* h = &qs->qnodes[z];
815//fprintf(stderr, "LOOKING AT %u POP %u QLINK %u CIDX %u\n", z, h->q.pop, h->qlink, h->cidx);
816 if(h->q.pop == 0 && h->qlink){
817 const onode* o = &qs->onodes[h->qlink - 1];
818 while(i >= 0){
819 h = o->q[i];
820 if(h && chosen_p(h)){
821 *hq = h;
822//fprintf(stderr, "NEW HQ: %p RET: %u\n", *hq, z * 8 + i);
823 return z * 8 + i;
824 }
825 if(++i == 8){
826 break;
827 }
828 }
829 }else{
830 if(chosen_p(h)){
831 *hq = h;
832//fprintf(stderr, "NEW HQ: %p RET: %u\n", *hq, z * 8);
833 return z * 8;
834 }
835 }
836 ++z;
837 i = 0;
838 }while(z < QNODECOUNT);
839//fprintf(stderr, "RETURNING -1\n");
840 return -1;
841}
842
843static inline void
844choose(qstate* qs, qnode* q, int z, int i, int* hi, int* lo,
845 const qnode** hq, const qnode** lq){
846 if(!chosen_p(q)){
847//fprintf(stderr, "NOT CHOSEN: %u %u %u %u\n", z, qs->qnodes[z].qlink, qs->qnodes[z].q.pop, qs->qnodes[z].cidx);
848 if(z * 8 > *hi){
849 *hi = find_next_lowest_chosen(qs, z, i, hq);
850 }
851 int cur = z * 8 + (i >= 0 ? i : 4);
852 if(*lo == -1){
853 q->cidx = qidx(*hq);
854 }else if(*hi == -1 || cur - *lo < *hi - cur){
855 q->cidx = qidx(*lq);
856 }else{
857 q->cidx = qidx(*hq);
858 }
859 }else{
860 *lq = q;
861 *lo = z * 8;
862 }
863}
864
865// we must reduce the number of colors until we're using less than or equal
866// to the number of color registers.
867static inline int
868merge_color_table(qstate* qs){
869 if(qs->smap->colors == 0){
870 return 0;
871 }
872 qnode* qactive = get_active_set(qs, qs->smap->colors);
873 if(qactive == NULL){
874 return -1;
875 }
876 // assign color table entries to the most popular colors. use the lowest
877 // color table entries for the most popular ones, as they're the shortest
878 // (this is not necessarily an optimizing huristic, but it'll do for now).
879 int cidx = 0;
880//fprintf(stderr, "colors: %u cregs: %u\n", qs->colors, colorregs);
881 for(int z = qs->smap->colors - 1 ; z >= 0 ; --z){
882 if(qs->smap->colors >= qs->bargs->u.pixel.colorregs){
883 if(cidx == qs->bargs->u.pixel.colorregs){
884 break; // we just ran out of color registers
885 }
886 }
887 qs->qnodes[qactive[z].qlink].cidx = make_chosen(cidx);
888 ++cidx;
889 }
890 free(qactive);
891 if(qs->smap->colors > qs->bargs->u.pixel.colorregs){
892 // tend to those which couldn't get a color table entry. we start with two
893 // values, lo and hi, initialized to -1. we iterate over the *static* qnodes,
894 // descending into onodes to check their qnodes. we thus iterate over all
895 // used qnodes, in order (and also unused static qnodes). if the node is
896 // empty, continue. if it is chosen, replace lo. otherwise, if hi is less
897 // than z, we need find the next lowest chosen one. if there is no next
898 // lowest, hi is reset to -1. otherwise, set hi. once we have the new hi > z,
899 // determine which of hi and lo are closer to z, discounting -1 values, and
900 // link te closer one to z. a toplevel node is worth 8 in terms of distance;
901 // and lowlevel node is worth 1.
902 int lo = -1;
903 int hi = -1;
904 const qnode* lq = NULL;
905 const qnode* hq = NULL;
906 for(int z = 0 ; z < QNODECOUNT ; ++z){
907 if(qs->qnodes[z].q.pop == 0){
908 if(qs->qnodes[z].qlink == 0){
909 continue; // unused
910 }
911 // process the onode
912 const onode* o = &qs->onodes[qs->qnodes[z].qlink - 1];
913 for(int i = 0 ; i < 8 ; ++i){
914 if(o->q[i]){
915 choose(qs, o->q[i], z, i, &hi, &lo, &hq, &lq);
916 }
917 }
918 }else{
919 choose(qs, &qs->qnodes[z], z, -1, &hi, &lo, &hq, &lq);
920 }
921 }
922 qs->smap->colors = qs->bargs->u.pixel.colorregs;
923 }
924 return 0;
925}
926
927static inline void
928load_color_table(const qstate* qs){
929 int loaded = 0;
930 int total = QNODECOUNT + (qs->dynnodes_total - qs->dynnodes_free);
931 for(int z = 0 ; z < total && loaded < qs->smap->colors ; ++z){
932 const qnode* q = &qs->qnodes[z];
933 if(chosen_p(q)){
934 qs->table[RGBSIZE * qidx(q) + 0] = ss(q->q.comps[0]);
935 qs->table[RGBSIZE * qidx(q) + 1] = ss(q->q.comps[1]);
936 qs->table[RGBSIZE * qidx(q) + 2] = ss(q->q.comps[2]);
937 ++loaded;
938 }
939 }
940//fprintf(stderr, "loaded: %u colors: %u\n", loaded, qs->colors);
941 assert(loaded == qs->smap->colors);
942}
943
944// build up a sixel band from (up to) 6 rows of the source RGBA.
945static inline int
946build_sixel_band(qstate* qs, int bnum){
947//fprintf(stderr, "building band %d\n", bnum);
948 sixelband* b = &qs->smap->bands[bnum];
949 b->size = qs->smap->colors;
950 size_t bsize = sizeof(*b->vecs) * b->size;
951 size_t mlen = qs->smap->colors * sizeof(struct band_extender);
952 struct band_extender* meta = malloc(mlen);
953 if(meta == NULL){
954 return -1;
955 }
956 b->vecs = malloc(bsize);
957 if(b->vecs == NULL){
958 free(meta);
959 return -1;
960 }
961 memset(b->vecs, 0, bsize);
962 memset(meta, 0, mlen);
963 const int ystart = qs->bargs->begy + bnum * 6;
964 const int endy = (bnum + 1 == qs->smap->sixelbands ?
965 qs->leny - qs->bargs->begy : ystart + 6);
966 struct {
967 int color; // 0..colormax
968 int rep; // non-zero representation, 1..63
969 } active[6];
970 // we're going to advance horizontally through the sixelband
971 int x;
972 // FIXME we could greatly clean this up by tracking, for each color, the active
973 // rep and the number of times we've seen it...but only write it out either (a)
974 // when the rep changes (b) when we get the color again after a gap or (c) at the
975 // end. that way we wouldn't need maintain these prevactive/active sets...
976 for(x = qs->bargs->begx ; x < (qs->bargs->begx + qs->lenx) ; ++x){ // pixel column
977 // there are at most 6 colors represented in any given sixel. at each
978 // sixel, we need to *start tracking* new colors, and colors which changed
979 // their representation. we also write out what we previously tracked for
980 // this color: possibly a non-zero rep, possibly followed by a zero-rep (we
981 // can have zero, either, or both).
982 int activepos = 0; // number of active entries used
983 for(int y = ystart ; y < endy ; ++y){
984 const uint32_t* rgb = (qs->data + (qs->linesize / 4 * y) + x);
985 if(rgba_trans_p(*rgb, qs->bargs->transcolor)){
986 continue;
987 }
988 int cidx = find_color(qs, *rgb);
989 if(cidx < 0){
990 free(meta);
991 return -1;
992 }
993 int act;
994 for(act = 0 ; act < activepos ; ++act){
995 if(active[act].color == cidx){
996 active[act].rep |= (1u << (y - ystart));
997 break;
998 }
999 }
1000 if(act == activepos){ // didn't find it; create new entry
1001 active[activepos].color = cidx;
1002 active[activepos].rep = (1u << (y - ystart));
1003 ++activepos;
1004 }
1005 }
1006 // we now have the active set. check to see if they extend existing RLEs,
1007 // and if not, write out whatever came before us.
1008 for(int i = 0 ; i < activepos ; ++i){
1009 const int c = active[i].color;
1010 if(meta[c].rep == active[i].rep && meta[c].rle + meta[c].wrote == x){
1011 ++meta[c].rle;
1012 }else{
1013 b->vecs[c] = sixelband_extend(b->vecs[c], &meta[c], qs->lenx, x);
1014 if(b->vecs[c] == NULL){
1015 free(meta);
1016 return -1;
1017 }
1018 meta[c].rle = 1;
1019 meta[c].wrote = x;
1020 meta[c].rep = active[i].rep;
1021 }
1022 }
1023 }
1024 for(int i = 0 ; i < qs->smap->colors ; ++i){
1025 if(meta[i].rle){ // color was wholly unused iff rle == 0 at end
1026 b->vecs[i] = sixelband_extend(b->vecs[i], &meta[i], qs->lenx, x);
1027 if(b->vecs[i] == NULL){
1028 free(meta);
1029 return -1;
1030 }
1031 }else{
1032 b->vecs[i] = NULL;
1033 }
1034 }
1035 free(meta);
1036 return 0;
1037}
1038
1039static int
1040bandworker(qstate* qs){
1041 int b;
1042 while((b = qs->bandbuilder++) < qs->smap->sixelbands){
1043 if(build_sixel_band(qs, b) < 0){
1044 return -1;
1045 }
1046 }
1047 return 0;
1048}
1049
1050// we have converged upon some number of colors. we now run over the pixels
1051// once again, and get the actual (color-indexed) sixels.
1052static inline int
1053build_data_table(sixel_engine* sengine, qstate* qs){
1054 sixelmap* smap = qs->smap;
1055 if(smap->sixelbands == 0){
1056 logerror("no sixels");
1057 return -1;
1058 }
1059 qs->bandbuilder = 0;
1060 enqueue_to_workers(sengine, qs);
1061 size_t tsize = RGBSIZE * smap->colors;
1062 qs->table = malloc(tsize);
1063 if(qs->table == NULL){
1064 return -1;
1065 }
1066 load_color_table(qs);
1067 bandworker(qs);
1068 block_on_workers(sengine, qs);
1069 return 0;
1070}
1071
1072static inline int
1073extract_cell_color_table(qstate* qs, long cellid){
1074 const int ccols = qs->bargs->u.pixel.spx->dimx;
1075 const long x = cellid % ccols;
1076 const long y = cellid / ccols;
1077 const int cdimy = qs->bargs->u.pixel.cellpxy;
1078 const int cdimx = qs->bargs->u.pixel.cellpxx;
1079 const int begy = qs->bargs->begy;
1080 const int begx = qs->bargs->begx;
1081 const int leny = qs->leny;
1082 const int lenx = qs->lenx;
1083 const int cstartx = begx + x * cdimx; // starting pixel col for cell
1084 const int cstarty = begy + y * cdimy; // starting pixel row for cell
1085 typeof(qs->bargs->u.pixel.spx->needs_refresh) rmatrix = qs->bargs->u.pixel.spx->needs_refresh;
1086 tament* tam = qs->bargs->u.pixel.spx->n->tam;
1087 int cendy = cstarty + cdimy; // one past last pixel row for cell
1088 if(cendy > begy + leny){
1089 cendy = begy + leny;
1090 }
1091 int cendx = cstartx + cdimx; // one past last pixel col for cell
1092 if(cendx > begx + lenx){
1093 cendx = begx + lenx;
1094 }
1095 // we initialize the TAM entry based on the first pixel. if it's transparent,
1096 // initialize as transparent, and otherwise as opaque. following that, any
1097 // transparent pixel takes opaque to mixed, and any filled pixel takes
1098 // transparent to mixed.
1099 if(cstarty >= cendy){ // we're entirely transparent sixel overhead
1100 tam[cellid].state = SPRIXCELL_TRANSPARENT;
1101 qs->smap->p2 = SIXEL_P2_TRANS; // even one forces P2=1
1102 // FIXME need we set rmatrix?
1103 return 0;
1104 }
1105 const uint32_t* rgb = (qs->data + (qs->linesize / 4 * cstarty) + cstartx);
1106 if(tam[cellid].state == SPRIXCELL_ANNIHILATED || tam[cellid].state == SPRIXCELL_ANNIHILATED_TRANS){
1107 if(rgba_trans_p(*rgb, qs->bargs->transcolor)){
1108 update_rmatrix(rmatrix, cellid, tam);
1109 tam[cellid].state = SPRIXCELL_ANNIHILATED_TRANS;
1110 free(tam[cellid].auxvector);
1111 tam[cellid].auxvector = NULL;
1112 }else{
1113 update_rmatrix(rmatrix, cellid, tam);
1114 free(tam[cellid].auxvector);
1115 tam[cellid].auxvector = NULL;
1116 }
1117 }else{
1118 if(rgba_trans_p(*rgb, qs->bargs->transcolor)){
1119 update_rmatrix(rmatrix, cellid, tam);
1120 tam[cellid].state = SPRIXCELL_TRANSPARENT;
1121 }else{
1122 update_rmatrix(rmatrix, cellid, tam);
1123 tam[cellid].state = SPRIXCELL_OPAQUE_SIXEL;
1124 }
1125 }
1126 for(int visy = cstarty ; visy < cendy ; ++visy){ // current abs pixel row
1127 for(int visx = cstartx ; visx < cendx ; ++visx){ // current abs pixel col
1128 rgb = (qs->data + (qs->linesize / 4 * visy) + visx);
1129 // we do *not* exempt already-wiped pixels from palette creation. once
1130 // we're done, we'll call sixel_wipe() on these cells. so they remain
1131 // one of SPRIXCELL_ANNIHILATED or SPRIXCELL_ANNIHILATED_TRANS.
1132 // intentional bitwise or, to avoid dependency
1133 if(tam[cellid].state != SPRIXCELL_ANNIHILATED){
1134 if(tam[cellid].state == SPRIXCELL_ANNIHILATED_TRANS){
1135 if(!rgba_trans_p(*rgb, qs->bargs->transcolor)){
1136 tam[cellid].state = SPRIXCELL_ANNIHILATED;
1137 }
1138 }else{
1139 if(rgba_trans_p(*rgb, qs->bargs->transcolor)){
1140 if(tam[cellid].state == SPRIXCELL_OPAQUE_SIXEL){
1141 tam[cellid].state = SPRIXCELL_MIXED_SIXEL;
1142 }
1143 }else{
1144 if(tam[cellid].state == SPRIXCELL_TRANSPARENT){
1145 tam[cellid].state = SPRIXCELL_MIXED_SIXEL;
1146 }
1147 }
1148 }
1149 }
1150//fprintf(stderr, "vis: %d/%d\n", visy, visx);
1151 if(rgba_trans_p(*rgb, qs->bargs->transcolor)){
1152 continue;
1153 }
1154 if(insert_color(qs, *rgb)){
1155 return -1;
1156 }
1157 }
1158 }
1159 // if we're opaque, we needn't clear the old cell with a glyph
1160 if(tam[cellid].state == SPRIXCELL_OPAQUE_SIXEL){
1161 rmatrix[cellid] = 0;
1162 }else{
1163 qs->smap->p2 = SIXEL_P2_TRANS; // even one forces P2=1
1164 }
1165 return 0;
1166}
1167
1168// we have a 4096-element array that takes the 4-5-3 MSBs from the RGB
1169// components. once it's complete, we might need to either merge some
1170// chunks, or expand them, converging towards the available number of
1171// color registers. |ccols| is cell geometry; |leny| and |lenx| are pixel
1172// geometry, and *do not* include sixel padding.
1173static int
1174extract_color_table(sixel_engine* sengine, qstate* qs){
1175 const blitterargs* bargs = qs->bargs;
1176 // use the cell geometry as computed by the visual layer; leny doesn't
1177 // include any mandatory sixel padding.
1178 const int crows = bargs->u.pixel.spx->dimy;
1179 const int ccols = bargs->u.pixel.spx->dimx;
1180 typeof(bargs->u.pixel.spx->needs_refresh) rmatrix;
1181 rmatrix = malloc(sizeof(*rmatrix) * crows * ccols);
1182 if(rmatrix == NULL){
1183 return -1;
1184 }
1185 bargs->u.pixel.spx->needs_refresh = rmatrix;
1186 long cellid = 0;
1187 for(int y = 0 ; y < crows ; ++y){ // cell row
1188 for(int x = 0 ; x < ccols ; ++x){ // cell column
1189 if(extract_cell_color_table(qs, cellid)){
1190 return -1;
1191 }
1192 ++cellid;
1193 }
1194 }
1195 loginfo("octree got %"PRIu32" entries", qs->smap->colors);
1196 if(merge_color_table(qs)){
1197 return -1;
1198 }
1199 if(build_data_table(sengine, qs)){
1200 return -1;
1201 }
1202 loginfo("final palette: %u/%u colors", qs->smap->colors, qs->bargs->u.pixel.colorregs);
1203 return 0;
1204}
1205
1206static inline int
1207write_sixel_intro(fbuf* f, sixel_p2_e p2, int leny, int lenx){
1208 int rr, r = fbuf_puts(f, "\x1bP0;");
1209 if(r < 0){
1210 return -1;
1211 }
1212 if((rr = fbuf_putint(f, p2)) < 0){
1213 return -1;
1214 }
1215 r += rr;
1216 if((rr = fbuf_puts(f, ";0q\"1;1;")) < 0){
1217 return -1;
1218 }
1219 r += rr;
1220 if((rr = fbuf_putint(f, lenx)) < 0){
1221 return -1;
1222 }
1223 r += rr;
1224 if(fbuf_putc(f, ';') != 1){
1225 return -1;
1226 }
1227 ++r;
1228 if((rr = fbuf_putint(f, leny)) < 0){
1229 return -1;
1230 }
1231 r += rr;
1232 return r;
1233}
1234
1235// write a single color register. rc/gc/bc are on [0..100].
1236static inline int
1237write_sixel_creg(fbuf* f, int idx, int rc, int gc, int bc){
1238 int rr, r = 0;
1239 if(fbuf_putc(f, '#') != 1){
1240 return -1;
1241 }
1242 ++r;
1243 if((rr = fbuf_putint(f, idx)) < 0){
1244 return -1;
1245 }
1246 r += rr;
1247 if((rr = fbuf_puts(f, ";2;")) < 0){
1248 return -1;
1249 }
1250 r += rr;
1251 if((rr = fbuf_putint(f, rc)) < 0){
1252 return -1;
1253 }
1254 r += rr;
1255 if(fbuf_putc(f, ';') != 1){
1256 return -1;
1257 }
1258 ++r;
1259 if((rr = fbuf_putint(f, gc)) < 0){
1260 return -1;
1261 }
1262 r += rr;
1263 if(fbuf_putc(f, ';') != 1){
1264 return -1;
1265 }
1266 ++r;
1267 if((rr = fbuf_putint(f, bc)) < 0){
1268 return -1;
1269 }
1270 r += rr;
1271 return r;
1272}
1273
1274// write the escape which opens a Sixel, plus the palette table. returns the
1275// number of bytes written, so that this header can be directly copied in
1276// future reencodings. |leny| and |lenx| are output pixel geometry.
1277// returns the number of bytes written, so it can be stored at *parse_start.
1278static int
1279write_sixel_header(qstate* qs, fbuf* f, int leny){
1280 if(leny % 6){
1281 return -1;
1282 }
1283 // Set Raster Attributes - pan/pad=1 (pixel aspect ratio), Ph=qs->lenx, Pv=leny
1284 int r = write_sixel_intro(f, qs->smap->p2, leny, qs->lenx);
1285 if(r < 0){
1286 return -1;
1287 }
1288 for(int i = 0 ; i < qs->smap->colors ; ++i){
1289 const unsigned char* rgb = qs->table + i * RGBSIZE;
1290 //fprintf(fp, "#%d;2;%u;%u;%u", i, rgb[0], rgb[1], rgb[2]);
1291 int rr = write_sixel_creg(f, i, rgb[0], rgb[1], rgb[2]);
1292 if(rr < 0){
1293 return -1;
1294 }
1295 r += rr;
1296 }
1297 return r;
1298}
1299
1300static int
1301write_sixel_payload(fbuf* f, const sixelmap* map){
1302 for(int j = 0 ; j < map->sixelbands ; ++j){
1303 int needclosure = 0;
1304 const sixelband* band = &map->bands[j];
1305 for(int i = 0 ; i < band->size ; ++i){
1306 if(band->vecs[i]){
1307 if(needclosure){
1308 if(fbuf_putc(f, '$') != 1){ // end previous one
1309 return -1;
1310 }
1311 }else{
1312 needclosure = 1;
1313 }
1314 if(fbuf_putc(f, '#') != 1){
1315 return -1;
1316 }
1317 if(fbuf_putint(f, i) < 0){
1318 return -1;
1319 }
1320 if(fbuf_puts(f, band->vecs[i]) < 0){
1321 return -1;
1322 }
1323 }
1324 }
1325 if(fbuf_putc(f, '-') != 1){
1326 return -1;
1327 }
1328 }
1329 if(fbuf_puts(f, "\e\\") < 0){
1330 return -1;
1331 }
1332 return 0;
1333}
1334
1335// once per render cycle (if needed), make the actual payload match the TAM. we
1336// don't do these one at a time due to the complex (expensive) process involved
1337// in regenerating a sixel (we can't easily do it in-place). anything newly
1338// ANNIHILATED (state is ANNIHILATED, but no auxvec present) is dropped from
1339// the payload, and an auxvec is generated. anything newly restored (state is
1340// OPAQUE_SIXEL or MIXED_SIXEL, but an auxvec is present) is restored to the
1341// payload, and the auxvec is freed. none of this takes effect until the sixel
1342// is redrawn, and annihilated sprixcells still require a glyph to be emitted.
1343static inline int
1344sixel_reblit(sprixel* s){
1345 fbuf_chop(&s->glyph, s->parse_start);
1346 if(write_sixel_payload(&s->glyph, s->smap) < 0){
1347 return -1;
1348 }
1349 change_p2(s->glyph.buf, s->smap->p2);
1350 return 0;
1351}
1352
1353// write out the sixel header after having quantized the palette.
1354static inline int
1355sixel_blit_inner(qstate* qs, sixelmap* smap, const blitterargs* bargs, tament* tam){
1356 fbuf f;
1357 if(fbuf_init(&f)){
1358 return -1;
1359 }
1360 sprixel* s = bargs->u.pixel.spx;
1361 const int cellpxy = bargs->u.pixel.cellpxy;
1362 const int cellpxx = bargs->u.pixel.cellpxx;
1363 int outy = qs->leny;
1364 if(outy % 6){
1365 outy += 6 - (qs->leny % 6);
1366 smap->p2 = SIXEL_P2_TRANS;
1367 }
1368 int parse_start = write_sixel_header(qs, &f, outy);
1369 if(parse_start < 0){
1370 fbuf_free(&f);
1371 return -1;
1372 }
1373 // we don't write out the payload yet -- set wipes_outstanding high, and
1374 // it'll be emitted via sixel_reblit(), taking into account any wipes that
1375 // occurred before it was displayed. otherwise, such a wipe would require
1376 // two emissions, one of which would be thrown away.
1377 scrub_tam_boundaries(tam, outy, qs->lenx, cellpxy, cellpxx);
1378 // take ownership of buf on success
1379 if(plane_blit_sixel(s, &f, outy, qs->lenx, parse_start, tam, SPRIXEL_INVALIDATED) < 0){
1380 fbuf_free(&f);
1381 return -1;
1382 }
1383 s->smap = smap;
1384 return 1;
1385}
1386
1387// |leny| and |lenx| are the scaled output geometry. we take |leny| up to the
1388// nearest multiple of six greater than or equal to |leny|.
1389int sixel_blit(ncplane* n, int linesize, const void* data, int leny, int lenx,
1390 const blitterargs* bargs){
1391 if(bargs->u.pixel.colorregs >= TRANS_PALETTE_ENTRY){
1392 logerror("palette too large %d", bargs->u.pixel.colorregs);
1393 return -1;
1394 }
1395 sixelmap* smap = sixelmap_create(leny - bargs->begy);
1396 if(smap == NULL){
1397 return -1;
1398 }
1399 assert(n->tam);
1400 qstate* qs;
1401 if((qs = alloc_qstate(bargs->u.pixel.colorregs)) == NULL){
1402 logerror("couldn't allocate qstate");
1403 sixelmap_free(smap);
1404 return -1;
1405 }
1406 qs->bargs = bargs;
1407 qs->data = data;
1408 qs->linesize = linesize;
1409 qs->smap = smap;
1410 qs->leny = leny;
1411 qs->lenx = lenx;
1412 sixel_engine* sengine = ncplane_pile(n) ? ncplane_notcurses(n)->tcache.sixelengine : NULL;
1413 if(extract_color_table(sengine, qs)){
1414 free(bargs->u.pixel.spx->needs_refresh);
1415 bargs->u.pixel.spx->needs_refresh = NULL;
1416 sixelmap_free(smap);
1417 free_qstate(qs);
1418 return -1;
1419 }
1420 // takes ownership of sixelmap on success
1421 int r = sixel_blit_inner(qs, smap, bargs, n->tam);
1422 free_qstate(qs);
1423 if(r < 0){
1424 sixelmap_free(smap);
1425 // FIXME free refresh table?
1426 }
1427 scrub_color_table(bargs->u.pixel.spx);
1428 // we haven't actually emitted the body of the sixel yet. instead, we'll emit
1429 // it at sixel_redraw(), thus avoiding a double emission in the case of wipes
1430 // taking place before it's visible.
1431 bargs->u.pixel.spx->wipes_outstanding = 1;
1432 return r;
1433}
1434
1435// to destroy a sixel, we damage all cells underneath it. we might not have
1436// to, though, if we've got a new sixel ready to go where the old sixel was
1437// (though we'll still need to if the new sprixcell not opaque, and the
1438// old and new sprixcell are different in any transparent pixel).
1439int sixel_scrub(const ncpile* p, sprixel* s){
1440 loginfo("%d state %d at %d/%d (%d/%d)", s->id, s->invalidated, s->movedfromy, s->movedfromx, s->dimy, s->dimx);
1441 int starty = s->movedfromy;
1442 int startx = s->movedfromx;
1443 for(int yy = starty ; yy < starty + (int)s->dimy && yy < (int)p->dimy ; ++yy){
1444 for(int xx = startx ; xx < startx + (int)s->dimx && xx < (int)p->dimx ; ++xx){
1445 int ridx = yy * p->dimx + xx;
1446 struct crender *r = &p->crender[ridx];
1447 if(!s->n){
1448 // need this to damage cells underneath a sprixel we're removing
1449 r->s.damaged = 1;
1450 continue;
1451 }
1452 sprixel* trues = r->sprixel ? r->sprixel : s;
1453 if(yy >= (int)trues->n->leny || yy - trues->n->absy < 0){
1454 r->s.damaged = 1;
1455 continue;
1456 }
1457 if(xx >= (int)trues->n->lenx || xx - trues->n->absx < 0){
1458 r->s.damaged = 1;
1459 continue;
1460 }
1461 sprixcell_e state = sprixel_state(trues, yy, xx);
1462//fprintf(stderr, "CHECKING %d/%d state: %d %d/%d\n", yy - s->movedfromy - s->n->absy, xx - s->movedfromx - s->n->absx, state, yy, xx);
1463 if(state == SPRIXCELL_TRANSPARENT || state == SPRIXCELL_MIXED_SIXEL){
1464 r->s.damaged = 1;
1465 }else if(s->invalidated == SPRIXEL_MOVED){
1466 // ideally, we wouldn't damage our annihilated sprixcells, but if
1467 // we're being annihilated only during this cycle, we need to go
1468 // ahead and damage it.
1469 r->s.damaged = 1;
1470 }
1471 }
1472 }
1473 return 1;
1474}
1475
1476// returns the number of bytes written
1477int sixel_draw(const tinfo* ti, const ncpile* p, sprixel* s, fbuf* f,
1478 int yoff, int xoff){
1479 (void)ti;
1480 // if we've wiped or rebuilt any cells, effect those changes now, or else
1481 // we'll get flicker when we move to the new location.
1482 if(s->wipes_outstanding){
1483 if(sixel_reblit(s)){
1484 return -1;
1485 }
1486 s->wipes_outstanding = false;
1487 }
1488 if(p){
1489 const int targy = s->n->absy + yoff;
1490 const int targx = s->n->absx + xoff;
1491 if(goto_location(p->nc, f, targy, targx, NULL)){
1492 return -1;
1493 }
1494 if(s->invalidated == SPRIXEL_MOVED){
1495 for(int yy = s->movedfromy ; yy < s->movedfromy + (int)s->dimy && yy < (int)p->dimy ; ++yy){
1496 if(yy < 0){
1497 continue;
1498 }
1499 for(int xx = s->movedfromx ; xx < s->movedfromx + (int)s->dimx && xx < (int)p->dimx ; ++xx){
1500 if(xx < 0){
1501 continue;
1502 }
1503 struct crender *r = &p->crender[yy * p->dimx + xx];
1504 if(!r->sprixel || sprixel_state(r->sprixel, yy, xx) != SPRIXCELL_OPAQUE_SIXEL){
1505 r->s.damaged = 1;
1506 }
1507 }
1508 }
1509 }
1510 }
1511 if(fbuf_putn(f, s->glyph.buf, s->glyph.used) < 0){
1512 return -1;
1513 }
1514 s->invalidated = SPRIXEL_QUIESCENT;
1515 return s->glyph.used;
1516}
1517
1518// a quantization worker.
1519static void *
1520sixel_worker(void* v){
1521 work_queue* wq = v;
1522 sixel_engine *sengine = wq->sengine;
1523
1524 qstate* qs = NULL;
1525 unsigned bufpos = 0; // index into worker queue
1526 do{
1527 pthread_mutex_lock(&sengine->lock);
1528 while(wq->used == 0 && !sengine->done){
1529 pthread_cond_wait(&sengine->cond, &sengine->lock);
1530 }
1531 if(!sengine->done){
1532 qs = wq->qstates[bufpos];
1533 }else{
1534 qs = NULL;
1535 }
1536 pthread_mutex_unlock(&sengine->lock);
1537 if(qs == NULL){
1538 break;
1539 }
1540 bandworker(qs);
1541 bool sendsignal = false;
1542 pthread_mutex_lock(&sengine->lock);
1543 --wq->used;
1544 if(--qs->refcount == 0){
1545 sendsignal = true;
1546 }
1547 pthread_mutex_unlock(&sengine->lock);
1548 if(sendsignal){
1549 pthread_cond_broadcast(&sengine->cond);
1550 }
1551 if(++bufpos == WORKERDEPTH){
1552 bufpos = 0;
1553 }
1554 }while(1);
1555 return NULL;
1556}
1557
1558static int
1559sixel_init_core(tinfo* ti, const char* initstr, int fd){
1560 if((ti->sixelengine = malloc(sizeof(sixel_engine))) == NULL){
1561 return -1;
1562 }
1563 sixel_engine* sengine = ti->sixelengine;
1564 pthread_mutex_init(&sengine->lock, NULL);
1565 pthread_cond_init(&sengine->cond, NULL);
1566 sengine->done = false;
1567 const int workers_wanted = sizeof(sengine->tids) / sizeof(*sengine->tids);
1568 for(int w = 0 ; w < workers_wanted ; ++w){
1569 sengine->queues[w].sengine = sengine;
1570 sengine->queues[w].writeto = 0;
1571 sengine->queues[w].used = 0;
1572 if(pthread_create(&sengine->tids[w], NULL, sixel_worker, &sengine->queues[w])){
1573 logerror("couldn't spin up sixel worker %d/%d", w, workers_wanted);
1574 // FIXME kill any created workers
1575 return -1;
1576 }
1577 }
1578 return tty_emit(initstr, fd);
1579}
1580
1581// private mode 80 (DECSDM) manages "Sixel Scrolling Mode" vs "Sixel Display
1582// Mode". when 80 is enabled (i.e. DECSDM mode), images are displayed at the
1583// upper left, and clipped to the window. we don't want either of those things
1584// to happen, so we explicitly disable DECSDM.
1585// private mode 8452 places the cursor at the end of a sixel when it's
1586// emitted. we don't need this for rendered mode, but we do want it for
1587// direct mode. it causes us no problems, so always set it.
1589 return sixel_init_core(ti, "\e[?80l\e[?8452h", fd);
1590}
1591
1593 // some terminals, at some versions, invert the sense of DECSDM. for those,
1594 // we must use 80h rather than the correct 80l. this grows out of a
1595 // misunderstanding in XTerm through patchlevel 368, which was widely
1596 // copied into other terminals.
1597 return sixel_init_core(ti, "\e[?80h\e[?8452h", fd);
1598}
1599
1600// if we aren't sure of the semantics of the terminal we're speaking with,
1601// don't touch DECSDM at all. it's almost certainly set up the way we want.
1602int sixel_init(tinfo* ti, int fd){
1603 return sixel_init_core(ti, "\e[?8452h", fd);
1604}
1605
1606// restore the |yoff|th bit of the sixel at |xoff| for the specified vec
1607// FIXME this is a very dopey implementation yuck, use RLE at least
1608static int
1609restore_vec(sixelband* b, int color, int bit, int xoff, int dimx){
1610 if(color >= b->size){
1611 logpanic("illegal color %d >= %d", color, b->size);
1612 return -1;
1613 }
1614 char* v = NULL;
1615 const char* vec = b->vecs[color]; // might be NULL
1616 if(vec == NULL){ // write this sixel, and we're done
1617 struct band_extender bes = {
1618 .rle = 1,
1619 .rep = bit,
1620 };
1621 if((v = sixelband_extend(v, &bes, dimx, xoff)) == NULL){
1622 return -1;
1623 }
1624 }else{
1625 int rle = 0; // the repetition number for this element
1626 int x = 0;
1627 int voff = 0;
1628 if((v = malloc(dimx + 1)) == NULL){
1629 return -1;
1630 }
1631 while(*vec){
1632 if(isdigit(*vec)){
1633 rle *= 10;
1634 rle += (*vec - '0');
1635 }else if(*vec == '!'){
1636 rle = 0;
1637 }else{
1638 if(rle == 0){
1639 rle = 1;
1640 }
1641 char rep = *vec;
1642//fprintf(stderr, "X/RLE/ENDX: %d %d %d\n", x, rle, endx);
1643 if(x + rle <= xoff){ // not wiped material; reproduce as-is
1644 write_rle(v, &voff, rle, rep);
1645 x += rle;
1646 }else if(x > xoff){
1647 write_rle(v, &voff, rle, rep);
1648 x += rle;
1649 }else{
1650 if(x < xoff){
1651 write_rle(v, &voff, xoff - x, rep);
1652 rle -= xoff - x;
1653 x = xoff;
1654 }
1655 write_rle(v, &voff, 1, ((rep - 63) | bit) + 63);
1656 --rle;
1657 ++x;
1658 if(rle){
1659 write_rle(v, &voff, rle, rep);
1660 x += rle;
1661 }
1662 }
1663 rle = 0;
1664 }
1665 ++vec;
1666 if(x > xoff){
1667 strcpy(v + voff, vec); // there is always room
1668 break;
1669 }
1670 }
1671 }
1672 free(b->vecs[color]);
1673 b->vecs[color] = v;
1674//fprintf(stderr, "SET NEW VEC (%zu) [%s]\n", strlen(v), v);
1675 return 0;
1676}
1677
1678// rebuild the portion of some cell which is within this band, having stored
1679// the pixels into the auxvec when the cell was wiped (and updated them if we
1680// loaded another frame). we go through the auxvec to the right and down,
1681// within the area covered by our band. if the entry is transparent, do
1682// nothing. otherwise, it is some color; collect other instances of the color,
1683// marking them transparent as we do so, and update that color's band. in
1684// the worst case (all pixels different colors), this will be p^2 =\ FIXME.
1685//
1686// returns the number of source-transparent pixels (i.e. pixels which weren't
1687// restored), which will be used to update the TAM state.
1688static inline int
1689restore_band(sixelmap* smap, int band, int startx, int endx,
1690 int starty, int endy, int dimx, int cellpxy, int cellpxx,
1691 uint8_t* auxvec){
1692 int restored = 0;
1693 const int sy = band * 6 < starty ? starty - band * 6 : 0;
1694 const int ey = (band + 1) * 6 > endy ? 6 - ((band + 1) * 6 - endy) : 6;
1695 const int width = endx - startx;
1696 const int height = ey - sy;
1697 const int totalpixels = width * height;
1698 sixelband* b = &smap->bands[band];
1699//fprintf(stderr, "RESTORING band %d (%d->%d (%d->%d), %d->%d) %d pixels\n", band, sy, ey, starty, endy, startx, endx, totalpixels);
1700 int yoff = ((band * 6) + sy - starty) % cellpxy; // we start off on this row of the auxvec
1701 int xoff = startx % cellpxx;
1702 for(int dy = sy ; dy < ey ; ++dy, ++yoff){
1703 const int idx = (yoff * cellpxx + xoff) * AUXVECELEMSIZE;
1704 const int bit = 1 << dy;
1705//fprintf(stderr, " looking at bandline %d (auxvec row %d idx %d, dy %d)\n", dy, yoff, idx, dy);
1706 for(int dx = 0 ; startx + dx < endx ; ++dx){
1707 uint16_t color;
1708 memcpy(&color, &auxvec[idx + dx * AUXVECELEMSIZE], AUXVECELEMSIZE);
1709//fprintf(stderr, " idx %d (dx %d x %d): %hu\n", idx, dx, dx + startx, color);
1710 if(color != TRANS_PALETTE_ENTRY){
1711 restore_vec(b, color, bit, startx + dx, dimx);
1712 ++restored;
1713 }
1714 }
1715 }
1716 (void)smap;
1717 return totalpixels - restored;
1718}
1719
1720// only called for cells in SPRIXCELL_ANNIHILATED[_TRANS]. just post to
1721// wipes_outstanding, so the Sixel gets regenerated the next render cycle,
1722// just like wiping. this is necessary due to the complex nature of
1723// modifying a Sixel -- we want to do them all in one batch.
1724int sixel_rebuild(sprixel* s, int ycell, int xcell, uint8_t* auxvec){
1725//fprintf(stderr, "REBUILDING %d/%d\n", ycell, xcell);
1726 if(auxvec == NULL){
1727 return -1;
1728 }
1729 const int cellpxy = ncplane_pile(s->n)->cellpxy;
1730 const int cellpxx = ncplane_pile(s->n)->cellpxx;
1731 sixelmap* smap = s->smap;
1732 const int startx = xcell * cellpxx;
1733 const int starty = ycell * cellpxy;
1734 int endx = ((xcell + 1) * cellpxx);
1735 if(endx >= s->pixx){
1736 endx = s->pixx;
1737 }
1738 int endy = ((ycell + 1) * cellpxy);
1739 if(endy >= s->pixy){
1740 endy = s->pixy;
1741 }
1742 const int startband = starty / 6;
1743 const int endband = (endy - 1) / 6;
1744//fprintf(stderr, "%d/%d start: %d/%d end: %d/%d bands: %d-%d\n", ycell, xcell, starty, startx, endy, endx, starty / 6, endy / 6);
1745 // walk through each color, and wipe the necessary sixels from each band
1746 int w = 0;
1747 for(int b = startband ; b <= endband ; ++b){
1748 w += restore_band(smap, b, startx, endx, starty, endy, s->pixx,
1749 cellpxy, cellpxx, auxvec);
1750 }
1751 s->wipes_outstanding = true;
1752 sprixcell_e newstate;
1753 if(w == cellpxx * cellpxy){
1754 newstate = SPRIXCELL_TRANSPARENT;
1755 }else if(w){
1756 newstate = SPRIXCELL_MIXED_SIXEL;
1757 }else{
1758 newstate = SPRIXCELL_OPAQUE_SIXEL;
1759 }
1760 s->n->tam[s->dimx * ycell + xcell].state = newstate;
1761 return 1;
1762}
1763
1765 sixel_engine* sengine = ti->sixelengine;
1766 const unsigned tids = POPULATION;
1767 pthread_mutex_lock(&sengine->lock);
1768 sengine->done = 1;
1769 pthread_mutex_unlock(&sengine->lock);
1770 pthread_cond_broadcast(&sengine->cond);
1771 loginfo("joining %u sixel thread%s", tids, tids == 1 ? "" : "s");
1772 for(unsigned t = 0 ; t < tids ; ++t){
1773 pthread_join(sengine->tids[t], NULL);
1774 }
1775 pthread_mutex_destroy(&sengine->lock);
1776 pthread_cond_destroy(&sengine->cond);
1777 free(sengine);
1778 loginfo("reaped sixel engine");
1779 ti->sixelengine = NULL;
1780 // no way to know what the state was before; we ought use XTSAVE/XTRESTORE
1781}
1782
1783// create an auxiliary vector suitable for a Sixel sprixcell, and zero it out.
1784// there are two bytes per pixel in the cell: a palette index of up to 65534,
1785// or 65535 to indicate transparency.
1786uint8_t* sixel_trans_auxvec(const ncpile* p){
1787 const size_t slen = AUXVECELEMSIZE * p->cellpxy * p->cellpxx;
1788 uint8_t* a = malloc(slen);
1789 if(a){
1790 memset(a, 0xff, slen);
1791 }
1792 return a;
1793}
assert(false)
const nccell * c
Definition egcpool.h:296
uint32_t idx
Definition egcpool.h:298
free(duplicated)
if(len<=2)
Definition egcpool.h:175
int r
Definition fbuf.h:226
void sprixel_invalidate(sprixel *s, int y, int x)
Definition sprite.c:103
#define logerror(fmt,...)
Definition logging.h:32
#define loginfo(fmt,...)
Definition logging.h:42
#define logpanic(fmt,...)
Definition logging.h:22
void ncplane_abs_yx(const ncplane *n, int *RESTRICT y, int *RESTRICT x)
Definition notcurses.c:2642
notcurses * ncplane_notcurses(const ncplane *n)
Definition notcurses.c:2626
int y
Definition notcurses.h:1905
vopts n
Definition notcurses.h:3502
int int x
Definition notcurses.h:1905
struct ncvisual_options v
Definition notcurses.h:3497
API int API int const nccell unsigned len
Definition notcurses.h:2588
uint8_t * sixel_trans_auxvec(const ncpile *p)
Definition sixel.c:1786
int sixel_scrub(const ncpile *p, sprixel *s)
Definition sixel.c:1439
void sixel_refresh(const ncpile *p, sprixel *s)
Definition sixel.c:725
int sixel_init(tinfo *ti, int fd)
Definition sixel.c:1602
int sixel_init_inverted(tinfo *ti, int fd)
Definition sixel.c:1592
int sixel_rebuild(sprixel *s, int ycell, int xcell, uint8_t *auxvec)
Definition sixel.c:1724
void sixel_cleanup(tinfo *ti)
Definition sixel.c:1764
int sixel_draw(const tinfo *ti, const ncpile *p, sprixel *s, fbuf *f, int yoff, int xoff)
Definition sixel.c:1477
int sixel_wipe(sprixel *s, int ycell, int xcell)
Definition sixel.c:655
#define WORKERDEPTH
Definition sixel.c:13
#define TRANS_PALETTE_ENTRY
Definition sixel.c:17
#define QNODECOUNT
Definition sixel.c:266
#define POPULATION
Definition sixel.c:10
int sixel_blit(ncplane *n, int linesize, const void *data, int leny, int lenx, const blitterargs *bargs)
Definition sixel.c:1389
#define AUXVECELEMSIZE
Definition sixel.c:20
#define RGBSIZE
Definition sixel.c:6
void sixelmap_free(sixelmap *s)
Definition sixel.c:216
int sixel_init_forcesdm(tinfo *ti, int fd)
Definition sixel.c:1588
sixel_p2_e
Definition sixel.c:55
@ SIXEL_P2_TRANS
Definition sixel.c:57
@ SIXEL_P2_ALLOPAQUE
Definition sixel.c:56
sprixcell_e
Definition sprite.h:114
@ SPRIXCELL_ANNIHILATED
Definition sprite.h:120
@ SPRIXCELL_TRANSPARENT
Definition sprite.h:115
@ SPRIXCELL_ANNIHILATED_TRANS
Definition sprite.h:121
@ SPRIXCELL_MIXED_SIXEL
Definition sprite.h:118
@ SPRIXCELL_OPAQUE_SIXEL
Definition sprite.h:116
@ SPRIXEL_QUIESCENT
Definition sprite.h:21
@ SPRIXEL_INVALIDATED
Definition sprite.h:24
@ SPRIXEL_MOVED
Definition sprite.h:26
struct blitterargs::@3::@5 pixel
sprixel * spx
Definition internal.h:388
union blitterargs::@3 u
uint32_t transcolor
Definition internal.h:380
int colorregs
Definition internal.h:387
struct crender::@2 s
const ncplane * p
Definition internal.h:267
Definition fbuf.h:25
char * buf
Definition fbuf.h:28
unsigned cellpxx
Definition internal.h:326
unsigned dimx
Definition internal.h:325
unsigned dimy
Definition internal.h:325
unsigned cellpxy
Definition internal.h:326
unsigned lenx
Definition internal.h:86
int absy
Definition internal.h:83
unsigned leny
Definition internal.h:86
tament * tam
Definition internal.h:106
int absx
Definition internal.h:83
tinfo tcache
Definition internal.h:360
Definition sixel.c:49
qnode * q[8]
Definition sixel.c:50
Definition sixel.c:31
uint16_t qlink
Definition sixel.c:43
qsample q
Definition sixel.c:32
uint16_t cidx
Definition sixel.c:44
unsigned char comps[RGBSIZE]
Definition sixel.c:24
uint32_t pop
Definition sixel.c:25
Definition sixel.c:88
onode * onodes
Definition sixel.c:97
const uint32_t * data
Definition sixel.c:103
sixelmap * smap
Definition sixel.c:105
unsigned onodes_free
Definition sixel.c:100
unsigned char * table
Definition sixel.c:95
int refcount
Definition sixel.c:89
int linesize
Definition sixel.c:104
unsigned dynnodes_free
Definition sixel.c:98
unsigned onodes_total
Definition sixel.c:101
const struct blitterargs * bargs
Definition sixel.c:102
int leny
Definition sixel.c:108
int lenx
Definition sixel.c:108
atomic_int bandbuilder
Definition sixel.c:90
qnode * qnodes
Definition sixel.c:96
unsigned dynnodes_total
Definition sixel.c:99
pthread_cond_t cond
Definition sixel.c:124
pthread_mutex_t lock
Definition sixel.c:123
bool done
Definition sixel.c:127
pthread_t tids[POPULATION]
Definition sixel.c:126
work_queue queues[POPULATION]
Definition sixel.c:125
char ** vecs
Definition sixel.c:65
int size
Definition sixel.c:64
int sixelbands
Definition sixel.c:83
sixel_p2_e p2
Definition sixel.c:85
sixelband * bands
Definition sixel.c:84
int colors
Definition sixel.c:82
int parse_start
Definition sprite.h:153
unsigned dimx
Definition sprite.h:146
int movedfromx
Definition sprite.h:151
int pixx
Definition sprite.h:147
int movedfromy
Definition sprite.h:150
int pixy
Definition sprite.h:147
bool wipes_outstanding
Definition sprite.h:158
unsigned dimy
Definition sprite.h:146
fbuf glyph
Definition sprite.h:138
struct ncplane * n
Definition sprite.h:142
unsigned char * needs_refresh
Definition sprite.h:156
uint32_t id
Definition sprite.h:139
struct sixelmap * smap
Definition sprite.h:157
sprixel_e invalidated
Definition sprite.h:143
void * auxvector
Definition sprite.h:128
sprixcell_e state
Definition sprite.h:127
void * sixelengine
Definition termdesc.h:175
qstate * qstates[WORKERDEPTH]
Definition sixel.c:114
unsigned used
Definition sixel.c:116
unsigned writeto
Definition sixel.c:115
struct sixel_engine * sengine
Definition sixel.c:117
return NULL
Definition termdesc.h:229