Notcurses 3.0.13
a blingful library for TUIs and character graphics
Loading...
Searching...
No Matches
kitty.c
Go to the documentation of this file.
1#include "internal.h"
2#include "base64.h"
3#ifdef USE_DEFLATE
4#include <libdeflate.h>
5#else
6#include <zlib.h>
7#endif
8
9// Kitty has its own bitmap graphics protocol, rather superior to DEC Sixel.
10// A header is written with various directives, followed by a number of
11// chunks. Each chunk carries up to 4096B of base64-encoded pixels. Bitmaps
12// can be ordered on a z-axis, with text at a logical z=0. A bitmap at a
13// positive coordinate will be drawn above text; a negative coordinate will
14// be drawn below text. It is not possible for a single bitmap to be under
15// some text and above other text; since we need both, we draw at a positive
16// coordinate (above all text), and cut out sections by setting their alpha
17// values to 0. We thus require RGBA, meaning 768 pixels per 4096B chunk
18// (768pix * 4Bpp * 4/3 base64 overhead == 4096B).
19//
20// 0.20.0 introduced an animation protocol which drastically reduces the
21// bandwidth necessary for wipe-and-rebuild. 0.21.1 improved it further.
22// we thus have three strategies:
23//
24// pre-0.20.0: keep an auxvec for each wiped cell, with a byte per pixel.
25// on wipe, copy the alphas into the auxvec, and set them to 0 in the
26// encoded graphic. on rebuild, rewrite the alphas from the auxvec. both
27// operations require delicate edits directly to the encoded form. the
28// graphic is updated by completely retransmitting it.
29//
30// 0.20.0: we make a copy of the RGBA data, populating all auxvecs upon
31// blit. to wipe, we generate a cell's woth of 0s, and merge them into
32// the existing image. to rebuild, we merge the original data into the
33// existing image. this cuts down on bandwidth--unchanged cells are not
34// retransmitted. it does require a fairly expensive copy of the source,
35// even though we might never use it.
36//
37// 0.21.1+: our auxvecs are now a single word -- the sprixcell state prior
38// to annihilation. we never need retransmit the original RGBA on
39// restore, as we can instead use composition with reflection.
40//
41// if a graphic needs be moved, we can move it with a control operation,
42// rather than erasing it and redrawing it manually.
43//
44// It has some interesting features of which we do not yet take advantage:
45// * in-terminal scaling of image data (we prescale)
46// * subregion display of a transmitted bitmap
47//
48// https://sw.kovidgoyal.net/kitty/graphics-protocol.html
49
50// get the first alpha from the triplet
51static inline uint8_t
52triplet_alpha1(const char* triplet){
53 uint8_t c1 = b64idx(triplet[0x4]);
54 uint8_t c2 = b64idx(triplet[0x5]);
55 return (c1 << 2u) | ((c2 & 0x30) >> 4);
56}
57
58static inline uint8_t
59triplet_alpha2(const char* triplet){
60 uint8_t c1 = b64idx(triplet[0x9]);
61 uint8_t c2 = b64idx(triplet[0xA]);
62 return ((c1 & 0xf) << 4u) | ((c2 & 0x3c) >> 2);
63}
64
65static inline uint8_t
66triplet_alpha3(const char* triplet){
67 uint8_t c1 = b64idx(triplet[0xE]);
68 uint8_t c2 = b64idx(triplet[0xF]);
69 return ((c1 & 0x3) << 6u) | c2;
70}
71
72// null out part of a triplet (a triplet is 3 pixels, which map to 12 bytes, which map to
73// 16 bytes when base64 encoded). skip the initial |skip| pixels, and null out a maximum
74// of |max| pixels after that. returns the number of pixels nulled out. |max| must be
75// positive. |skip| must be non-negative, and less than 3. |pleft| is the number of pixels
76// available in the chunk. the RGB is 24 bits, and thus 4 base64 bytes, but
77// unfortunately don't always start on a byte boundary =[.
78// 0: R1(0..5)
79// 1: R1(6..7), G1(0..3)
80// 2: G1(4..7), B1(0..1)
81// 3: B1(2..7)
82// 4: A1(0..5)
83// 5: A1(6..7), R2(0..3)
84// 6: R2(4..7), G2(0..1)
85// 7: G2(2..7)
86// 8: B2(0..5)
87// 9: B2(6..7), A2(0..3)
88// A: A2(4..7), R3(0..1)
89// B: R3(2..7)
90// C: G3(0..5)
91// D: G3(6..7), B3(0..3)
92// E: B3(4..7), A3(0..1)
93// F: A3(2..7)
94// so we will only ever zero out bytes 4, 5, 9, A, E, and F
95static inline int
96kitty_null(char* triplet, int skip, int max, int pleft, uint8_t* auxvec){
97//fprintf(stderr, "SKIP/MAX/PLEFT %d/%d/%d\n", skip, max, pleft);
98 if(pleft > 3){
99 pleft = 3;
100 }
101 if(max + skip > pleft){
102 max = pleft - skip;
103 }
104//fprintf(stderr, "alpha-nulling %d after %d\n", max, skip);
105 if(skip == 0){
106 auxvec[0] = triplet_alpha1(triplet);
107 triplet[0x4] = b64subs[0];
108 triplet[0x5] = b64subs[b64idx(triplet[0x5]) & 0xf];
109 if(max > 1){
110 auxvec[1] = triplet_alpha2(triplet);
111 triplet[0x9] = b64subs[b64idx(triplet[0x9]) & 0x30];
112 triplet[0xA] = b64subs[b64idx(triplet[0xA]) & 0x3];
113 }
114 if(max == 3){
115 auxvec[2] = triplet_alpha3(triplet);
116 triplet[0xE] = b64subs[b64idx(triplet[0xE]) & 0x3c];
117 triplet[0xF] = b64subs[0];
118 }
119 }else if(skip == 1){
120 auxvec[0] = triplet_alpha2(triplet);
121 triplet[0x9] = b64subs[b64idx(triplet[0x9]) & 0x30];
122 triplet[0xA] = b64subs[b64idx(triplet[0xA]) & 0x3];
123 if(max == 2){
124 auxvec[1] = triplet_alpha3(triplet);
125 triplet[0xE] = b64subs[b64idx(triplet[0xE]) & 0x3c];
126 triplet[0xF] = b64subs[0];
127 }
128 }else{ // skip == 2
129 auxvec[0] = triplet_alpha3(triplet);
130 triplet[0xE] = b64subs[b64idx(triplet[0xE]) & 0x3c];
131 triplet[0xF] = b64subs[0];
132 }
133 return max;
134}
135
136// restore part of a triplet (a triplet is 3 pixels, which map to 12 bytes,
137// which map to 16 bytes when base64 encoded). skip the initial |skip| pixels,
138// and restore a maximum of |max| pixels after that. returns the number of
139// pixels restored. |max| must be positive. |skip| must be non-negative, and
140// less than 3. |pleft| is the number of pixels available in the chunk.
141// |state| is set to MIXED if we find transparent pixels.
142static inline int
143kitty_restore(char* triplet, int skip, int max, int pleft,
144 const uint8_t* auxvec, sprixcell_e* state){
145//fprintf(stderr, "SKIP/MAX/PLEFT %d/%d/%d auxvec %p\n", skip, max, pleft, auxvec);
146 if(pleft > 3){
147 pleft = 3;
148 }
149 if(max + skip > pleft){
150 max = pleft - skip;
151 }
152 if(skip == 0){
153 int a = auxvec[0];
154 if(a == 0){
155 *state = SPRIXCELL_MIXED_KITTY;
156 }
157 triplet[0x4] = b64subs[(a & 0xfc) >> 2];
158 triplet[0x5] = b64subs[((a & 0x3) << 4) | (b64idx(triplet[0x5]) & 0xf)];
159 if(max > 1){
160 a = auxvec[1];
161 if(a == 0){
162 *state = SPRIXCELL_MIXED_KITTY;
163 }
164 triplet[0x9] = b64subs[(b64idx(triplet[0x9]) & 0x30) | ((a & 0xf0) >> 4)];
165 triplet[0xA] = b64subs[((a & 0xf) << 2) | (b64idx(triplet[0xA]) & 0x3)];
166 }
167 if(max == 3){
168 a = auxvec[2];
169 if(a == 0){
170 *state = SPRIXCELL_MIXED_KITTY;
171 }
172 triplet[0xE] = b64subs[((a & 0xc0) >> 6) | (b64idx(triplet[0xE]) & 0x3c)];
173 triplet[0xF] = b64subs[(a & 0x3f)];
174 }
175 }else if(skip == 1){
176 int a = auxvec[0];
177 if(a == 0){
178 *state = SPRIXCELL_MIXED_KITTY;
179 }
180 triplet[0x9] = b64subs[(b64idx(triplet[0x9]) & 0x30) | ((a & 0xf0) >> 4)];
181 triplet[0xA] = b64subs[((a & 0xf) << 2) | (b64idx(triplet[0xA]) & 0x3)];
182 if(max == 2){
183 a = auxvec[1];
184 if(a == 0){
185 *state = SPRIXCELL_MIXED_KITTY;
186 }
187 triplet[0xE] = b64subs[((a & 0xc0) >> 6) | (b64idx(triplet[0xE]) & 0x3c)];
188 triplet[0xF] = b64subs[(a & 0x3f)];
189 }
190 }else{ // skip == 2
191 int a = auxvec[0];
192 if(a == 0){
193 *state = SPRIXCELL_MIXED_KITTY;
194 }
195 triplet[0xE] = b64subs[((a & 0xc0) >> 6) | (b64idx(triplet[0xE]) & 0x3c)];
196 triplet[0xF] = b64subs[(a & 0x3f)];
197 }
198 return max;
199}
200
201// if there is no mstreamfp open, create one, using glyph and glyphlen as the
202// base. we're blowing away the glyph.
203static int
204init_sprixel_animation(sprixel* s){
205 if(s->animating){
206 return 0;
207 }
208 fbuf_free(&s->glyph);
209 if(fbuf_init(&s->glyph)){
210 return -1;
211 }
212 s->animating = true;
213 return 0;
214}
215
216#define RGBA_MAXLEN 768 // 768 base64-encoded pixels in 4096 bytes
217// restore an annihilated sprixcell by copying the alpha values from the
218// auxiliary vector back into the actual data. we then free the auxvector.
219int kitty_rebuild(sprixel* s, int ycell, int xcell, uint8_t* auxvec){
220 const int totalpixels = s->pixy * s->pixx;
221 const int xpixels = ncplane_pile(s->n)->cellpxx;
222 const int ypixels = ncplane_pile(s->n)->cellpxy;
223 int targx = xpixels;
224 if((xcell + 1) * xpixels > s->pixx){
225 targx = s->pixx - xcell * xpixels;
226 }
227 int targy = ypixels;
228 if((ycell + 1) * ypixels > s->pixy){
229 targy = s->pixy - ycell * ypixels;
230 }
231 char* c = (char*)s->glyph.buf + s->parse_start;
232 int nextpixel = (s->pixx * ycell * ypixels) + (xpixels * xcell);
233 int thisrow = targx;
234 int chunkedhandled = 0;
236 const int chunks = totalpixels / RGBA_MAXLEN + !!(totalpixels % RGBA_MAXLEN);
237 int auxvecidx = 0;
238 while(targy && chunkedhandled < chunks){ // need to null out |targy| rows of |targx| pixels, track with |thisrow|
239 int inchunk = totalpixels - chunkedhandled * RGBA_MAXLEN;
240 if(inchunk > RGBA_MAXLEN){
241 inchunk = RGBA_MAXLEN;
242 }
243 const int curpixel = chunkedhandled * RGBA_MAXLEN;
244 // a full chunk is 4096 + 2 + 7 (5005)
245 while(nextpixel - curpixel < RGBA_MAXLEN && thisrow){
246 // our next pixel is within this chunk. find the pixel offset of the
247 // first pixel (within the chunk).
248 int pixoffset = nextpixel - curpixel;
249 int triples = pixoffset / 3;
250 int tripbytes = triples * 16;
251 int tripskip = pixoffset - triples * 3;
252 // we start within a 16-byte chunk |tripbytes| into the chunk. determine
253 // the number of bits.
254//fprintf(stderr, "pixoffset: %d next: %d tripbytes: %d tripskip: %d thisrow: %d\n", pixoffset, nextpixel, tripbytes, tripskip, thisrow);
255 // the maximum number of pixels we can convert is the minimum of the
256 // pixels remaining in the target row, and the pixels left in the chunk.
257//fprintf(stderr, "inchunk: %d total: %d triples: %d\n", inchunk, totalpixels, triples);
258 int chomped = kitty_restore(c + tripbytes, tripskip, thisrow,
259 inchunk - triples * 3, auxvec + auxvecidx,
260 &state);
261 assert(chomped >= 0);
262 auxvecidx += chomped;
263 thisrow -= chomped;
264//fprintf(stderr, "POSTCHIMP CHOMP: %d pixoffset: %d next: %d tripbytes: %d tripskip: %d thisrow: %d\n", chomped, pixoffset, nextpixel, tripbytes, tripskip, thisrow);
265 if(thisrow == 0){
266//fprintf(stderr, "CLEARED ROW, TARGY: %d\n", targy - 1);
267 if(--targy == 0){
268 s->n->tam[s->dimx * ycell + xcell].state = state;
270 return 1;
271 }
272 thisrow = targx;
273//fprintf(stderr, "BUMP IT: %d %d %d %d\n", nextpixel, s->pixx, targx, chomped);
274 nextpixel += s->pixx - targx + chomped;
275 }else{
276 nextpixel += chomped;
277 }
278 }
279 c += RGBA_MAXLEN * 4 * 4 / 3; // 4bpp * 4/3 for base64, 4096b per chunk
280 c += 8; // new chunk header
281 ++chunkedhandled;
282//fprintf(stderr, "LOOKING NOW AT %u [%s]\n", c - s->glyph, c);
283 while(*c != ';'){
284 ++c;
285 }
286 ++c;
287 }
288 return -1;
289}
290
291// does this auxvec correspond to a sprixcell which was nulled out during the
292// blitting of the frame (can only happen with a multiframe that's seen some
293// wiping)?
294static inline unsigned
295kitty_anim_auxvec_blitsource_p(const sprixel* s, const uint8_t* auxvec){
296 size_t off = ncplane_pile(s->n)->cellpxy * ncplane_pile(s->n)->cellpxx * 4;
297 if(auxvec[off]){
298 return 1;
299 }
300 return 0;
301}
302
303// an animation auxvec requires storing all the pixel data for the cell,
304// instead of just the alpha channel. pass the start of the RGBA to be
305// copied, and the rowstride. dimy and dimx are the source image's total
306// size in pixels. posy and posx are the origin of the cell to be copied,
307// again in pixels. data is the image source. around the edges, we might
308// get truncated regions. we also need to store a final byte indicating
309// whether the null write originated in blitting or wiping, as that affects
310// our rebuild animation.
311static inline void*
312kitty_anim_auxvec(int dimy, int dimx, int posy, int posx,
313 int cellpxy, int cellpxx, const uint32_t* data,
314 int rowstride, uint8_t* existing, uint32_t transcolor){
315 const size_t slen = 4 * cellpxy * cellpxx + 1;
316 uint32_t* a = existing ? existing : malloc(slen);
317 if(a){
318 for(int y = posy ; y < posy + cellpxy && y < dimy ; ++y){
319 int pixels = cellpxx;
320 if(pixels + posx > dimx){
321 pixels = dimx - posx;
322 }
323 /*logtrace("copying %d (%d) from %p to %p %d/%d",
324 pixels * 4, y,
325 data + y * (rowstride / 4) + posx,
326 a + (y - posy) * (pixels * 4),
327 posy / cellpxy, posx / cellpxx);*/
328 memcpy(a + (y - posy) * pixels, data + y * (rowstride / 4) + posx, pixels * 4);
329 for(int x = posx ; x < posx + cellpxx && x < dimx ; ++x){
330 uint32_t pixel = data[y * (rowstride / 4) + x];
331 if(rgba_trans_p(pixel, transcolor)){
332 uint32_t* ap = a + (y - posy) * pixels + (x - posx);
333 ncpixel_set_a(ap, 0);
334 }
335 }
336 }
337 ((uint8_t*)a)[slen - 1] = 0; // reset blitsource ownership
338 }
339 return a;
340}
341
342uint8_t* kitty_trans_auxvec(const ncpile* p){
343 const size_t slen = p->cellpxy * p->cellpxx;
344 uint8_t* a = malloc(slen);
345 if(a){
346 memset(a, 0, slen);
347 }
348 return a;
349}
350
351// just dump the wipe into the fbuf -- don't manipulate any state. used both
352// by the wipe proper, and when blitting a new frame with annihilations.
353static int
354kitty_blit_wipe_selfref(sprixel* s, fbuf* f, int ycell, int xcell){
355 const int cellpxx = ncplane_pile(s->n)->cellpxx;
356 const int cellpxy = ncplane_pile(s->n)->cellpxy;
357 if(fbuf_printf(f, "\x1b_Ga=f,x=%d,y=%d,s=%d,v=%d,i=%d,X=1,r=2,c=1,q=2;",
358 xcell * cellpxx, ycell * cellpxy, cellpxx, cellpxy, s->id) < 0){
359 return -1;
360 }
361 // FIXME ought be smaller around the fringes!
362 int totalp = cellpxy * cellpxx;
363 // FIXME preserve so long as cellpixel geom stays constant?
364 #define TRINULLALPHA "AAAAAAAAAAAAAAAA"
365 for(int p = 0 ; p + 3 <= totalp ; p += 3){
366 if(fbuf_putn(f, TRINULLALPHA, strlen(TRINULLALPHA)) < 0){
367 return -1;
368 }
369 }
370 #undef TRINULLALPHA
371 if(totalp % 3 == 1){
372 #define UNUMNULLALPHA "AAAAAA=="
373 if(fbuf_putn(f, UNUMNULLALPHA, strlen(UNUMNULLALPHA)) < 0){
374 return -1;
375 }
376 #undef UNUMNULLALPHA
377 }else if(totalp % 3 == 2){
378 #define DUONULLALPHA "AAAAAAAAAAA="
379 if(fbuf_putn(f, DUONULLALPHA, strlen(DUONULLALPHA)) < 0){
380 return -1;
381 }
382 #undef DUONULLALPHA
383 }
384 // FIXME need chunking for cells of 768+ pixels
385 if(fbuf_printf(f, "\x1b\\\x1b_Ga=a,i=%d,c=2,q=2\x1b\\", s->id) < 0){
386 return -1;
387 }
388 return 0;
389}
390
391// we lay a cell-sixed animation block atop the graphic, giving it a
392// cell id with which we can delete it in O(1) for a rebuild. this
393// way, we needn't delete and redraw the entire sprixel.
394int kitty_wipe_animation(sprixel* s, int ycell, int xcell){
395 logdebug("wiping sprixel %u at %d/%d", s->id, ycell, xcell);
396 if(init_sprixel_animation(s)){
397 return -1;
398 }
399 fbuf* f = &s->glyph;
400 if(kitty_blit_wipe_selfref(s, f, ycell, xcell) < 0){
401 return -1;
402 }
403 int tamidx = ycell * s->dimx + xcell;
404 uint8_t* auxvec = s->n->tam[tamidx].auxvector;
405 auxvec[ncplane_pile(s->n)->cellpxx * ncplane_pile(s->n)->cellpxy * 4] = 0;
407 return 1;
408}
409
410int kitty_wipe_selfref(sprixel* s, int ycell, int xcell){
411 if(init_sprixel_animation(s)){
412 return -1;
413 }
414 const int tyx = xcell + ycell * s->dimx;
415 int state = s->n->tam[tyx].state;
416 void* auxvec = s->n->tam[tyx].auxvector;
417 logdebug("wiping sprixel %u at %d/%d auxvec: %p state: %d", s->id, ycell, xcell, auxvec, state);
418 fbuf* f = &s->glyph;
419 if(kitty_blit_wipe_selfref(s, f, ycell, xcell)){
420 return -1;
421 }
423 memcpy(auxvec, &state, sizeof(state));
424 return 1;
425}
426
428 assert(n->sprite);
429 sprixel* hides = n->sprite;
430 int dimy = hides->dimy;
431 int dimx = hides->dimx;
432 sprixel_hide(hides);
433 return sprixel_alloc(n, dimy, dimx);
434}
435
436// for pre-animation kitty (NCPIXEL_KITTY_STATIC), we need a byte per pixel,
437// in which we stash the alpha.
438static inline uint8_t*
439kitty_auxiliary_vector(const sprixel* s){
440 int pixels = ncplane_pile(s->n)->cellpxy * ncplane_pile(s->n)->cellpxx;
441 uint8_t* ret = malloc(sizeof(*ret) * pixels);
442 if(ret){
443 memset(ret, 0, sizeof(*ret) * pixels);
444 }
445 return ret;
446}
447
448int kitty_wipe(sprixel* s, int ycell, int xcell){
449//fprintf(stderr, "NEW WIPE %d %d/%d\n", s->id, ycell, xcell);
450 uint8_t* auxvec = kitty_auxiliary_vector(s);
451 if(auxvec == NULL){
452 return -1;
453 }
454 const int totalpixels = s->pixy * s->pixx;
455 const int xpixels = ncplane_pile(s->n)->cellpxx;
456 const int ypixels = ncplane_pile(s->n)->cellpxy;
457 // if the cell is on the right or bottom borders, it might only be partially
458 // filled by actual graphic data, and we need to cap our target area.
459 int targx = xpixels;
460 if((xcell + 1) * xpixels > s->pixx){
461 targx = s->pixx - xcell * xpixels;
462 }
463 int targy = ypixels;
464 if((ycell + 1) * ypixels > s->pixy){
465 targy = s->pixy - ycell * ypixels;
466 }
467 char* c = (char*)s->glyph.buf + s->parse_start;
468//fprintf(stderr, "TARGET AREA: %d x %d @ %dx%d of %d/%d (%d/%d) len %zu\n", targy, targx, ycell, xcell, s->dimy, s->dimx, s->pixy, s->pixx, strlen(c));
469 // every pixel was 4 source bytes, 32 bits, 6.33 base64 bytes. every 3 input pixels is
470 // 12 bytes (96 bits), an even 16 base64 bytes. there is chunking to worry about. there
471 // are up to 768 pixels in a chunk.
472 int nextpixel = (s->pixx * ycell * ypixels) + (xpixels * xcell);
473 int thisrow = targx;
474 int chunkedhandled = 0;
475 const int chunks = totalpixels / RGBA_MAXLEN + !!(totalpixels % RGBA_MAXLEN);
476 int auxvecidx = 0;
477 while(targy && chunkedhandled < chunks){ // need to null out |targy| rows of |targx| pixels, track with |thisrow|
478//fprintf(stderr, "PLUCKING FROM [%s]\n", c);
479 int inchunk = totalpixels - chunkedhandled * RGBA_MAXLEN;
480 if(inchunk > RGBA_MAXLEN){
481 inchunk = RGBA_MAXLEN;
482 }
483 const int curpixel = chunkedhandled * RGBA_MAXLEN;
484 // a full chunk is 4096 + 2 + 7 (5005)
485 while(nextpixel - curpixel < RGBA_MAXLEN && thisrow){
486 // our next pixel is within this chunk. find the pixel offset of the
487 // first pixel (within the chunk).
488 int pixoffset = nextpixel - curpixel;
489 int triples = pixoffset / 3;
490 int tripbytes = triples * 16;
491 // we start within a 16-byte chunk |tripbytes| into the chunk. determine
492 // the number of bits.
493 int tripskip = pixoffset - triples * 3;
494//fprintf(stderr, "pixoffset: %d next: %d tripbytes: %d tripskip: %d thisrow: %d\n", pixoffset, nextpixel, tripbytes, tripskip, thisrow);
495 // the maximum number of pixels we can convert is the minimum of the
496 // pixels remaining in the target row, and the pixels left in the chunk.
497//fprintf(stderr, "inchunk: %d total: %d triples: %d\n", inchunk, totalpixels, triples);
498//fprintf(stderr, "PRECHOMP: [%.16s]\n", c + tripbytes);
499 int chomped = kitty_null(c + tripbytes, tripskip, thisrow,
500 inchunk - triples * 3, auxvec + auxvecidx);
501//fprintf(stderr, "POSTCHOMP: [%.16s]\n", c + tripbytes);
502 assert(chomped >= 0);
503 auxvecidx += chomped;
504 assert(auxvecidx <= ypixels * xpixels);
505 thisrow -= chomped;
506//fprintf(stderr, "POSTCHIMP CHOMP: %d pixoffset: %d next: %d tripbytes: %d tripskip: %d thisrow: %d\n", chomped, pixoffset, nextpixel, tripbytes, tripskip, thisrow);
507 if(thisrow == 0){
508//fprintf(stderr, "CLEARED ROW, TARGY: %d\n", targy - 1);
509 if(--targy == 0){
510 s->n->tam[s->dimx * ycell + xcell].auxvector = auxvec;
512 return 1;
513 }
514 thisrow = targx;
515//fprintf(stderr, "BUMP IT: %d %d %d %d\n", nextpixel, s->pixx, targx, chomped);
516 nextpixel += s->pixx - targx + chomped;
517 }else{
518 nextpixel += chomped;
519 }
520 }
521 c += RGBA_MAXLEN * 4 * 4 / 3; // 4bpp * 4/3 for base64, 4096b per chunk
522 c += 8; // new chunk header
523 ++chunkedhandled;
524//fprintf(stderr, "LOOKING NOW AT %u [%s]\n", c - s->glyph, c);
525 while(*c != ';'){
526 ++c;
527 }
528 ++c;
529 }
530 free(auxvec);
531 return -1;
532}
533
534int kitty_commit(fbuf* f, sprixel* s, unsigned noscroll){
535 loginfo("committing Kitty graphic id %u", s->id);
536 int i;
537 if(s->pxoffx || s->pxoffy){
538 i = fbuf_printf(f, "\e_Ga=p,i=%u,p=1,X=%u,Y=%u%s,q=2\e\\", s->id,
539 s->pxoffx, s->pxoffy, noscroll ? ",C=1" : "");
540 }else{
541 i = fbuf_printf(f, "\e_Ga=p,i=%u,p=1,q=2%s\e\\", s->id, noscroll ? ",C=1" : "");
542 }
543 if(i < 0){
544 return -1;
545 }
547 return 0;
548}
549
550// chunkify and write the collected buffer in the animated case. this might
551// or might not be compressed (depends on whether compression was useful).
552static int
553encode_and_chunkify(fbuf* f, const unsigned char* buf, size_t blen, unsigned compressed){
554 // need to terminate the header, requiring semicolon
555 if(compressed){
556 if(fbuf_putn(f, ",o=z", 4) < 0){
557 return -1;
558 }
559 }
560 if(blen > 4096 * 3 / 4){
561 if(fbuf_putn(f, ",m=1", 4) < 0){
562 return -1;
563 }
564 }
565 if(fbuf_putc(f, ';') < 0){
566 return -1;
567 }
568 bool first = true;
569 unsigned long i = 0;
570 char b64d[4];
571 while(blen - i > 4096 * 3 / 4){
572 if(!first){
573 if(fbuf_putn(f, "\x1b_Gm=1;", 7) < 0){
574 return -1;
575 }
576 }
577 unsigned long max = i + 4096 * 3 / 4;
578 while(i < max){
579 base64x3(buf + i, b64d);
580 if(fbuf_putn(f, b64d, 4) < 0){
581 return -1;
582 }
583 i += 3;
584 }
585 first = false;
586 if(fbuf_putn(f, "\x1b\\", 2) < 0){
587 return -1;
588 }
589 }
590 if(!first){
591 if(fbuf_putn(f, "\x1b_Gm=0;", 7) < 0){
592 return -1;
593 }
594 }
595 while(i < blen){
596 if(blen - i < 3){
597 base64final(buf + i, b64d, blen - i);
598 if(fbuf_putn(f, b64d, 4) < 0){
599 return -1;
600 }
601 i += blen - i;
602 }else{
603 base64x3(buf + i, b64d);
604 if(fbuf_putn(f, b64d, 4) < 0){
605 return -1;
606 }
607 i += 3;
608 }
609 }
610 if(fbuf_putn(f, "\x1b\\", 2) < 0){
611 return -1;
612 }
613 return 0;
614}
615
616static int
617deflate_buf(void* buf, fbuf* f, int dimy, int dimx){
618 const size_t blen = dimx * dimy * 4;
619 void* cbuf = NULL;
620 size_t clen = 0;
621#ifdef USE_DEFLATE
622 // 2 has been shown to work pretty well for things that are actually going
623 // to compress; results per unit time fall off quickly after 2.
624 struct libdeflate_compressor* cmp = libdeflate_alloc_compressor(2);
625 if(cmp == NULL){
626 logerror("couldn't get libdeflate context");
627 return -1;
628 }
629 // if this allocation fails, just skip compression, no need to bail
630 cbuf = malloc(blen);
631 if(cbuf){
632 clen = libdeflate_zlib_compress(cmp, buf, blen, cbuf, blen);
633 }
634 libdeflate_free_compressor(cmp);
635#else
636 z_stream zctx = {0};
637 int z = deflateInit(&zctx, 2);
638 if(z != Z_OK){
639 logerror("couldn't get zlib context");
640 return -1;
641 }
642 clen = deflateBound(&zctx, blen);
643 cbuf = malloc(clen);
644 if(cbuf == NULL){
645 logerror("couldn't allocate %" PRIuPTR "B", clen);
646 deflateEnd(&zctx);
647 return -1;
648 }
649 zctx.avail_out = clen;
650 zctx.next_out = cbuf;
651 zctx.next_in = buf;
652 zctx.avail_in = blen;
653 z = deflate(&zctx, Z_FINISH);
654 if(z != Z_STREAM_END){
655 logerror("error %d deflating %" PRIuPTR "B -> %" PRIuPTR "B", z, blen, clen);
656 deflateEnd(&zctx);
657 return -1;
658 }
659 deflateEnd(&zctx);
660 clen -= zctx.avail_out;
661#endif
662 int ret;
663 if(0 == clen){ // wasn't enough room; compressed data is larger than original
664 loginfo("deflated in vain; using original %" PRIuPTR "B", blen);
665 ret = encode_and_chunkify(f, buf, blen, 0);
666 }else{
667 loginfo("deflated %" PRIuPTR "B to %" PRIuPTR "B", blen, clen);
668 ret = encode_and_chunkify(f, cbuf, clen, 1);
669 }
670 free(cbuf);
671 return ret;
672}
673
674// copy |encodeable| ([1..3]) pixels from |src| to the buffer |dst|, setting
675// alpha along the way according to |wipe|.
676static inline int
677add_to_buf(uint32_t *dst, const uint32_t* src, int encodeable, bool wipe[static 3]){
678 dst[0] = *src++;
679 if(wipe[0] || rgba_trans_p(dst[0], 0)){
680 ncpixel_set_a(&dst[0], 0);
681 }
682 if(encodeable > 1){
683 dst[1] = *src++;
684 if(wipe[1] || rgba_trans_p(dst[1], 0)){
685 ncpixel_set_a(&dst[1], 0);
686 }
687 if(encodeable > 2){
688 dst[2] = *src++;
689 if(wipe[2] || rgba_trans_p(dst[2], 0)){
690 ncpixel_set_a(&dst[2], 0);
691 }
692 }
693 }
694 return 0;
695}
696
697// writes to |*animated| based on normalized |level|. if we're not animated,
698// we won't be using compression.
699static inline int
700prep_animation(ncpixelimpl_e level, uint32_t** buf, int leny, int lenx, unsigned* animated){
701 if(level < NCPIXEL_KITTY_ANIMATED){
702 *animated = false;
703 *buf = NULL;
704 return 0;
705 }
706 *animated = true;
707 if((*buf = malloc(lenx * leny * sizeof(uint32_t))) == NULL){
708 return -1;
709 }
710 return 0;
711}
712
713// if we're NCPIXEL_KITTY_SELFREF, and we're blitting a secondary frame, we need
714// carry through the TAM's annihilation entires...but we also need load the
715// frame *without* annihilations, lest we be unable to build it. we thus go
716// back through the TAM following a selfref blit, and any sprixcells which
717// are annihilated will have their annhilation appended to the main blit.
718// ought only be called for NCPIXEL_KITTY_SELFREF.
719static int
720finalize_multiframe_selfref(sprixel* s, fbuf* f){
721 int prewiped = 0;
722 for(unsigned y = 0 ; y < s->dimy ; ++y){
723 for(unsigned x = 0 ; x < s->dimx ; ++x){
724 unsigned tyxidx = y * s->dimx + x;
725 unsigned state = s->n->tam[tyxidx].state;
726 if(state >= SPRIXCELL_ANNIHILATED){
727 if(kitty_blit_wipe_selfref(s, f, y, x)){
728 return -1;
729 }
730 ++prewiped;
731 }
732 }
733 }
734 loginfo("transitively wiped %d/%u", prewiped, s->dimy * s->dimx);
735 return 0;
736}
737
738// we can only write 4KiB at a time. we're writing base64-encoded RGBA. each
739// pixel is 4B raw (32 bits). each chunk of three pixels is then 12 bytes, or
740// 16 base64-encoded bytes. 4096 / 16 == 256 3-pixel groups, or 768 pixels.
741// closes |fp| on all paths.
742static int
743write_kitty_data(fbuf* f, int linesize, int leny, int lenx, int cols,
744 const uint32_t* data, const blitterargs* bargs,
745 tament* tam, int* parse_start, ncpixelimpl_e level){
746 if(linesize % sizeof(*data)){
747 logerror("stride (%d) badly aligned", linesize);
748 return -1;
749 }
750 unsigned animated;
751 uint32_t* buf;
752 // we'll be collecting the pixels, modified to reflect alpha nullification
753 // due to preexisting wipes, into a temporary buffer for compression (iff
754 // we're animated). pixels are 32 bits each.
755 if(prep_animation(level, &buf, leny, lenx, &animated)){
756 return -1;
757 }
758 unsigned bufidx = 0; // an index; the actual offset is bufidx * 4
759 bool translucent = bargs->flags & NCVISUAL_OPTION_BLEND;
760 sprixel* s = bargs->u.pixel.spx;
761 const int cdimy = bargs->u.pixel.cellpxy;
762 const int cdimx = bargs->u.pixel.cellpxx;
763 assert(0 != cdimy);
764 assert(0 != cdimx);
765 const uint32_t transcolor = bargs->transcolor;
766 int total = leny * lenx; // total number of pixels (4 * total == bytecount)
767 // number of 4KiB chunks we'll need
768 int chunks = (total + (RGBA_MAXLEN - 1)) / RGBA_MAXLEN;
769 int totalout = 0; // total pixels of payload out
770 int y = 0; // position within source image (pixels)
771 int x = 0;
772 int targetout = 0; // number of pixels expected out after this chunk
773//fprintf(stderr, "total: %d chunks = %d, s=%d,v=%d\n", total, chunks, lenx, leny);
774 char out[17]; // three pixels base64 to no more than 17 bytes
775 // set high if we are (1) reloading a frame with (2) annihilated cells copied over
776 // from the TAM and (3) we are NCPIXEL_KITTY_SELFREF. calls finalize_multiframe_selfref().
777 bool selfref_annihilated = false;
778 while(chunks--){
779 // q=2 has been able to go on chunks other than the last chunk since
780 // 2021-03, but there's no harm in this small bit of backwards compat.
781 if(totalout == 0){
782 // older versions of kitty will delete uploaded images when scrolling,
783 // alas. see https://github.com/dankamongmen/notcurses/issues/1910 =[.
784 // parse_start isn't used in animation mode, so no worries about the
785 // fact that this doesn't complete the header in that case.
786 *parse_start = fbuf_printf(f, "\e_Gf=32,s=%d,v=%d,i=%d,p=1,a=t,%s",
787 lenx, leny, s->id,
788 animated ? "q=2" : chunks ? "m=1;" : "q=2;");
789 if(*parse_start < 0){
790 goto err;
791 }
792 // so if we're animated, we've printed q=2, but no semicolon to close
793 // the control block, since we're not yet sure what m= to write. we've
794 // otherwise written q=2; if we're the only chunk, and m=1; otherwise.
795 // if we're *not* animated, we'll get q=2,m=0; below. otherwise, it's
796 // handled following deflate.
797 }else{
798 if(!animated){
799 if(fbuf_printf(f, "\e_G%sm=%d;", chunks ? "" : "q=2,", chunks ? 1 : 0) < 0){
800 goto err;
801 }
802 }
803 }
804 if((targetout += RGBA_MAXLEN) > total){
805 targetout = total;
806 }
807 while(totalout < targetout){
808 int encodeable = targetout - totalout;
809 if(encodeable > 3){
810 encodeable = 3;
811 }
812 uint32_t source[3]; // we encode up to 3 pixels at a time
813 bool wipe[3];
814 for(int e = 0 ; e < encodeable ; ++e){
815 if(x == lenx){
816 x = 0;
817 ++y;
818 }
819 const uint32_t* line = data + (linesize / sizeof(*data)) * y;
820 source[e] = line[x];
821 if(translucent){
822 ncpixel_set_a(&source[e], ncpixel_a(source[e]) / 2);
823 }
824 int xcell = x / cdimx;
825 int ycell = y / cdimy;
826 int tyx = xcell + ycell * cols;
827//fprintf(stderr, "Tyx: %d y: %d (%d) * %d x: %d (%d) state %d %p\n", tyx, y, y / cdimy, cols, x, x / cdimx, tam[tyx].state, tam[tyx].auxvector);
828 // old-style animated auxvecs carry the entirety of the replacement
829 // data in them. on the first pixel of the cell, ditch the previous
830 // auxvec in its entirety, and copy over the entire cell.
831 if(x % cdimx == 0 && y % cdimy == 0){
832 if(level == NCPIXEL_KITTY_ANIMATED){
833 uint8_t* tmp;
834 tmp = kitty_anim_auxvec(leny, lenx, y, x, cdimy, cdimx,
835 data, linesize, tam[tyx].auxvector,
836 transcolor);
837 if(tmp == NULL){
838 logerror("got a NULL auxvec at %d/%d", y, x);
839 goto err;
840 }
841 tam[tyx].auxvector = tmp;
842 }else if(level == NCPIXEL_KITTY_SELFREF){
843 if(tam[tyx].auxvector == NULL){
844 tam[tyx].auxvector = malloc(sizeof(tam[tyx].state));
845 if(tam[tyx].auxvector == NULL){
846 logerror("got a NULL auxvec at %d", tyx);
847 goto err;
848 }
849 }
850 memcpy(tam[tyx].auxvector, &tam[tyx].state, sizeof(tam[tyx].state));
851 }
852 }
853 if(tam[tyx].state >= SPRIXCELL_ANNIHILATED){
854 if(!animated){
855 // this pixel is part of a cell which is currently wiped (alpha-nulled
856 // out, to present a glyph "atop" it). we will continue to mark it
857 // transparent, but we need to update the auxiliary vector.
858 const int vyx = (y % cdimy) * cdimx + (x % cdimx);
859 ((uint8_t*)tam[tyx].auxvector)[vyx] = ncpixel_a(source[e]);
860 wipe[e] = 1;
861 }else if(level == NCPIXEL_KITTY_SELFREF){
862 selfref_annihilated = true;
863 }else{
864 ((uint8_t*)tam[tyx].auxvector)[cdimx * cdimy * 4] = 1;
865 wipe[e] = 1;
866 }
867 if(rgba_trans_p(source[e], transcolor)){
868 ncpixel_set_a(&source[e], 0); // in case it was transcolor
869 if(x % cdimx == 0 && y % cdimy == 0){
871 if(level == NCPIXEL_KITTY_SELFREF){
872 *(sprixcell_e*)tam[tyx].auxvector = SPRIXCELL_TRANSPARENT;
873 }
874 }else if(level == NCPIXEL_KITTY_SELFREF && tam[tyx].state == SPRIXCELL_ANNIHILATED_TRANS){
875 *(sprixcell_e*)tam[tyx].auxvector = SPRIXCELL_MIXED_KITTY;
876 }
877 }else{
878 if(x % cdimx == 0 && y % cdimy == 0 && level == NCPIXEL_KITTY_SELFREF){
879 *(sprixcell_e*)tam[tyx].auxvector = SPRIXCELL_OPAQUE_KITTY;
880 }else if(level == NCPIXEL_KITTY_SELFREF && *(sprixcell_e*)tam[tyx].auxvector == SPRIXCELL_TRANSPARENT){
881 *(sprixcell_e*)tam[tyx].auxvector = SPRIXCELL_MIXED_KITTY;
882 }
883 tam[tyx].state = SPRIXCELL_ANNIHILATED;
884 }
885 }else{
886 wipe[e] = 0;
887 if(rgba_trans_p(source[e], transcolor)){
888 ncpixel_set_a(&source[e], 0); // in case it was transcolor
889 if(x % cdimx == 0 && y % cdimy == 0){
890 tam[tyx].state = SPRIXCELL_TRANSPARENT;
891 }else if(tam[tyx].state == SPRIXCELL_OPAQUE_KITTY){
892 tam[tyx].state = SPRIXCELL_MIXED_KITTY;
893 }
894 }else{
895 if(x % cdimx == 0 && y % cdimy == 0){
896 tam[tyx].state = SPRIXCELL_OPAQUE_KITTY;
897 }else if(tam[tyx].state == SPRIXCELL_TRANSPARENT){
898 tam[tyx].state = SPRIXCELL_MIXED_KITTY;
899 }
900 }
901 }
902 ++x;
903 }
904 totalout += encodeable;
905 if(animated){
906 if(add_to_buf(buf + bufidx, source, encodeable, wipe)){
907 goto err;
908 }
909 bufidx += encodeable;
910 }else{
911 // we already took transcolor to alpha 0; there's no need to
912 // check it again, so pass 0.
913 base64_rgba3(source, encodeable, out, wipe, 0);
914 if(fbuf_puts(f, out) < 0){
915 goto err;
916 }
917 }
918 }
919 if(!animated){
920 if(fbuf_putn(f, "\x1b\\", 2) < 0){
921 goto err;
922 }
923 }
924 }
925 // we only deflate if we're using animation, since otherwise we need be able
926 // to edit the encoded bitmap in-place for wipes/restores.
927 if(animated){
928 if(deflate_buf(buf, f, leny, lenx)){
929 goto err;
930 }
931 if(selfref_annihilated){
932 if(finalize_multiframe_selfref(s, f)){
933 goto err;
934 }
935 }
936 }
937 scrub_tam_boundaries(tam, leny, lenx, cdimy, cdimx);
938 free(buf);
939 return 0;
940
941err:
942 logerror("failed blitting kitty graphics");
943 cleanup_tam(tam, (leny + cdimy - 1) / cdimy, (lenx + cdimx - 1) / cdimx);
944 free(buf);
945 return -1;
946}
947
948// with t=z, we can reference the original frame, and say "redraw this region",
949// thus avoiding the need to carry the original data around in our auxvecs.
950int kitty_rebuild_selfref(sprixel* s, int ycell, int xcell, uint8_t* auxvec){
951 if(init_sprixel_animation(s)){
952 return -1;
953 }
954 fbuf* f = &s->glyph;
955 const int cellpxy = ncplane_pile(s->n)->cellpxy;
956 const int cellpxx = ncplane_pile(s->n)->cellpxx;
957 const int ystart = ycell * cellpxy;
958 const int xstart = xcell * cellpxx;
959 const int xlen = xstart + cellpxx > s->pixx ? s->pixx - xstart : cellpxx;
960 const int ylen = ystart + cellpxy > s->pixy ? s->pixy - ystart : cellpxy;
961 logdebug("rematerializing %u at %d/%d (%dx%d)", s->id, ycell, xcell, ylen, xlen);
962 fbuf_printf(f, "\e_Ga=c,x=%d,y=%d,X=%d,Y=%d,w=%d,h=%d,i=%d,r=1,c=2,q=2;\x1b\\",
963 xcell * cellpxx, ycell * cellpxy,
964 xcell * cellpxx, ycell * cellpxy,
965 xlen, ylen, s->id);
966 const int tyx = xcell + ycell * s->dimx;
967 memcpy(&s->n->tam[tyx].state, auxvec, sizeof(s->n->tam[tyx].state));
969 return 0;
970}
971
972int kitty_rebuild_animation(sprixel* s, int ycell, int xcell, uint8_t* auxvec){
973 logdebug("rebuilding sprixel %u %d at %d/%d", s->id, s->invalidated, ycell, xcell);
974 if(init_sprixel_animation(s)){
975 return -1;
976 }
977 fbuf* f = &s->glyph;
978 const int cellpxy = ncplane_pile(s->n)->cellpxy;
979 const int cellpxx = ncplane_pile(s->n)->cellpxx;
980 const int ystart = ycell * cellpxy;
981 const int xstart = xcell * cellpxx;
982 const int xlen = xstart + cellpxx > s->pixx ? s->pixx - xstart : cellpxx;
983 const int ylen = ystart + cellpxy > s->pixy ? s->pixy - ystart : cellpxy;
984 const int linesize = xlen * 4;
985 const int total = xlen * ylen;
986 const int tyx = xcell + ycell * s->dimx;
987 int chunks = (total + (RGBA_MAXLEN - 1)) / RGBA_MAXLEN;
988 int totalout = 0; // total pixels of payload out
989 int y = 0; // position within source image (pixels)
990 int x = 0;
991 int targetout = 0; // number of pixels expected out after this chunk
992//fprintf(stderr, "total: %d chunks = %d, s=%d,v=%d\n", total, chunks, lenx, leny);
993 // FIXME this ought be factored out and shared with write_kitty_data()
994 logdebug("placing %d/%d at %d/%d", ylen, xlen, ycell * cellpxy, xcell * cellpxx);
995 while(chunks--){
996 if(totalout == 0){
997 const int c = kitty_anim_auxvec_blitsource_p(s, auxvec) ? 2 : 1;
998 const int r = kitty_anim_auxvec_blitsource_p(s, auxvec) ? 1 : 2;
999 if(fbuf_printf(f, "\e_Ga=f,x=%d,y=%d,s=%d,v=%d,i=%d,X=1,c=%d,r=%d,%s;",
1000 xcell * cellpxx, ycell * cellpxy, xlen, ylen,
1001 s->id, c, r, chunks ? "m=1" : "q=2") < 0){
1002 return -1;
1003 }
1004 }else{
1005 if(fbuf_putn(f, "\x1b_G", 3) < 0){
1006 return -1;
1007 }
1008 if(!chunks){
1009 if(fbuf_putn(f, "q=2,", 4) < 0){
1010 return -1;
1011 }
1012 }
1013 if(fbuf_putn(f, "m=", 2) < 0){
1014 return -1;
1015 }
1016 if(fbuf_putint(f, chunks ? 1 : 0) < 0){
1017 return -1;
1018 }
1019 if(fbuf_putc(f, ';') != 1){
1020 return -1;
1021 }
1022 }
1023 if((targetout += RGBA_MAXLEN) > total){
1024 targetout = total;
1025 }
1026 while(totalout < targetout){
1027 int encodeable = targetout - totalout;
1028 if(encodeable > 3){
1029 encodeable = 3;
1030 }
1031 uint32_t source[3]; // we encode up to 3 pixels at a time
1032 bool wipe[3];
1033 for(int e = 0 ; e < encodeable ; ++e){
1034 if(x == xlen){
1035 x = 0;
1036 ++y;
1037 }
1038 const uint32_t* line = (const uint32_t*)(auxvec + linesize * y);
1039 source[e] = line[x];
1040//fprintf(stderr, "%u/%u/%u -> %c%c%c%c %u %u %u %u\n", r, g, b, b64[0], b64[1], b64[2], b64[3], b64[0], b64[1], b64[2], b64[3]);
1041//fprintf(stderr, "Tyx: %d y: %d (%d) * %d x: %d (%d) state %d %p\n", tyx, y, y / cdimy, cols, x, x / cdimx, tam[tyx].state, tam[tyx].auxvector);
1042 wipe[e] = 0;
1043 if(rgba_trans_p(source[e], 0)){
1044 if(x % cellpxx == 0 && y % cellpxy == 0){
1045 s->n->tam[tyx].state = SPRIXCELL_TRANSPARENT;
1046 }else if(s->n->tam[tyx].state == SPRIXCELL_OPAQUE_KITTY){
1047 s->n->tam[tyx].state = SPRIXCELL_MIXED_KITTY;
1048 }
1049 }else{
1050 if(x % cellpxx == 0 && y % cellpxy == 0){
1051 s->n->tam[tyx].state = SPRIXCELL_OPAQUE_KITTY;
1052 }else if(s->n->tam[tyx].state == SPRIXCELL_TRANSPARENT){
1053 s->n->tam[tyx].state = SPRIXCELL_MIXED_KITTY;
1054 }
1055 }
1056 ++x;
1057 }
1058 totalout += encodeable;
1059 char out[17];
1060 base64_rgba3(source, encodeable, out, wipe, 0);
1061 if(fbuf_puts(f, out) < 0){
1062 return -1;
1063 }
1064 }
1065 if(fbuf_putn(f, "\x1b\\", 2) < 0){
1066 return -1;
1067 }
1068 }
1069//fprintf(stderr, "EMERGED WITH TAM STATE %d\n", s->n->tam[tyx].state);
1071 return 0;
1072}
1073#undef RGBA_MAXLEN
1074
1075// Kitty graphics blitter. Kitty can take in up to 4KiB at a time of (optionally
1076// deflate-compressed) 24bit RGB. Returns -1 on error, 1 on success.
1077static inline int
1078kitty_blit_core(ncplane* n, int linesize, const void* data, int leny, int lenx,
1079 const blitterargs* bargs, ncpixelimpl_e level){
1080 int cols = bargs->u.pixel.spx->dimx;
1081 sprixel* s = bargs->u.pixel.spx;
1082 if(init_sprixel_animation(s)){
1083 return -1;
1084 }
1085 int parse_start = 0;
1086 fbuf* f = &s->glyph;
1087 int pxoffx = bargs->u.pixel.pxoffx;
1088 int pxoffy = bargs->u.pixel.pxoffy;
1089 if(write_kitty_data(f, linesize, leny, lenx, cols, data,
1090 bargs, n->tam, &parse_start, level)){
1091 goto error;
1092 }
1093 // FIXME need set pxoffx and pxoffy in sprixel
1094 if(level == NCPIXEL_KITTY_STATIC){
1095 s->animating = false;
1096 }
1097 // take ownership of |buf| and |tam| on success.
1098 if(plane_blit_sixel(s, &s->glyph, leny + pxoffy, lenx + pxoffx, parse_start,
1099 n->tam, SPRIXEL_UNSEEN) < 0){
1100 goto error;
1101 }
1102 s->pxoffx = pxoffx;
1103 s->pxoffy = pxoffy;
1104 return 1;
1105
1106error:
1107 cleanup_tam(n->tam, bargs->u.pixel.spx->dimy, bargs->u.pixel.spx->dimx);
1108 fbuf_free(&s->glyph);
1109 return -1;
1110}
1111
1112int kitty_blit(ncplane* n, int linesize, const void* data, int leny, int lenx,
1113 const blitterargs* bargs){
1114 return kitty_blit_core(n, linesize, data, leny, lenx, bargs,
1116}
1117
1118int kitty_blit_animated(ncplane* n, int linesize, const void* data,
1119 int leny, int lenx, const blitterargs* bargs){
1120 return kitty_blit_core(n, linesize, data, leny, lenx, bargs,
1122}
1123
1124int kitty_blit_selfref(ncplane* n, int linesize, const void* data,
1125 int leny, int lenx, const blitterargs* bargs){
1126 return kitty_blit_core(n, linesize, data, leny, lenx, bargs,
1128}
1129
1130int kitty_remove(int id, fbuf* f){
1131 loginfo("removing graphic %u", id);
1132 if(fbuf_printf(f, "\e_Ga=d,d=I,i=%d\e\\", id) < 0){
1133 return -1;
1134 }
1135 return 0;
1136}
1137
1138// damages cells underneath the graphic which were OPAQUE
1139int kitty_scrub(const ncpile* p, sprixel* s){
1140//fprintf(stderr, "FROM: %d/%d state: %d s->n: %p\n", s->movedfromy, s->movedfromx, s->invalidated, s->n);
1141 for(unsigned yy = s->movedfromy ; yy < s->movedfromy + s->dimy && yy < p->dimy ; ++yy){
1142 for(unsigned xx = s->movedfromx ; xx < s->movedfromx + s->dimx && xx < p->dimx ; ++xx){
1143 const int ridx = yy * p->dimx + xx;
1144 assert(0 <= ridx);
1145 struct crender *r = &p->crender[ridx];
1146 if(!r->sprixel){
1147 if(s->n){
1148//fprintf(stderr, "CHECKING %d/%d\n", yy - s->movedfromy, xx - s->movedfromx);
1149 sprixcell_e state = sprixel_state(s, yy - s->movedfromy + s->n->absy,
1150 xx - s->movedfromx + s->n->absx);
1151 if(state == SPRIXCELL_OPAQUE_KITTY){
1152 r->s.damaged = 1;
1153 }else if(s->invalidated == SPRIXEL_MOVED){
1154 // ideally, we wouldn't damage our annihilated sprixcells, but if
1155 // we're being annihilated only during this cycle, we need to go
1156 // ahead and damage it.
1157 r->s.damaged = 1;
1158 }
1159 }else{
1160 // need this to damage cells underneath a sprixel we're removing
1161 r->s.damaged = 1;
1162 }
1163 }
1164 }
1165 }
1166 return 0;
1167}
1168
1169// returns the number of bytes written
1170int kitty_draw(const tinfo* ti, const ncpile* p, sprixel* s, fbuf* f,
1171 int yoff, int xoff){
1172 (void)ti;
1173 (void)p;
1174 bool animated = false;
1175 if(s->animating){ // active animation
1176 s->animating = false;
1177 animated = true;
1178 }
1179 int ret = s->glyph.used;
1180 logdebug("dumping %" PRIu64 "b for %u at %d %d", s->glyph.used, s->id, yoff, xoff);
1181 if(ret){
1182 if(fbuf_putn(f, s->glyph.buf, s->glyph.used) < 0){
1183 ret = -1;
1184 }
1185 }
1186 if(animated){
1187 fbuf_free(&s->glyph);
1188 }
1189 s->invalidated = SPRIXEL_LOADED;
1190 return ret;
1191}
1192
1193// returns -1 on failure, 0 on success (move bytes do not count for sprixel stats)
1194int kitty_move(sprixel* s, fbuf* f, unsigned noscroll, int yoff, int xoff){
1195 const int targy = s->n->absy;
1196 const int targx = s->n->absx;
1197 logdebug("moving %u to %d %d", s->id, targy, targx);
1198 int ret = 0;
1199 if(goto_location(ncplane_notcurses(s->n), f, targy + yoff, targx + xoff, s->n)){
1200 ret = -1;
1201 }else if(fbuf_printf(f, "\e_Ga=p,i=%d,p=1,q=2%s\e\\", s->id,
1202 noscroll ? ",C=1" : "") < 0){
1203 ret = -1;
1204 }
1205 s->invalidated = SPRIXEL_QUIESCENT;
1206 return ret;
1207}
1208
1209// clears all kitty bitmaps
1211//fprintf(stderr, "KITTY UNIVERSAL ERASE\n");
1212 if(fbuf_putn(f, "\x1b_Ga=d,q=2\x1b\\", 12) < 0){
1213 return -1;
1214 }
1215 return 0;
1216}
API int API int API int uint64_t uint64_t uint64_t uint64_t unsigned unsigned xlen
Definition direct.h:217
API int API int API int uint64_t uint64_t uint64_t uint64_t unsigned ylen
Definition direct.h:217
assert(false)
const nccell * c
Definition egcpool.h:296
free(duplicated)
int r
Definition fbuf.h:226
void sprixel_hide(sprixel *s)
Definition sprite.c:83
sprixel * sprixel_alloc(ncplane *n, int dimy, int dimx)
Definition sprite.c:117
int kitty_remove(int id, fbuf *f)
Definition kitty.c:1130
int kitty_move(sprixel *s, fbuf *f, unsigned noscroll, int yoff, int xoff)
Definition kitty.c:1194
#define RGBA_MAXLEN
Definition kitty.c:216
#define TRINULLALPHA
int kitty_wipe(sprixel *s, int ycell, int xcell)
Definition kitty.c:448
int kitty_scrub(const ncpile *p, sprixel *s)
Definition kitty.c:1139
int kitty_blit_selfref(ncplane *n, int linesize, const void *data, int leny, int lenx, const blitterargs *bargs)
Definition kitty.c:1124
int kitty_rebuild_selfref(sprixel *s, int ycell, int xcell, uint8_t *auxvec)
Definition kitty.c:950
int kitty_clear_all(fbuf *f)
Definition kitty.c:1210
int kitty_rebuild(sprixel *s, int ycell, int xcell, uint8_t *auxvec)
Definition kitty.c:219
#define UNUMNULLALPHA
int kitty_rebuild_animation(sprixel *s, int ycell, int xcell, uint8_t *auxvec)
Definition kitty.c:972
#define DUONULLALPHA
int kitty_blit(ncplane *n, int linesize, const void *data, int leny, int lenx, const blitterargs *bargs)
Definition kitty.c:1112
int kitty_draw(const tinfo *ti, const ncpile *p, sprixel *s, fbuf *f, int yoff, int xoff)
Definition kitty.c:1170
uint8_t * kitty_trans_auxvec(const ncpile *p)
Definition kitty.c:342
int kitty_wipe_animation(sprixel *s, int ycell, int xcell)
Definition kitty.c:394
sprixel * kitty_recycle(ncplane *n)
Definition kitty.c:427
int kitty_commit(fbuf *f, sprixel *s, unsigned noscroll)
Definition kitty.c:534
int kitty_wipe_selfref(sprixel *s, int ycell, int xcell)
Definition kitty.c:410
int kitty_blit_animated(ncplane *n, int linesize, const void *data, int leny, int lenx, const blitterargs *bargs)
Definition kitty.c:1118
#define logerror(fmt,...)
Definition logging.h:32
#define loginfo(fmt,...)
Definition logging.h:42
#define logdebug(fmt,...)
Definition logging.h:52
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
ncpixelimpl_e
Definition notcurses.h:1672
@ NCPIXEL_KITTY_STATIC
Definition notcurses.h:1680
@ NCPIXEL_KITTY_SELFREF
Definition notcurses.h:1689
@ NCPIXEL_KITTY_ANIMATED
Definition notcurses.h:1685
#define NCVISUAL_OPTION_BLEND
Definition notcurses.h:3338
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_KITTY
Definition sprite.h:119
@ SPRIXCELL_OPAQUE_KITTY
Definition sprite.h:117
@ SPRIXEL_UNSEEN
Definition sprite.h:22
@ SPRIXEL_LOADED
Definition sprite.h:23
@ SPRIXEL_QUIESCENT
Definition sprite.h:21
@ SPRIXEL_INVALIDATED
Definition sprite.h:24
@ SPRIXEL_MOVED
Definition sprite.h:26
struct blitterargs::@3::@5 pixel
uint64_t flags
Definition internal.h:379
sprixel * spx
Definition internal.h:388
union blitterargs::@3 u
uint32_t transcolor
Definition internal.h:380
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
tament * tam
Definition internal.h:106
int parse_start
Definition sprite.h:153
int pxoffx
Definition sprite.h:154
bool animating
Definition sprite.h:159
unsigned dimx
Definition sprite.h:146
int movedfromx
Definition sprite.h:151
int pxoffy
Definition sprite.h:154
int pixx
Definition sprite.h:147
int movedfromy
Definition sprite.h:150
int pixy
Definition sprite.h:147
unsigned dimy
Definition sprite.h:146
fbuf glyph
Definition sprite.h:138
struct ncplane * n
Definition sprite.h:142
uint32_t id
Definition sprite.h:139
sprixel_e invalidated
Definition sprite.h:143
void * auxvector
Definition sprite.h:128
sprixcell_e state
Definition sprite.h:127
return NULL
Definition termdesc.h:229
static escape_e e
Definition termdesc.h:224