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