1 #ifndef NOTCURSES_SPRITE
2 #define NOTCURSES_SPRITE
3 
4 #ifdef __cplusplus
5 extern "C" {
6 #endif
7 
8 #include <stdint.h>
9 #include <stdbool.h>
10 #include "fbuf.h"
11 
12 struct tinfo;
13 struct ncpile;
14 struct ncplane;
15 struct sixelmap;
16 struct blitterargs;
17 
18 typedef enum {
19   SPRIXEL_QUIESCENT,   // up-to-date and visible at the proper place
20   SPRIXEL_UNSEEN,      // not yet loaded, invisible, but wants loading
21   SPRIXEL_LOADED,      // loaded, but not yet made visible (kitty-only)
22   SPRIXEL_INVALIDATED, // not up-to-date, need reload
23   SPRIXEL_HIDE,        // queued for destruction
24   SPRIXEL_MOVED,       // visible, up-to-date, but in the wrong place
25 } sprixel_e;
26 
27 // elements of the T-A matrix describe transparency and annihilation at a
28 // per-cell basis, making up something of a state machine. when a sprixel
29 // plane is first created, the TAM is (meaninglessly) initialized to all
30 // zeroes (SPRIXCELL_OPAQUE). during the construction of the sprixel from
31 // an RGBA frame, OPAQUE entries are possibly marked MIXED or TRANSPARENT.
32 // subsequent sprixels blitted to the same plane will reuse the TAM, and
33 // retain any SPRIXCELL_ANNIHILATED entries, cutting them out of the
34 // sprixel.
35 //
36 // sixel can transition to ANNIHILATED via a no-op; kitty can transition
37 // to ANNIHILATED only by wiping the cell (removing it from the sprixel via
38 // all-0 alphas), deleting the bitmap, and displaying it once more. sixel
39 // bitmaps are removed by obliterating them with new output, while kitty
40 // bitmaps are removed by a fixed-length terminal escape. an important
41 // implication is that sixels cannot be progressively reduced by emitting
42 // progressively more transparent sixels atop one another--to remove a
43 // cell from a Sixel sprixel, it is necessary to print a glyph. the same
44 // goes for Kitty sprixels, but there we delete and rerender bitmaps
45 // in toto without glyph involvement.
46 //
47 // a glyph above an OPAQUE sprixel requires annihilating the underlying cell,
48 // and emitting the glyph only after annihilation is complete. a glyph below
49 // an OPAQUE sprixel should never be emitted (update the lastframe to
50 // contain it, but do not mark the cell damaged). should the sprixel be
51 // removed, the cell will be marked damaged, and the glyph will be updated.
52 //
53 // a glyph above a MIXED sprixcell requires the same process as one above an
54 // OPAQUE sprixcell. a glyph below a MIXED sprixcell can be emitted, but a
55 // Sixel-based sprixel must then be printed afresh. a Kitty-based sprixel
56 // needn't be touched in this case.
57 //
58 // a glyph above a TRANSPARENT sprixcell requires annihilating the underlying
59 // cell, but this is a special annihilation which never requires a wipe nor
60 // redisplay, just the O(1) state transition. a glyph below a TRANSPARENT
61 // sprixcell can be emitted with no change to the sprixcell. TRANSPARENT
62 // sprixcells move to ANNIHILATED_TRANS upon annihilation.
63 //
64 // a glyph above an ANNIHILATED sprixcell can be emitted with no change to
65 // the sprixcell. it does not make sense to emit a glyph below an ANNIHILATED
66 // sprixcell; if there is no longer a glyph above the sprixcell, the sprixcell
67 // must transition back to its original state (see below).
68 //
69 // rendering a new RGBA frame into the same sprixel plane can result in changes
70 // between OPAQUE, MIXED, and TRANSPARENT. an OPAQUE sprixcell which becomes
71 // TRANSPARENT or MIXED upon rendering a new RGBA frame must damage its cell,
72 // since the glyph underneath might have changed without being emitted. the
73 // new glyph must be emitted prior to redisplay of the sprixel.
74 //
75 // an ANNIHILATED sprixcell with no glyph above it must be restored to its
76 // original form (from the most recent RGBA frame). this requires the original
77 // pixel data. for Sixel, we must keep the palette indices in an auxiliary
78 // vector, hung off the TAM, updated each time we convert an RGBA frame into a
79 // partially- or wholly-ANNIHILATED sprixel. for Kitty, we must keep the
80 // original alpha values. the new state can be solved from this data. if the
81 // new state is either OPAQUE or MIXED, the sprixel must be redisplayed. if the
82 // new state is TRANSPARENT, this cell requires no such redisplay, and the
83 // payload needn't be modified. to special-case this O(1) conversion, we keep a
84 // distinct state, ANNIHILATED_TRANS. only a TRANSPARENT sprixcell can enter
85 // into this state.
86 //
87 // when a sprixel is removed from the rendering pile, in Sixel all cells it
88 // covered must be marked damaged, so that they are rendered, obliterating
89 // the bitmap. in Kitty the bitmap can simply be deleted, except for those
90 // cells which were SPRIXCELL_OPAQUE (they must be damaged).
91 //
92 // when a sprixel is moved, its TAM must be updated. OPAQUE, MIXED, and
93 // TRANSPARENT cells retain their entries. ANNIHILATED cells remain
94 // ANNIHILATED if their new absolute position corresponds to an ANNIHILATED
95 // cell; they otherwise transition back as outlined above. this is because
96 // ANNIHILATION is a property of those glyphs above us, while the other
97 // three are internal, intrinsic properties. for Sixel, all cells no longer
98 // covered must be damaged for rerendering, and the sprixel must subsequently
99 // be displayed at its new position. for Kitty, the sprixel must be deleted,
100 // and all cells no longer covered but which were previously under an OPAQUE
101 // cell must be damaged for rerendering (not to erase the bitmap, but because
102 // they might have changed without being emitted while obstructed by the
103 // sprixel). the sprixel should be displayed at its new position. using Kitty's
104 // bitmap movement is also acceptable, rather than a deletion and rerender.
105 // whichever method is used, it is necessary to recover any ANNIHILATED cells
106 // before moving or redisplaying the sprixel.
107 //
108 // all emissions take place at rasterization time. cell wiping happens at
109 // rendering time. cell reconstruction happens at rendering time (for
110 // ANNIHILATED cells which are no longer ANNIHILATED), or at blittime for
111 // a new RGBA frame.
112 typedef enum {
113   SPRIXCELL_TRANSPARENT,       // all pixels are naturally transparent
114   SPRIXCELL_OPAQUE_SIXEL,      // no transparent pixels in this cell
115   SPRIXCELL_OPAQUE_KITTY,
116   SPRIXCELL_MIXED_SIXEL,       // this cell has both opaque and transparent pixels
117   SPRIXCELL_MIXED_KITTY,
118   SPRIXCELL_ANNIHILATED,       // this cell has been wiped (all trans)
119   SPRIXCELL_ANNIHILATED_TRANS, // this transparent cell is covered
120 } sprixcell_e;
121 
122 // a TAM entry is a sprixcell_e state plus a possible auxiliary vector for
123 // reconstruction of annihilated cells, valid only for SPRIXCELL_ANNIHILATED.
124 typedef struct tament {
125   sprixcell_e state;
126   void* auxvector; // palette entries for sixel, alphas for kitty
127 } tament;
128 
129 // a sprixel represents a bitmap, using whatever local protocol is available.
130 // there is a list of sprixels per ncpile. there ought never be very many
131 // associated with a context (a dozen or so at max). with the kitty protocol,
132 // we can register them, and then manipulate them by id. with the sixel
133 // protocol, we just have to rewrite them. there's a doubly-linked list of
134 // sprixels per ncpile, to which the pile keeps a head link.
135 typedef struct sprixel {
136   fbuf glyph;
137   uint32_t id;          // embedded into gcluster field of nccell, 24 bits
138   // both the plane and visual can die before the sprixel does. they are
139   // responsible in such a case for NULLing out this link themselves.
140   struct ncplane* n;    // associated ncplane
141   sprixel_e invalidated;// sprixel invalidation state
142   struct sprixel* next;
143   struct sprixel* prev;
144   unsigned dimy, dimx;  // cell geometry
145   int pixy, pixx;       // pixel geometry (might be smaller than cell geo)
146   // each tacache entry is one of 0 (standard opaque cell), 1 (cell with
147   // some transparency), 2 (annihilated, excised)
148   int movedfromy;       // for SPRIXEL_MOVED, the starting absolute position,
149   int movedfromx;       // so that we can damage old cells when redrawn
150   // only used for kitty-based sprixels
151   int parse_start;      // where to start parsing for cell wipes
152   int pxoffy, pxoffx;   // X and Y parameters to display command
153   // only used for sixel-based sprixels
154   unsigned char* needs_refresh; // one per cell, whether new frame needs damage
155   struct sixelmap* smap;  // copy of palette indices + transparency bits
156   bool wipes_outstanding; // do we need rebuild the sixel next render?
157   bool animating;        // do we have an active animation?
158 } sprixel;
159 
160 static inline tament*
create_tam(int rows,int cols)161 create_tam(int rows, int cols){
162   // need cast for c++ callers
163   tament* tam = (tament*)malloc(sizeof(*tam) * rows * cols);
164   if(tam){
165     memset(tam, 0, sizeof(*tam) * rows * cols);
166   }
167   return tam;
168 }
169 
170 int sixel_wipe(sprixel* s, int ycell, int xcell);
171 // nulls out a cell from a kitty bitmap via changing the alpha value
172 // throughout to 0. the same trick doesn't work on sixel, but there we
173 // can just print directly over the bitmap.
174 int kitty_wipe(sprixel* s, int ycell, int xcell);
175 // wipes out a cell by animating an all-transparent cell, and integrating
176 // it with the original image using the animation protocol of 0.20.0+.
177 int kitty_wipe_animation(sprixel* s, int ycell, int xcell);
178 int kitty_wipe_selfref(sprixel* s, int ycell, int xcell);
179 // wipes out a cell by changing the alpha value throughout the PNG cell to 0.
180 int fbcon_wipe(sprixel* s, int ycell, int xcell);
181 int sixel_rebuild(sprixel* s, int ycell, int xcell, uint8_t* auxvec);
182 int kitty_rebuild(sprixel* s, int ycell, int xcell, uint8_t* auxvec);
183 int fbcon_rebuild(sprixel* s, int ycell, int xcell, uint8_t* auxvec);
184 int kitty_rebuild_animation(sprixel* s, int ycell, int xcell, uint8_t* auxvec);
185 int kitty_rebuild_selfref(sprixel* s, int ycell, int xcell, uint8_t* auxvec);
186 int sixel_draw(const tinfo* ti, const struct ncpile *p, sprixel* s,
187                fbuf* f, int yoff, int xoff);
188 int kitty_draw(const tinfo* ti, const struct ncpile *p, sprixel* s,
189                fbuf* f, int yoff, int xoff);
190 int kitty_move(sprixel* s, fbuf* f, unsigned noscroll, int yoff, int xoff);
191 int sixel_scrub(const struct ncpile* p, sprixel* s);
192 int kitty_scrub(const struct ncpile* p, sprixel* s);
193 int fbcon_scrub(const struct ncpile* p, sprixel* s);
194 int kitty_remove(int id, fbuf* f);
195 int kitty_clear_all(fbuf* f);
196 int sixel_init(int fd);
197 int sixel_init_inverted(int fd);
198 uint8_t* sixel_trans_auxvec(const struct ncpile* p);
199 uint8_t* kitty_trans_auxvec(const struct ncpile* p);
200 int kitty_commit(fbuf* f, sprixel* s, unsigned noscroll);
201 int sixel_blit(struct ncplane* nc, int linesize, const void* data,
202                int leny, int lenx, const struct blitterargs* bargs);
203 int kitty_blit(struct ncplane* nc, int linesize, const void* data,
204                int leny, int lenx, const struct blitterargs* bargs);
205 int kitty_blit_animated(struct ncplane* n, int linesize, const void* data,
206                         int leny, int lenx, const struct blitterargs* bargs);
207 int kitty_blit_selfref(struct ncplane* nc, int linesize, const void* data,
208                        int leny, int lenx, const struct blitterargs* bargs);
209 int fbcon_blit(struct ncplane* nc, int linesize, const void* data,
210                int leny, int lenx, const struct blitterargs* bargs);
211 int fbcon_draw(const tinfo* ti, sprixel* s, int yoff, int xoff);
212 void fbcon_scroll(const struct ncpile* p, tinfo* ti, int rows);
213 void sixel_refresh(const struct ncpile* p, sprixel* s);
214 
215 // takes ownership of s on success.
216 int sprixel_load(sprixel* spx, fbuf* f, unsigned pixy, unsigned pixx,
217                  int parse_start, sprixel_e state);
218 
219 // called when a sprixel's cell-pixel geometry needs to change to |ncellpxy,ncellpxx|.
220 int sprixel_rescale(sprixel* spx, unsigned ncellpixy, unsigned ncellpixx);
221 
222 #ifdef __cplusplus
223 }
224 #endif
225 
226 #endif
227