1 #include "internal.h"
2 #include "fbuf.h"
3 
4 #define RGBSIZE 3
5 #define CENTSIZE (RGBSIZE + 1) // size of a color table entry
6 
7 // we set P2 based on whether there is any transparency in the sixel. if not,
8 // use SIXEL_P2_ALLOPAQUE (0), for faster drawing in certain terminals.
9 typedef enum {
10   SIXEL_P2_ALLOPAQUE = 0,
11   SIXEL_P2_TRANS = 1,
12 } sixel_p2_e;
13 
14 // returns the number of individual sixels necessary to represent the specified
15 // pixel geometry. these might encompass more pixel rows than |dimy| would
16 // suggest, up to the next multiple of 6 (i.e. a single row becomes a 6-row
17 // bitmap; as do two, three, four, five, or six rows). input is scaled geometry.
18 static inline int
sixelcount(int dimy,int dimx)19 sixelcount(int dimy, int dimx){
20   return (dimy + 5) / 6 * dimx;
21 }
22 
23 // create an auxiliary vector suitable for a Sixel sprixcell, and zero it out.
24 // there are two bytes per pixel in the cell: a contiguous set of palette
25 // indices (yet another reason that we work with 256 colors), and a contiguous
26 // set of two-value transparencies (these could be folded down to bits from
27 // bytes, saving 7/8 of the space FIXME).
28 static inline uint8_t*
sixel_auxiliary_vector(const sprixel * s)29 sixel_auxiliary_vector(const sprixel* s){
30   int pixels = ncplane_pile(s->n)->cellpxy * ncplane_pile(s->n)->cellpxx;
31   uint8_t* ret = malloc(sizeof(*ret) * pixels * 2);
32   if(ret){
33     memset(ret, 0, sizeof(*ret) * pixels);
34   }
35   return ret;
36 }
37 
38 // we keep a color-indexed set of sixels (a single-row column of six pixels,
39 // encoded as a byte) across the life of the sprixel. This provides a good
40 // combination of easy-to-edit (for wipes and restores) -- you can index by
41 // color, and then by position, in O(1) -- and a form which can easily be
42 // converted to the actual Sixel encoding. wipes and restores come in and edit
43 // these sixels in O(1), and then at display time we recreate the encoded
44 // bitmap in one go if necessary. we could just wipe and restore directly using
45 // the encoded form, but it's a tremendous pain in the ass. this sixelmap will
46 // be kept in the sprixel. when first encoding, data and table each have an
47 // entry for every color register; call sixelmap_trim() when done to cut them
48 // down to the actual number of colors used.
49 typedef struct sixelmap {
50   int colors;
51   int sixelcount;
52   unsigned char* data;  // |colors| x |sixelcount|-byte arrays
53   unsigned char* table; // |colors| x CENTSIZE: components + dtable index
54 } sixelmap;
55 
56 // whip up an all-zero sixelmap for the specified pixel geometry and color
57 // register count. we might not use all the available color registers; call
58 // sixelmap_trim() to release any unused memory once done encoding.
59 static sixelmap*
sixelmap_create(int cregs,int dimy,int dimx)60 sixelmap_create(int cregs, int dimy, int dimx){
61   sixelmap* ret = malloc(sizeof(*ret));
62   if(ret){
63     ret->sixelcount = sixelcount(dimy, dimx);
64     if(ret->sixelcount){
65       size_t dsize = sizeof(*ret->data) * cregs * ret->sixelcount;
66       ret->data = malloc(dsize);
67       if(ret->data){
68         size_t tsize = CENTSIZE * cregs;
69         ret->table = malloc(tsize);
70         if(ret->table){
71           memset(ret->table, 0, tsize);
72           memset(ret->data, 0, dsize);
73           ret->colors = 0;
74           return ret;
75         }
76         free(ret->data);
77       }
78     }
79     free(ret);
80   }
81   return NULL;
82 }
83 
84 // trims s->data down to the number of colors actually used (as opposed to the
85 // number of color registers available).
86 static int
sixelmap_trim(sixelmap * s)87 sixelmap_trim(sixelmap* s){
88   if(s->colors == 0){
89     free(s->table);
90     s->table = NULL;
91     free(s->data);
92     s->data = NULL;
93     return 0;
94   }
95   size_t dsize = sizeof(*s->data) * s->colors * s->sixelcount;
96   unsigned char* tmp = realloc(s->data, dsize);
97   if(tmp == NULL){
98     return -1;
99   }
100   s->data = tmp;
101   size_t tsize = CENTSIZE * s->colors;
102   if((tmp = realloc(s->table, tsize)) == NULL){
103     return -1;
104   }
105   s->table = tmp;
106   return 0;
107 }
108 
sixelmap_free(sixelmap * s)109 void sixelmap_free(sixelmap *s){
110   if(s){
111     free(s->table);
112     free(s->data);
113     free(s);
114   }
115 }
116 
117 typedef struct cdetails {
118   int64_t sums[3];   // sum of components of all matching original colors
119   int32_t count;     // count of pixels matching
120   char hi[RGBSIZE];  // highest sixelspace components we've seen
121   char lo[RGBSIZE];  // lowest sixelspace color we've seen
122 } cdetails;
123 
124 // second pass: construct data for extracted colors over the sixels
125 typedef struct sixeltable {
126   sixelmap* map;        // copy of palette indices / transparency bits
127   // FIXME keep these internal to palette extraction; finalize there
128   cdetails* deets;      // |colorregs| cdetails structures
129   int colorregs;
130   sixel_p2_e p2;        // set to SIXEL_P2_TRANS if we have transparent pixels
131 } sixeltable;
132 
133 // the P2 parameter on a sixel specifies how unspecified pixels are drawn.
134 // if P2 is 1, unspecified pixels are transparent. otherwise, they're drawn
135 // as something else. some terminals (e.g. foot) can draw more quickly if
136 // P2 is 0, so we set that when we have no transparent pixels -- i.e. when
137 // all TAM entries are 0. P2 is at a fixed location in the sixel header.
138 // obviously, the sixel must already exist.
139 static inline void
change_p2(char * sixel,sixel_p2_e value)140 change_p2(char* sixel, sixel_p2_e value){
141   sixel[4] = value + '0';
142 }
143 
144 // take (8-bit rgb value & mask) to sixelspace [0..100]
145 static inline char
ss(unsigned rgb,unsigned char mask)146 ss(unsigned rgb, unsigned char mask){
147   return (rgb & mask) * 100 / 255;
148 }
149 
150 static inline void
break_sixel_comps(unsigned char comps[static RGBSIZE],uint32_t rgba,unsigned char mask)151 break_sixel_comps(unsigned char comps[static RGBSIZE], uint32_t rgba, unsigned char mask){
152   comps[0] = ss(ncpixel_r(rgba), mask);
153   comps[1] = ss(ncpixel_g(rgba), mask);
154   comps[2] = ss(ncpixel_b(rgba), mask);
155 //fprintf(stderr, "%u %u %u\n", comps[0], comps[1], comps[2]);
156 }
157 
158 static inline int
ctable_to_dtable(const unsigned char * ctable)159 ctable_to_dtable(const unsigned char* ctable){
160   return ctable[3]; // * 256 + ctable[4];
161 }
162 
163 static inline void
dtable_to_ctable(int dtable,unsigned char * ctable)164 dtable_to_ctable(int dtable, unsigned char* ctable){
165   ctable[3] = dtable;
166   /*ctable[3] = dtable / 256;
167   ctable[4] = dtable % 256;*/
168 }
169 
170 // wipe the color from startx to endx, from starty to endy. returns 1 if any
171 // pixels were actually wiped.
172 static inline int
wipe_color(sixelmap * smap,int color,int sband,int eband,int startx,int endx,int starty,int endy,int dimx,int cellpixy,int cellpixx,uint8_t * auxvec)173 wipe_color(sixelmap* smap, int color, int sband, int eband,
174            int startx, int endx, int starty, int endy, int dimx,
175            int cellpixy, int cellpixx, uint8_t* auxvec){
176   int wiped = 0;
177   int didx = ctable_to_dtable(smap->table + color * CENTSIZE);
178   // offset into map->data where our color starts
179   int coff = smap->sixelcount * didx;
180 //fprintf(stderr, "didx: %d sixels: %d color: %d B: %d-%d Y: %d-%d X: %d-%d coff: %d\n", didx, smap->sixelcount, color, sband, eband, starty, endy, startx, endx, coff);
181   // we're going to repurpose starty as "starting row of this band", so keep it
182   // around as originy for auxvecidx computations
183   int originy = starty;
184   for(int b = sband ; b <= eband && b * 6 <= endy ; ++b){
185     const int boff = coff + b * dimx; // offset in data where band starts
186     unsigned char mask = 63;
187     for(int i = 0 ; i < 6 ; ++i){
188       if(b * 6 + i >= starty && b * 6 + i <= endy){
189         mask &= ~(1u << i);
190       }
191 //fprintf(stderr, "s/e: %d/%d mask: %02x\n", starty, endy, mask);
192     }
193     for(int x = startx ; x <= endx ; ++x){
194       const int xoff = boff + x;
195       assert(xoff < (smap->colors + 1) * smap->sixelcount);
196 //fprintf(stderr, "band: %d color: %d idx: %d mask: %02x\n", b, color, color * smap->sixelcount + xoff, mask);
197 //fprintf(stderr, "color: %d idx: %d data: %02x\n", color, color * smap->sixelcount + xoff, smap->data[color * smap->sixelcount + xoff]);
198       // this is the auxvec position of the upperleftmost pixel of the sixel
199       // there will be up to five more, each cellpxx away, for the five pixels
200       // below it. there will be cellpxx - 1 after it, each with their own five.
201 //fprintf(stderr, "smap->data[%d] = %02x boff: %d x: %d color: %d\n", xoff, smap->data[xoff], boff, x, color);
202       for(int i = 0 ; i < 6 && b * 6 + i <= endy ; ++i){
203         int auxvecidx = (x - startx) + ((b * 6 + i - originy) * cellpixx);
204         unsigned bit = 1u << i;
205 //fprintf(stderr, "xoff: %d i: %d b: %d endy: %d mask: 0x%02x\n", xoff, i, b, endy, mask);
206         if(!(mask & bit) && (smap->data[xoff] & bit)){
207 //fprintf(stderr, "band %d %d/%d writing %d to auxvec[%d] %p xoff: %d boff: %d\n", b, b * 6 + i, x, color, auxvecidx, auxvec, xoff, boff);
208           auxvec[auxvecidx] = color;
209           auxvec[cellpixx * cellpixy + auxvecidx] = 0;
210         }
211       }
212       if((smap->data[xoff] & mask) != smap->data[xoff]){
213         smap->data[xoff] &= mask;
214         wiped = 1;
215       }
216 //fprintf(stderr, "post: %02x\n", smap->data[color * smap->sixelcount + xoff]);
217     }
218     starty = (starty + 6) / 6 * 6;
219   }
220   return wiped;
221 }
222 
223 // we return -1 because we're not doing a proper wipe -- that's not possible
224 // using sixel. we just mark it as partially transparent, so that if it's
225 // redrawn, it's redrawn using P2=1.
sixel_wipe(sprixel * s,int ycell,int xcell)226 int sixel_wipe(sprixel* s, int ycell, int xcell){
227 //fprintf(stderr, "WIPING %d/%d\n", ycell, xcell);
228   uint8_t* auxvec = sixel_auxiliary_vector(s);
229   if(auxvec == NULL){
230     return -1;
231   }
232   const int cellpxy = ncplane_pile(s->n)->cellpxy;
233   const int cellpxx = ncplane_pile(s->n)->cellpxx;
234   memset(auxvec + cellpxx * cellpxy, 0xff, cellpxx * cellpxy);
235   sixelmap* smap = s->smap;
236   const int startx = xcell * cellpxx;
237   const int starty = ycell * cellpxy;
238   int endx = ((xcell + 1) * cellpxx) - 1;
239   if(endx >= s->pixx){
240     endx = s->pixx - 1;
241   }
242   int endy = ((ycell + 1) * cellpxy) - 1;
243   if(endy >= s->pixy){
244     endy = s->pixy - 1;
245   }
246   const int startband = starty / 6;
247   const int endband = endy / 6;
248 //fprintf(stderr, "y/x: %d/%d start: %d/%d end: %d/%d\n", ycell, xcell, starty, startx, endy, endx);
249   // walk through each color, and wipe the necessary sixels from each band
250   int w = 0;
251   for(int c = 0 ; c < smap->colors ; ++c){
252     w |= wipe_color(smap, c, startband, endband, startx, endx, starty, endy,
253                     s->pixx, cellpxy, cellpxx, auxvec);
254   }
255   if(w){
256     s->wipes_outstanding = true;
257   }
258   change_p2(s->glyph.buf, SIXEL_P2_TRANS);
259   assert(NULL == s->n->tam[s->dimx * ycell + xcell].auxvector);
260   s->n->tam[s->dimx * ycell + xcell].auxvector = auxvec;
261   // FIXME this invalidation ought not be necessary, since we're simply
262   // wiping, and thus a glyph is going to be printed over whatever we've
263   // just destroyed. in alacritty, however, this isn't sufficient to knock
264   // out a graphic; we need repaint with the transparency.
265   // see https://github.com/dankamongmen/notcurses/issues/2142
266   int absx, absy;
267   ncplane_abs_yx(s->n, &absy, &absx);
268   sprixel_invalidate(s, absy, absx);
269   return 0;
270 }
271 
272 // rebuilds the auxiliary vectors, and scrubs the actual pixels, following
273 // extraction of the palette. doing so allows the new frame's pixels to
274 // contribute to the solved palette, even if they were wiped in the previous
275 // frame. pixels ought thus have been set up in sixel_blit(), despite TAM
276 // entries in the ANNIHILATED state.
277 static int
scrub_color_table(sprixel * s)278 scrub_color_table(sprixel* s){
279   if(s->n && s->n->tam){
280     // we use the sprixel cell geometry rather than the plane's because this
281     // is called during our initial blit, before we've resized the plane.
282     for(unsigned y = 0 ; y < s->dimy ; ++y){
283       for(unsigned x = 0 ; x < s->dimx ; ++x){
284         unsigned txyidx = y * s->dimx + x;
285         sprixcell_e state = s->n->tam[txyidx].state;
286         if(state == SPRIXCELL_ANNIHILATED || state == SPRIXCELL_ANNIHILATED_TRANS){
287 //fprintf(stderr, "POSTEXRACT WIPE %d/%d\n", y, x);
288           sixel_wipe(s, y, x);
289         }
290       }
291     }
292   }
293   return 0;
294 }
295 
296 // returns the index at which the provided color can be found *in the
297 // dtable*, possibly inserting it into the ctable. returns -1 if the
298 // color is not in the table and the table is full.
299 static int
find_color(sixeltable * stab,unsigned char comps[static RGBSIZE])300 find_color(sixeltable* stab, unsigned char comps[static RGBSIZE]){
301   int i;
302   if(stab->map->colors){
303     int l, r;
304     l = 0;
305     r = stab->map->colors - 1;
306     do{
307       i = l + (r - l) / 2;
308 //fprintf(stderr, "%02x%02x%02x L %d R %d m %d\n", comps[0], comps[1], comps[2], l, r, i);
309       int cmp = memcmp(stab->map->table + i * CENTSIZE, comps, RGBSIZE);
310       if(cmp == 0){
311         return ctable_to_dtable(stab->map->table + i * CENTSIZE);
312       }
313       if(cmp < 0){
314         l = i + 1;
315       }else{ // key is smaller
316         r = i - 1;
317       }
318 //fprintf(stderr, "BCMP: %d L %d R %d m: %d\n", cmp, l, r, i);
319     }while(l <= r);
320     if(r < 0){
321       i = 0;
322     }else if(l == stab->map->colors){
323       i = stab->map->colors;
324     }else{
325       i = l;
326     }
327     if(stab->map->colors == stab->colorregs){
328       return -1;
329     }
330     if(i < stab->map->colors){
331 //fprintf(stderr, "INSERTING COLOR %u %u %u AT %d\n", comps[0], comps[1], comps[2], i);
332       memmove(stab->map->table + (i + 1) * CENTSIZE,
333               stab->map->table + i * CENTSIZE,
334               (stab->map->colors - i) * CENTSIZE);
335     }
336   }else{
337     i = 0;
338   }
339 //fprintf(stderr, "NEW COLOR CONCAT %u %u %u AT %d\n", comps[0], comps[1], comps[2], i);
340   memcpy(stab->map->table + i * CENTSIZE, comps, RGBSIZE);
341   dtable_to_ctable(stab->map->colors, stab->map->table + i * CENTSIZE);
342   ++stab->map->colors;
343   return stab->map->colors - 1;
344   //return ctable_to_dtable(stab->map->table + i * CENTSIZE);
345 }
346 
347 static void
update_deets(uint32_t rgb,cdetails * deets)348 update_deets(uint32_t rgb, cdetails* deets){
349   unsigned char comps[RGBSIZE];
350   deets->sums[0] += ncpixel_r(rgb);
351   deets->sums[1] += ncpixel_g(rgb);
352   deets->sums[2] += ncpixel_b(rgb);
353   comps[0] = ss(ncpixel_r(rgb), 0xff);
354   comps[1] = ss(ncpixel_g(rgb), 0xff);
355   comps[2] = ss(ncpixel_b(rgb), 0xff);
356   if(deets->count == 0){
357     deets->lo[0] = deets->hi[0] = comps[0];
358     deets->lo[1] = deets->hi[1] = comps[1];
359     deets->lo[2] = deets->hi[2] = comps[2];
360   }else{
361     if(deets->hi[0] < comps[0]){
362       deets->hi[0] = comps[0];
363     }else if(deets->lo[0] > comps[0]){
364       deets->lo[0] = comps[0];
365     }
366     if(deets->hi[1] < comps[1]){
367       deets->hi[1] = comps[1];
368     }else if(deets->lo[1] > comps[1]){
369       deets->lo[1] = comps[1];
370     }
371     if(deets->hi[2] < comps[2]){
372       deets->hi[2] = comps[2];
373     }else if(deets->lo[2] > comps[2]){
374       deets->lo[2] = comps[2];
375     }
376   }
377   ++deets->count;
378 }
379 
380 // goes through the needs_refresh matrix, and damages cells needing refreshing.
sixel_refresh(const ncpile * p,sprixel * s)381 void sixel_refresh(const ncpile* p, sprixel* s){
382   if(s->needs_refresh == NULL){
383     return;
384   }
385   int absy, absx;
386   ncplane_abs_yx(s->n, &absy, &absx);
387   for(unsigned y = 0 ; y < s->dimy ; ++y){
388     const unsigned yy = absy + y;
389     for(unsigned x = 0 ; x < s->dimx ; ++x){
390       unsigned idx = y * s->dimx + x;
391       if(s->needs_refresh[idx]){
392         const unsigned xx = absx + x;
393         if(xx < p->dimx && yy < p->dimy){
394           unsigned ridx = yy * p->dimx + xx;
395           struct crender *r = &p->crender[ridx];
396           r->s.damaged = 1;
397         }
398       }
399     }
400   }
401   free(s->needs_refresh);
402   s->needs_refresh = NULL;
403 }
404 
405 // when we first cross into a new cell, we check its old state, and if it
406 // was transparent, set the rmatrix low. otherwise, set it high. this should
407 // only be called for the first pixel in each cell.
408 static inline void
update_rmatrix(unsigned char * rmatrix,int txyidx,const tament * tam)409 update_rmatrix(unsigned char* rmatrix, int txyidx, const tament* tam){
410   if(rmatrix == NULL){
411     return;
412   }
413   sprixcell_e state = tam[txyidx].state;
414   if(state == SPRIXCELL_TRANSPARENT || state > SPRIXCELL_ANNIHILATED){
415     rmatrix[txyidx] = 0;
416   }else{
417     rmatrix[txyidx] = 1;
418   }
419 }
420 
421 // no mattter the input palette, we can always get a maximum of 64 colors if we
422 // mask at 0xc0 on each component (this partitions each component into 4 chunks,
423 // and 4 * 4 * 4 -> 64). so this will never overflow our color register table
424 // (assumed to have at least 256 registers). at each color, we store a pixel
425 // count, and a sum of all three channels. in addition, we track whether we've
426 // seen at least two colors in the chunk.
427 static inline int
extract_color_table(const uint32_t * data,int linesize,int cols,int leny,int lenx,sixeltable * stab,tament * tam,const blitterargs * bargs)428 extract_color_table(const uint32_t* data, int linesize, int cols,
429                     int leny, int lenx, sixeltable* stab,
430                     tament* tam, const blitterargs* bargs){
431   const int begx = bargs->begx;
432   const int begy = bargs->begy;
433   const int cdimy = bargs->u.pixel.cellpxy;
434   const int cdimx = bargs->u.pixel.cellpxx;
435   unsigned char mask = 0xc0;
436   int pos = 0; // pixel position
437   unsigned char* rmatrix = bargs->u.pixel.spx->needs_refresh;
438   for(int visy = begy ; visy < (begy + leny) ; visy += 6){ // pixel row
439     for(int visx = begx ; visx < (begx + lenx) ; visx += 1){ // pixel column
440       for(int sy = visy ; sy < (begy + leny) && sy < visy + 6 ; ++sy){ // offset within sprixel
441         const uint32_t* rgb = (data + (linesize / 4 * sy) + visx);
442         int txyidx = (sy / cdimy) * cols + (visx / cdimx);
443         // we can't just check if lastidx != txyidx; that's true for each row
444         // of the cell. this will only be true once.
445         bool firstpix = (sy % cdimy == 0 && visx % cdimx == 0);
446         bool lastrow = visy + 6 >= begy + leny;
447         // we do *not* exempt already-wiped pixels from palette creation. once
448         // we're done, we'll call sixel_wipe() on these cells. so they remain
449         // one of SPRIXCELL_ANNIHILATED or SPRIXCELL_ANNIHILATED_TRANS.
450         if(tam[txyidx].state != SPRIXCELL_ANNIHILATED && tam[txyidx].state != SPRIXCELL_ANNIHILATED_TRANS){
451           if(rgba_trans_p(*rgb, bargs->transcolor)){
452             if(firstpix){
453               update_rmatrix(rmatrix, txyidx, tam);
454               tam[txyidx].state = SPRIXCELL_TRANSPARENT;
455             }else if(tam[txyidx].state == SPRIXCELL_OPAQUE_SIXEL){
456               tam[txyidx].state = SPRIXCELL_MIXED_SIXEL;
457             }
458             stab->p2 = SIXEL_P2_TRANS; // even one forces P2=1
459           }else{
460             if(firstpix){
461               update_rmatrix(rmatrix, txyidx, tam);
462               tam[txyidx].state = SPRIXCELL_OPAQUE_SIXEL;
463             }else if(tam[txyidx].state == SPRIXCELL_TRANSPARENT){
464               tam[txyidx].state = SPRIXCELL_MIXED_SIXEL;
465             }
466           }
467         }else{
468 //fprintf(stderr, "TRANS SKIP %d %d %d %d (cell: %d %d)\n", visy, visx, sy, txyidx, sy / cdimy, visx / cdimx);
469           if(rgba_trans_p(*rgb, bargs->transcolor)){
470             if(firstpix){
471               update_rmatrix(rmatrix, txyidx, tam);
472               tam[txyidx].state = SPRIXCELL_ANNIHILATED_TRANS;
473               free(tam[txyidx].auxvector);
474               tam[txyidx].auxvector = NULL;
475             }
476           }else{
477             if(firstpix){
478               update_rmatrix(rmatrix, txyidx, tam);
479               free(tam[txyidx].auxvector);
480               tam[txyidx].auxvector = NULL;
481             }
482             tam[txyidx].state = SPRIXCELL_ANNIHILATED;
483           }
484           stab->p2 = SIXEL_P2_TRANS; // even one forces P2=1
485         }
486         if(lastrow){
487           bool lastcol = visx + 1 >= begx + lenx;
488           if(lastcol){
489             // if we're opaque, we needn't clear the old cell with a glyph
490             if(tam[txyidx].state == SPRIXCELL_OPAQUE_SIXEL){
491               if(rmatrix){
492                 rmatrix[txyidx] = 0;
493               }
494             }
495           }
496         }
497         if(rgba_trans_p(*rgb, bargs->transcolor)){
498           continue;
499         }
500         unsigned char comps[RGBSIZE];
501         break_sixel_comps(comps, *rgb, mask);
502         int c = find_color(stab, comps);
503         if(c < 0){
504 //fprintf(stderr, "FAILED FINDING COLOR AUGH 0x%02x\n", mask);
505           return -1;
506         }
507         stab->map->data[c * stab->map->sixelcount + pos] |= (1u << (sy - visy));
508         update_deets(*rgb, &stab->deets[c]);
509 //fprintf(stderr, "color %d pos %d: 0x%x\n", c, pos, stab->data[c * stab->map->sixelcount + pos]);
510 //fprintf(stderr, " sums: %u %u %u count: %d r/g/b: %u %u %u\n", stab->deets[c].sums[0], stab->deets[c].sums[1], stab->deets[c].sums[2], stab->deets[c].count, ncpixel_r(*rgb), ncpixel_g(*rgb), ncpixel_b(*rgb));
511       }
512       ++pos;
513     }
514   }
515   return 0;
516 }
517 
518 // run through the sixels matching color |src|, going to color |stab->colors|,
519 // keeping those under |r||g||b|, and putting those above it into the new
520 // color. rebuilds both sixel groups and color details.
521 static void
unzip_color(const uint32_t * data,int linesize,int begy,int begx,int leny,int lenx,sixeltable * stab,int src,unsigned char rgb[static3])522 unzip_color(const uint32_t* data, int linesize, int begy, int begx,
523             int leny, int lenx, sixeltable* stab, int src,
524             unsigned char rgb[static 3]){
525   unsigned char* tcrec = stab->map->table + CENTSIZE * stab->map->colors;
526   dtable_to_ctable(stab->map->colors, tcrec);
527   cdetails* targdeets = stab->deets + stab->map->colors;
528   unsigned char* crec = stab->map->table + CENTSIZE * src;
529   int didx = ctable_to_dtable(crec);
530   cdetails* deets = stab->deets + didx;
531   unsigned char* srcsixels = stab->map->data + stab->map->sixelcount * didx;
532   unsigned char* dstsixels = stab->map->data + stab->map->sixelcount * stab->map->colors;
533 //fprintf(stderr, "counts: src: %d dst: %d src: %p dst: %p\n", deets->count, targdeets->count, srcsixels, dstsixels);
534   int sixel = 0;
535   memset(deets, 0, sizeof(*deets));
536   for(int visy = begy ; visy < (begy + leny) ; visy += 6){
537     for(int visx = begx ; visx < (begx + lenx) ; visx += 1, ++sixel){
538       if(srcsixels[sixel]){
539         for(int sy = visy ; sy < (begy + leny) && sy < visy + 6 ; ++sy){
540           if(srcsixels[sixel] & (1u << (sy - visy))){
541             const uint32_t* pixel = (const uint32_t*)(data + (linesize / 4 * sy) + visx);
542             unsigned char comps[RGBSIZE];
543             break_sixel_comps(comps, *pixel, 0xff);
544             if(comps[0] > rgb[0] || comps[1] > rgb[1] || comps[2] > rgb[2]){
545               dstsixels[sixel] |= (1u << (sy - visy));
546               srcsixels[sixel] &= ~(1u << (sy - visy));
547               update_deets(*pixel, targdeets);
548 //fprintf(stderr, "%u/%u/%u comps: [%u/%u/%u]\n", r, g, b, comps[0], comps[1], comps[2]);
549 //fprintf(stderr, "match sixel %d %u %u\n", sixel, srcsixels[sixel], 1u << (sy - visy));
550             }else{
551               update_deets(*pixel, deets);
552             }
553           }
554         }
555       }
556     }
557   }
558 }
559 
560 // relax segment |coloridx|. we must have room for a new color. we find the
561 // biggest component gap, and split our color entry in half there. we know
562 // the elements can't go into any preexisting color entry, so the only
563 // choices are staying where they are, or going to the new one. "unzip" the
564 // sixels from the data table by looking back to the sources and classifying
565 // them in one or the other centry. rebuild our sums, sixels, hi/lo, and
566 // counts as we do so. anaphase, baybee! target always gets the upper range.
567 // returns 1 if we did a refinement, 0 otherwise.
568 static int
refine_color(const uint32_t * data,int linesize,int begy,int begx,int leny,int lenx,sixeltable * stab,int color)569 refine_color(const uint32_t* data, int linesize, int begy, int begx,
570              int leny, int lenx, sixeltable* stab, int color){
571   unsigned char* crec = stab->map->table + CENTSIZE * color;
572   int didx = ctable_to_dtable(crec);
573   cdetails* deets = stab->deets + didx;
574   int rdelt = deets->hi[0] - deets->lo[0];
575   int gdelt = deets->hi[1] - deets->lo[1];
576   int bdelt = deets->hi[2] - deets->lo[2];
577   unsigned char rgbmax[3] = { deets->hi[0], deets->hi[1], deets->hi[2] };
578   if(gdelt >= rdelt && gdelt >= bdelt){ // split on green
579     if(gdelt < 3){
580       return 0;
581     }
582 //fprintf(stderr, "[%d->%d] SPLIT ON GREEN %d %d (pop: %d)\n", color, stab->map->colors, deets->hi[1], deets->lo[1], deets->count);
583     rgbmax[1] = deets->lo[1] + (deets->hi[1] - deets->lo[1]) / 2;
584   }else if(rdelt >= gdelt && rdelt >= bdelt){ // split on red
585     if(rdelt < 3){
586       return 0;
587     }
588 //fprintf(stderr, "[%d->%d] SPLIT ON RED %d %d (pop: %d)\n", color, stab->map->colors, deets->hi[0], deets->lo[0], deets->count);
589     rgbmax[0] = deets->lo[0] + (deets->hi[0] - deets->lo[0]) / 2;
590   }else{ // split on blue
591     if(bdelt < 3){
592       return 0;
593     }
594 //fprintf(stderr, "[%d->%d] SPLIT ON BLUE %d %d (pop: %d)\n", color, stab->map->colors, deets->hi[2], deets->lo[2], deets->count);
595     rgbmax[2] = deets->lo[2] + (deets->hi[2] - deets->lo[2]) / 2;
596   }
597   unzip_color(data, linesize, begy, begx, leny, lenx, stab, color, rgbmax);
598   ++stab->map->colors;
599   return 1;
600 }
601 
602 // relax the details down into free color registers
603 static void
refine_color_table(const uint32_t * data,int linesize,int begy,int begx,int leny,int lenx,sixeltable * stab)604 refine_color_table(const uint32_t* data, int linesize, int begy, int begx,
605                    int leny, int lenx, sixeltable* stab){
606   while(stab->map->colors < stab->colorregs){
607     bool refined = false;
608     int tmpcolors = stab->map->colors; // force us to come back through
609     for(int i = 0 ; i < tmpcolors ; ++i){
610       unsigned char* crec = stab->map->table + CENTSIZE * i;
611       int didx = ctable_to_dtable(crec);
612       cdetails* deets = stab->deets + didx;
613 //fprintf(stderr, "[%d->%d] hi: %d %d %d lo: %d %d %d\n", i, didx, deets->hi[0], deets->hi[1], deets->hi[2], deets->lo[0], deets->lo[1], deets->lo[2]);
614       if(deets->count > leny * lenx / stab->colorregs){
615         if(refine_color(data, linesize, begy, begx, leny, lenx, stab, i)){
616           if(stab->map->colors == stab->colorregs){
617   //fprintf(stderr, "filled table!\n");
618             break;
619           }
620           refined = true;
621         }
622       }
623     }
624     if(!refined){ // no more possible work
625       break;
626     }
627   }
628   // we're full!
629 }
630 
631 // Emit some number of equivalent, subsequent sixels, using sixel RLE. We've
632 // seen the sixel |crle| for |seenrle| columns in a row. |seenrle| must > 0.
633 static int
write_rle(int * printed,int color,fbuf * f,int seenrle,unsigned char crle,int * needclosure)634 write_rle(int* printed, int color, fbuf* f, int seenrle, unsigned char crle,
635           int* needclosure){
636   if(!*printed){
637     if(*needclosure){
638       if(fbuf_putc(f, '$') != 1){
639         return -1;
640       }
641     }
642     if(fbuf_putc(f, '#') != 1){
643       return -1;
644     }
645     if(fbuf_putint(f, color) < 0){
646       return -1;
647     }
648     *printed = 1;
649     *needclosure = 0;
650   }
651   crle += 63;
652   if(seenrle == 2){
653     if(fbuf_putc(f, crle) != 1){
654       return -1;
655     }
656   }else if(seenrle != 1){
657     if(fbuf_putc(f, '!') != 1){
658       return -1;
659     }
660     if(fbuf_putint(f, seenrle) < 0){
661       return -1;
662     }
663   }
664   if(fbuf_putc(f, crle) != 1){
665     return -1;
666   }
667   return 0;
668 }
669 
670 static inline int
write_sixel_intro(fbuf * f,sixel_p2_e p2,int leny,int lenx)671 write_sixel_intro(fbuf* f, sixel_p2_e p2, int leny, int lenx){
672   int r = fbuf_puts(f, "\x1bP0;");
673   if(r < 0){
674     return -1;
675   }
676   int rr = fbuf_putint(f, p2);
677   if(rr < 0){
678     return -1;
679   }
680   r += rr;
681   rr = fbuf_puts(f, ";0q\"1;1;");
682   if(rr < 0){
683     return -1;
684   }
685   r += rr;
686   rr = fbuf_putint(f, lenx);
687   if(rr < 0){
688     return -1;
689   }
690   r += rr;
691   if(fbuf_putc(f, ';') != 1){
692     return -1;
693   }
694   ++r;
695   rr = fbuf_putint(f, leny);
696   if(rr < 0){
697     return -1;
698   }
699   r += rr;
700   return r;
701 }
702 
703 // write a single color register. rc/gc/bc are on [0..100].
704 static inline int
write_sixel_creg(fbuf * f,int idx,int rc,int gc,int bc)705 write_sixel_creg(fbuf* f, int idx, int rc, int gc, int bc){
706   int r = 0;
707   if(fbuf_putc(f, '#') != 1){
708     return -1;
709   }
710   ++r;
711   int rr = fbuf_putint(f, idx);
712   if(rr < 0){
713     return -1;
714   }
715   r += rr;
716   rr = fbuf_puts(f, ";2;");
717   if(rr < 0){
718     return -1;
719   }
720   r += rr;
721   rr = fbuf_putint(f, rc);
722   if(rr < 0){
723     return -1;
724   }
725   r += rr;
726   if(fbuf_putc(f, ';') != 1){
727     return -1;
728   }
729   ++r;
730   rr = fbuf_putint(f, gc);
731   if(rr < 0){
732     return -1;
733   }
734   r += rr;
735   if(fbuf_putc(f, ';') != 1){
736     return -1;
737   }
738   ++r;
739   rr = fbuf_putint(f, bc);
740   if(rr < 0){
741     return -1;
742   }
743   r += rr;
744   return r;
745 }
746 
747 // write the escape which opens a Sixel, plus the palette table. returns the
748 // number of bytes written, so that this header can be directly copied in
749 // future reencodings. |leny| and |lenx| are output pixel geometry.
750 // returns the number of bytes written, so it can be stored at *parse_start.
751 static int
write_sixel_header(fbuf * f,int leny,int lenx,const sixeltable * stab,sixel_p2_e p2)752 write_sixel_header(fbuf* f, int leny, int lenx, const sixeltable* stab, sixel_p2_e p2){
753   if(leny % 6){
754     return -1;
755   }
756   // Set Raster Attributes - pan/pad=1 (pixel aspect ratio), Ph=lenx, Pv=leny
757   int r = write_sixel_intro(f, p2, leny, lenx);
758   if(r < 0){
759     return -1;
760   }
761   for(int i = 0 ; i < stab->map->colors ; ++i){
762     const unsigned char* rgb = stab->map->table + i * CENTSIZE;
763     int idx = ctable_to_dtable(rgb);
764     int count = stab->deets[idx].count;
765 //fprintf(stderr, "RGB: %3u(%d) %3u(%d) %3u(%d) DT: %d SUMS: %3ld %3ld %3ld COUNT: %d\n", rgb[0], ss(rgb[0], 0xff), rgb[1], ss(rgb[1], 0xff), rgb[2], ss(rgb[2], 0xff), idx, stab->deets[idx].sums[0] / count * 100 / 255, stab->deets[idx].sums[1] / count * 100 / 255, stab->deets[idx].sums[2] / count * 100 / 255, count);
766     //fprintf(fp, "#%d;2;%u;%u;%u", i, rgb[0], rgb[1], rgb[2]);
767     // we emit the average of the actual sums rather than the RGB clustering
768     // point, as it can be (and usually is) much more accurate.
769     int rr = write_sixel_creg(f, i, (stab->deets[idx].sums[0] * 100 / count / 255),
770                               (stab->deets[idx].sums[1] * 100 / count / 255),
771                               (stab->deets[idx].sums[2] * 100 / count / 255));
772     if(rr < 0){
773       return -1;
774     }
775     r += rr;
776   }
777   return r;
778 }
779 
780 static int
write_sixel_payload(fbuf * f,int lenx,const sixelmap * map)781 write_sixel_payload(fbuf* f, int lenx, const sixelmap* map){
782   int p = 0;
783   while(p < map->sixelcount){
784     int needclosure = 0;
785     for(int i = 0 ; i < map->colors ; ++i){
786       int seenrle = 0; // number of repetitions
787       unsigned char crle = 0; // character being repeated
788       int idx = ctable_to_dtable(map->table + i * CENTSIZE);
789       int printed = 0;
790       for(int m = p ; m < map->sixelcount && m < p + lenx ; ++m){
791 //fprintf(stderr, "%d ", idx * map->sixelcount + m);
792 //fputc(map->data[idx * map->sixelcount + m] + 63, stderr);
793         if(seenrle){
794           if(map->data[idx * map->sixelcount + m] == crle){
795             ++seenrle;
796           }else{
797             if(write_rle(&printed, i, f, seenrle, crle, &needclosure)){
798               return -1;
799             }
800             seenrle = 1;
801             crle = map->data[idx * map->sixelcount + m];
802           }
803         }else{
804           seenrle = 1;
805           crle = map->data[idx * map->sixelcount + m];
806         }
807       }
808       if(crle){
809         if(write_rle(&printed, i, f, seenrle, crle, &needclosure)){
810           return -1;
811         }
812       }
813       needclosure = needclosure | printed;
814     }
815     if(p + lenx < map->sixelcount){
816       if(fbuf_putc(f, '-') != 1){
817         return -1;
818       }
819     }
820     p += lenx;
821   }
822   if(fbuf_puts(f, "\e\\") < 0){
823     return -1;
824   }
825   return 0;
826 }
827 
828 // emit the sixel in its entirety, plus escapes to start and end pixel mode.
829 // only called the first time we encode; after that, the palette remains
830 // constant, and is simply copied. fclose()s |fp| on success. |outx| and |outy|
831 // are output geometry.
832 static int
write_sixel(fbuf * f,int outy,int outx,const sixeltable * stab,int * parse_start,sixel_p2_e p2)833 write_sixel(fbuf* f, int outy, int outx, const sixeltable* stab,
834             int* parse_start, sixel_p2_e p2){
835   *parse_start = write_sixel_header(f, outy, outx, stab, p2);
836   if(*parse_start < 0){
837     return -1;
838   }
839   if(write_sixel_payload(f, outx, stab->map) < 0){
840     return -1;
841   }
842   return 0;
843 }
844 
845 // once per render cycle (if needed), make the actual payload match the TAM. we
846 // don't do these one at a time due to the complex (expensive) process involved
847 // in regenerating a sixel (we can't easily do it in-place). anything newly
848 // ANNIHILATED (state is ANNIHILATED, but no auxvec present) is dropped from
849 // the payload, and an auxvec is generated. anything newly restored (state is
850 // OPAQUE_SIXEL or MIXED_SIXEL, but an auxvec is present) is restored to the
851 // payload, and the auxvec is freed. none of this takes effect until the sixel
852 // is redrawn, and annihilated sprixcells still require a glyph to be emitted.
853 static inline int
sixel_reblit(sprixel * s)854 sixel_reblit(sprixel* s){
855   fbuf f;
856   if(fbuf_init(&f)){
857     return -1;
858   }
859   if(fbuf_putn(&f, s->glyph.buf, s->parse_start) != s->parse_start){
860     fbuf_free(&f);
861     return -1;
862   }
863   if(write_sixel_payload(&f, s->pixx, s->smap) < 0){
864     fbuf_free(&f);
865     return -1;
866   }
867   fbuf_free(&s->glyph);
868   // FIXME update P2 if necessary
869   memcpy(&s->glyph, &f, sizeof(f));
870   return 0;
871 }
872 
873 // Sixel blitter. Sixels are stacks 6 pixels high, and 1 pixel wide. RGB colors
874 // are programmed as a set of registers, which are then referenced by the
875 // stacks. There is also a RLE component, handled in rasterization.
876 // A pixel block is indicated by setting cell_pixels_p(). |leny| and |lenx| are
877 // scaled geometry in pixels. We calculate output geometry herein, and supply
878 // transparent filler input for any missing rows.
879 static inline int
sixel_blit_inner(int leny,int lenx,sixeltable * stab,const blitterargs * bargs,tament * tam)880 sixel_blit_inner(int leny, int lenx, sixeltable* stab, const blitterargs* bargs, tament* tam){
881   fbuf f;
882   if(fbuf_init(&f)){
883     return -1;
884   }
885   sprixel* s = bargs->u.pixel.spx;
886   const int cellpxy = bargs->u.pixel.cellpxy;
887   const int cellpxx = bargs->u.pixel.cellpxx;
888   int parse_start = 0;
889   int outy = leny;
890   if(leny % 6){
891     outy += 6 - (leny % 6);
892     stab->p2 = SIXEL_P2_TRANS;
893   }
894   // calls fclose() on success
895   if(write_sixel(&f, outy, lenx, stab, &parse_start, stab->p2)){
896     fbuf_free(&f);
897     return -1;
898   }
899   scrub_tam_boundaries(tam, outy, lenx, cellpxy, cellpxx);
900   // take ownership of buf on success
901   if(plane_blit_sixel(s, &f, outy, lenx, parse_start, tam, SPRIXEL_INVALIDATED) < 0){
902     fbuf_free(&f);
903     return -1;
904   }
905   // we're keeping the buf remnants
906   sixelmap_trim(stab->map);
907   s->smap = stab->map;
908   return 1;
909 }
910 
911 // |leny| and |lenx| are the scaled output geometry. we take |leny| up to the
912 // nearest multiple of six greater than or equal to |leny|.
sixel_blit(ncplane * n,int linesize,const void * data,int leny,int lenx,const blitterargs * bargs)913 int sixel_blit(ncplane* n, int linesize, const void* data, int leny, int lenx,
914                const blitterargs* bargs){
915   int colorregs = bargs->u.pixel.colorregs;
916   if(colorregs > 256){
917     colorregs = 256;
918   }
919   assert(colorregs >= 64);
920   sixeltable stable = {
921     .map = sixelmap_create(colorregs, leny - bargs->begy, lenx - bargs->begx),
922     .deets = malloc(colorregs * sizeof(cdetails)),
923     .colorregs = colorregs,
924     .p2 = SIXEL_P2_ALLOPAQUE,
925   };
926   if(stable.deets == NULL || stable.map == NULL){
927     sixelmap_free(stable.map);
928     free(stable.deets);
929     return -1;
930   }
931   // stable.table doesn't need initializing; we start from the bottom
932   memset(stable.deets, 0, sizeof(*stable.deets) * colorregs);
933   int cols = bargs->u.pixel.spx->dimx;
934   int rows = bargs->u.pixel.spx->dimy;
935   typeof(bargs->u.pixel.spx->needs_refresh) rmatrix;
936   rmatrix = malloc(sizeof(*rmatrix) * rows * cols);
937   if(rmatrix == NULL){
938     sixelmap_free(stable.map);
939     free(stable.deets);
940     return -1;
941   }
942   bargs->u.pixel.spx->needs_refresh = rmatrix;
943   assert(n->tam);
944   if(extract_color_table(data, linesize, cols, leny, lenx, &stable, n->tam, bargs)){
945     free(bargs->u.pixel.spx->needs_refresh);
946     sixelmap_free(stable.map);
947     free(stable.deets);
948     return -1;
949   }
950   refine_color_table(data, linesize, bargs->begy, bargs->begx, leny, lenx, &stable);
951   // takes ownership of sixelmap on success
952   int r = sixel_blit_inner(leny, lenx, &stable, bargs, n->tam);
953   if(r < 0){
954     sixelmap_free(stable.map);
955   }
956   free(stable.deets);
957   scrub_color_table(bargs->u.pixel.spx);
958   return r;
959 }
960 
961 // to destroy a sixel, we damage all cells underneath it. we might not have
962 // to, though, if we've got a new sixel ready to go where the old sixel was
963 // (though we'll still need to if the new sprixcell not opaque, and the
964 // old and new sprixcell are different in any transparent pixel).
sixel_scrub(const ncpile * p,sprixel * s)965 int sixel_scrub(const ncpile* p, sprixel* s){
966   loginfo("%d state %d at %d/%d (%d/%d)\n", s->id, s->invalidated, s->movedfromy, s->movedfromx, s->dimy, s->dimx);
967   int starty = s->movedfromy;
968   int startx = s->movedfromx;
969   for(int yy = starty ; yy < starty + (int)s->dimy && yy < (int)p->dimy ; ++yy){
970     for(int xx = startx ; xx < startx + (int)s->dimx && xx < (int)p->dimx ; ++xx){
971       int ridx = yy * p->dimx + xx;
972       struct crender *r = &p->crender[ridx];
973       if(!s->n){
974         // need this to damage cells underneath a sprixel we're removing
975         r->s.damaged = 1;
976         continue;
977       }
978       sprixel* trues = r->sprixel ? r->sprixel : s;
979       if(yy >= (int)trues->n->leny || yy - trues->n->absy < 0){
980         r->s.damaged = 1;
981         continue;
982       }
983       if(xx >= (int)trues->n->lenx || xx - trues->n->absx < 0){
984         r->s.damaged = 1;
985         continue;
986       }
987       sprixcell_e state = sprixel_state(trues, yy, xx);
988 //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);
989       if(state == SPRIXCELL_TRANSPARENT || state == SPRIXCELL_MIXED_SIXEL){
990         r->s.damaged = 1;
991       }else if(s->invalidated == SPRIXEL_MOVED){
992         // ideally, we wouldn't damage our annihilated sprixcells, but if
993         // we're being annihilated only during this cycle, we need to go
994         // ahead and damage it.
995         r->s.damaged = 1;
996       }
997     }
998   }
999   return 1;
1000 }
1001 
1002 // returns the number of bytes written
sixel_draw(const tinfo * ti,const ncpile * p,sprixel * s,fbuf * f,int yoff,int xoff)1003 int sixel_draw(const tinfo* ti, const ncpile* p, sprixel* s, fbuf* f,
1004                int yoff, int xoff){
1005   (void)ti;
1006   // if we've wiped or rebuilt any cells, effect those changes now, or else
1007   // we'll get flicker when we move to the new location.
1008   if(s->wipes_outstanding){
1009     if(sixel_reblit(s)){
1010       return -1;
1011     }
1012     s->wipes_outstanding = false;
1013   }
1014   if(p){
1015     const int targy = s->n->absy + yoff;
1016     const int targx = s->n->absx + xoff;
1017     if(goto_location(p->nc, f, targy, targx, NULL)){
1018       return -1;
1019     }
1020     if(s->invalidated == SPRIXEL_MOVED){
1021       for(int yy = s->movedfromy ; yy < s->movedfromy + (int)s->dimy && yy < (int)p->dimy ; ++yy){
1022         if(yy < 0){
1023           continue;
1024         }
1025         for(int xx = s->movedfromx ; xx < s->movedfromx + (int)s->dimx && xx < (int)p->dimx ; ++xx){
1026           if(xx < 0){
1027             continue;
1028           }
1029           struct crender *r = &p->crender[yy * p->dimx + xx];
1030           if(!r->sprixel || sprixel_state(r->sprixel, yy, xx) != SPRIXCELL_OPAQUE_SIXEL){
1031             r->s.damaged = 1;
1032           }
1033         }
1034       }
1035     }
1036   }
1037   if(fbuf_putn(f, s->glyph.buf, s->glyph.used) < 0){
1038     return -1;
1039   }
1040   s->invalidated = SPRIXEL_QUIESCENT;
1041   return s->glyph.used;
1042 }
1043 
1044 // private mode 80 (DECSDM) manages "Sixel Scrolling Mode" vs "Sixel Display
1045 // Mode". when 80 is enabled (i.e. DECSDM mode), images are displayed at the
1046 // upper left, and clipped to the window. we don't want either of those things
1047 // to happen, so we explicitly disable DECSDM.
1048 // private mode 8452 places the cursor at the end of a sixel when it's
1049 //  emitted. we don't need this for rendered mode, but we do want it for
1050 //  direct mode. it causes us no problems, so always set it.
sixel_init(int fd)1051 int sixel_init(int fd){
1052   return tty_emit("\e[?80l\e[?8452h", fd);
1053 }
1054 
sixel_init_inverted(int fd)1055 int sixel_init_inverted(int fd){
1056   // some terminals, at some versions, invert the sense of DECSDM. for those,
1057   // we must use 80h rather than the correct 80l. this grows out of a
1058   // misunderstanding in XTerm through patchlevel 368, which was widely
1059   // copied into other terminals.
1060   return tty_emit("\e[?80h\e[?8452h", fd);
1061 }
1062 
1063 // only called for cells in SPRIXCELL_ANNIHILATED[_TRANS]. just post to
1064 // wipes_outstanding, so the Sixel gets regenerated the next render cycle,
1065 // just like wiping. this is necessary due to the complex nature of
1066 // modifying a Sixel -- we want to do them all in one batch.
sixel_rebuild(sprixel * s,int ycell,int xcell,uint8_t * auxvec)1067 int sixel_rebuild(sprixel* s, int ycell, int xcell, uint8_t* auxvec){
1068   s->wipes_outstanding = true;
1069   sixelmap* smap = s->smap;
1070   const int cellpxx = ncplane_pile(s->n)->cellpxx;
1071   const int cellpxy = ncplane_pile(s->n)->cellpxy;
1072   const int startx = xcell * cellpxx;
1073   const int starty = ycell * cellpxy;
1074   int endx = ((xcell + 1) * cellpxx) - 1;
1075   if(endx > s->pixx){
1076     endx = s->pixx;
1077   }
1078   int endy = ((ycell + 1) * cellpxy) - 1;
1079   if(endy > s->pixy){
1080     endy = s->pixy;
1081   }
1082   int transparent = 0;
1083 //fprintf(stderr, "%d/%d start: %d/%d end: %d/%d bands: %d-%d\n", ycell, xcell, starty, startx, endy, endx, starty / 6, endy / 6);
1084   for(int x = startx ; x <= endx ; ++x){
1085     for(int y = starty ; y <= endy ; ++y){
1086       int auxvecidx = (y - starty) * cellpxx + (x - startx);
1087       int trans = auxvec[cellpxx * cellpxy + auxvecidx];
1088       if(!trans){
1089         int color = auxvec[auxvecidx];
1090         int didx = ctable_to_dtable(smap->table + color * CENTSIZE);
1091         int coff = smap->sixelcount * didx;
1092         int band = y / 6;
1093         int boff = coff + band * s->pixx;
1094         int xoff = boff + x;
1095 //fprintf(stderr, "DIDX: %d %d/%d band: %d coff: %d boff: %d rebuild %d/%d with color %d from %d %p xoff: %d\n", didx, ycell, xcell, band, coff, boff, y, x, color, auxvecidx, auxvec, xoff);
1096         s->smap->data[xoff] |= (1u << (y % 6));
1097       }else{
1098         ++transparent;
1099       }
1100     }
1101   }
1102   sprixcell_e newstate;
1103   if(transparent == cellpxx * cellpxy){
1104     newstate = SPRIXCELL_TRANSPARENT;
1105   }else if(transparent){
1106     newstate = SPRIXCELL_MIXED_SIXEL;
1107   }else{
1108     newstate = SPRIXCELL_OPAQUE_SIXEL;
1109   }
1110   s->n->tam[s->dimx * ycell + xcell].state = newstate;
1111   return 1;
1112 }
1113 
sixel_shutdown(fbuf * f)1114 int sixel_shutdown(fbuf* f){
1115   (void)f;
1116   // no way to know what the state was before; we ought use XTSAVE/XTRESTORE
1117   return 0;
1118 }
1119 
sixel_trans_auxvec(const ncpile * p)1120 uint8_t* sixel_trans_auxvec(const ncpile* p){
1121   const size_t slen = 2 * p->cellpxy * p->cellpxx;
1122   uint8_t* a = malloc(slen);
1123   if(a){
1124     memset(a, 0, slen);
1125   }
1126   return a;
1127 }
1128