-
-
Notifications
You must be signed in to change notification settings - Fork 114
/
sixel.c
1793 lines (1720 loc) · 60.4 KB
/
sixel.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include <math.h>
#include <stdatomic.h>
#include "internal.h"
#include "fbuf.h"
#define RGBSIZE 3
// number of worker threads
// FIXME fit to local machine, but more than 3 never seems to help
#define POPULATION 3
// a worker can have up to three qstates enqueued for work
#define WORKERDEPTH 3
// this palette entry is a sentinel for a transparent pixel (and thus caps
// the palette at 65535 other entries).
#define TRANS_PALETTE_ENTRY 65535
// bytes per element in the auxiliary vector
#define AUXVECELEMSIZE 2
// three scaled sixel [0..100x3] components plus a population count.
typedef struct qsample {
unsigned char comps[RGBSIZE];
uint32_t pop;
} qsample;
// lowest samples for each node. first-order nodes track 1000 points in
// sixelspace (10x10x10). there are eight possible second-order nodes from a
// fractured first-order node, covering 125 points each (5x5x5).
typedef struct qnode {
qsample q;
// cidx plays two roles. during merge, we select the active set, and extract
// them (since they'll be sorted, we can't operate directly on the octree).
// here, we use cidx to map back to the initial octree entry, as we need
// update them (from the active set) at the end of merging. afterwards, the
// high bit indicates that it was chosen, and the cidx is a valid index into
// the final color table. it is otherwise a link to the merged qnode.
// during initial filtering, qlink determines whether a node has fractured:
// if qlink is non-zero, it is a one-biased index to an onode.
// FIXME combine these once more, but for now to keep it easy, we have two.
// qlink links back into the octree.
uint16_t qlink;
uint16_t cidx;
} qnode;
// an octree-style node, used for fractured first-order nodes. the first
// bit is whether we're on the top or bottom of the R, then G, then B.
typedef struct onode {
qnode* q[8];
} onode;
// we set P2 based on whether there is any transparency in the sixel. if not,
// use SIXEL_P2_ALLOPAQUE (0), for faster drawing in certain terminals.
typedef enum {
SIXEL_P2_ALLOPAQUE = 0,
SIXEL_P2_TRANS = 1,
} sixel_p2_e;
// data for a single sixelband. a vector of sixel rows, one for each color
// represented within the band. we initially create a vector for every
// possible (quantized) color, and then collapse it.
typedef struct sixelband {
int size; // capacity FIXME if same for all, eliminate this
char** vecs; // array of vectors, many of which can be NULL
} sixelband;
// across the life of the sixel, we'll need to wipe and restore cells, without
// recourse to the original RGBA data. this is prohibitively expensive to do on
// the encoded data, since it might require expanding or collapsing sections in
// the middle (we could use a rope, but it would be annoying). instead, we keep
// for each sixelrow (i.e. for every 6 rows) a vector of colors and distinct
// encoded sections (i.e. *not* from some common long single allocation). this
// way, the encoded sections can be easily and cheaply changed (since they're
// small, and quickly indexed by sixelrow * color)). whenever we want to emit
// the sixel, we just gather all these dynamic sections and write them
// successively into the fbuf. this table can be built up in parallel, since
// it's isolated among sixelrows -- the sixelrow is then the natural work unit.
// this sixelmap is kept across the life of the sprixel; any longlived state
// must be here, whereas state necessary only for rendering ought be in qstate.
typedef struct sixelmap {
int colors;
int sixelbands;
sixelband* bands; // |sixelbands| collections of sixel vectors
sixel_p2_e p2; // set to SIXEL_P2_TRANS if we have transparent pixels
} sixelmap;
typedef struct qstate {
int refcount; // initialized to worker count
atomic_int bandbuilder; // threads take bands as their work unit
// we always work in terms of quantized colors (quantization is the first
// step of rendering), using indexes into the derived palette. the actual
// palette need only be stored during the initial render, since the sixel
// header can be preserved, and the palette is unchanged by wipes/restores.
unsigned char* table; // |colors| x RGBSIZE components
qnode* qnodes;
onode* onodes;
unsigned dynnodes_free;
unsigned dynnodes_total;
unsigned onodes_free;
unsigned onodes_total;
const struct blitterargs* bargs;
const uint32_t* data;
int linesize;
sixelmap* smap;
// these are the leny and lenx passed to sixel_blit(), which are likely
// different from those reachable through bargs->len{y,x}!
int leny, lenx;
} qstate;
// a work_queue per worker thread. if used == WORKERDEPTH, this thread is
// backed up, and we cannot enqueue to it. writeto wraps around the array.
typedef struct work_queue {
qstate* qstates[WORKERDEPTH];
unsigned writeto;
unsigned used;
struct sixel_engine* sengine;
} work_queue;
// we keep a few worker threads (POPULATION) spun up to assist with
// quantization. each has an array of up to WORKERDEPTH qstates to work on.
typedef struct sixel_engine {
pthread_mutex_t lock;
pthread_cond_t cond;
work_queue queues[POPULATION];
pthread_t tids[POPULATION];
bool done;
} sixel_engine;
// enqueue |qs| to any workers with available space. the number of workers with
// a reference will be stored in |qs|->refcount.
static void
enqueue_to_workers(sixel_engine* eng, qstate* qs){
if(eng == NULL){
return;
}
int usecount = 0;
pthread_mutex_lock(&eng->lock);
for(int i = 0 ; i < POPULATION ; ++i){
work_queue* wq = &eng->queues[i];
if(wq->used < WORKERDEPTH){
wq->qstates[wq->writeto] = qs;
++wq->used;
++usecount;
if(++wq->writeto == WORKERDEPTH){
wq->writeto = 0;
}
}
}
qs->refcount = usecount;
pthread_mutex_unlock(&eng->lock);
if(usecount){
pthread_cond_broadcast(&eng->cond);
}
}
// block until all workers have finished up with |qs|
static void
block_on_workers(sixel_engine* eng, qstate* qs){
if(eng == NULL){
return;
}
pthread_mutex_lock(&eng->lock);
while(qs->refcount){
pthread_cond_wait(&eng->cond, &eng->lock);
}
pthread_mutex_unlock(&eng->lock);
}
// returns the number of individual sixels necessary to represent the specified
// pixel geometry. these might encompass more pixel rows than |dimy| would
// suggest, up to the next multiple of 6 (i.e. a single row becomes a 6-row
// bitmap; as do two, three, four, five, or six rows). input is scaled geometry.
static inline int
sixelcount(int dimy, int dimx){
return (dimy + 5) / 6 * dimx;
}
// returns the number of sixel bands (horizontal series of sixels, aka 6 rows)
// for |dimy| source rows. sixels are encoded as a series of sixel bands.
static inline int
sixelbandcount(int dimy){
return sixelcount(dimy, 1);
}
// whip up a sixelmap sans data for the specified pixel geometry and color
// register count.
static sixelmap*
sixelmap_create(int dimy){
sixelmap* ret = malloc(sizeof(*ret));
if(ret){
ret->p2 = SIXEL_P2_ALLOPAQUE;
// they'll be initialized by their workers, possibly in parallel
ret->sixelbands = sixelbandcount(dimy);
ret->bands = malloc(sizeof(*ret->bands) * ret->sixelbands);
if(ret->bands == NULL){
free(ret);
return NULL;
}
for(int i = 0 ; i < ret->sixelbands ; ++i){
ret->bands[i].size = 0;
}
ret->colors = 0;
}
return ret;
}
static inline void
sixelband_free(sixelband* s){
for(int j = 0 ; j < s->size ; ++j){
free(s->vecs[j]);
}
free(s->vecs);
}
void sixelmap_free(sixelmap *s){
if(s){
for(int i = 0 ; i < s->sixelbands ; ++i){
sixelband_free(&s->bands[i]);
}
free(s->bands);
free(s);
}
}
// convert rgb [0..255] to sixel [0..99]
static inline unsigned
ss(unsigned c){
unsigned r = round(c * 100.0 / 255); // use real [0..100] scaling
return r > 99 ? 99: r;
}
// get the keys for an rgb point. the returned value is on [0..999], and maps
// to a static qnode. the second value is on [0..7], and maps within the
// fractured onode (if necessary).
static inline unsigned
qnode_keys(unsigned r, unsigned g, unsigned b, unsigned *skey){
unsigned ssr = ss(r);
unsigned ssg = ss(g);
unsigned ssb = ss(b);
unsigned ret = ssr / 10 * 100 + ssg / 10 * 10 + ssb / 10;
*skey = (((ssr % 10) / 5) << 2u) +
(((ssg % 10) / 5) << 1u) +
((ssb % 10) / 5);
//fprintf(stderr, "0x%02x 0x%02x 0x%02x %02u %02u %02u %u %u\n", r, g, b, ssr, ssg, ssb, ret, *skey);
return ret;
}
// have we been chosen for the color table?
static inline bool
chosen_p(const qnode* q){
return q->cidx & 0x8000u;
}
static inline unsigned
make_chosen(unsigned cidx){
return cidx | 0x8000u;
}
// get the cidx without the chosen bit
static inline unsigned
qidx(const qnode* q){
return q->cidx & ~0x8000u;
}
#define QNODECOUNT 1000
// create+zorch an array of QNODECOUNT qnodes. this is 1000 entries covering
// 1000 sixel colors each (we pretend 100 doesn't exist, working on [0..99],
// heh). in addition, at the end we allocate |colorregs| qnodes, to be used
// dynamically in "fracturing". the original QNODECOUNT nodes are a static
// octree, flattened into an array; the latter are used as an actual octree.
// we must have 8 dynnodes available for every onode we create, or we can run
// into a situation where we don't have an available dynnode
// (see insert_color()).
static qstate*
alloc_qstate(unsigned colorregs){
qstate* qs = malloc(sizeof(*qs));
if(qs){
qs->dynnodes_free = colorregs;
qs->dynnodes_total = qs->dynnodes_free;
if((qs->qnodes = malloc((QNODECOUNT + qs->dynnodes_total) * sizeof(qnode))) == NULL){
free(qs);
return NULL;
}
qs->onodes_free = qs->dynnodes_total / 8;
qs->onodes_total = qs->onodes_free;
if((qs->onodes = malloc(qs->onodes_total * sizeof(*qs->onodes))) == NULL){
free(qs->qnodes);
free(qs);
return NULL;
}
// don't technically need to clear the components, as we could
// check the pop, but it's hidden under the compulsory cache misses.
// we only initialize the static nodes, not the dynamic ones--we know
// when we pull a dynamic one that it needs its popcount initialized.
memset(qs->qnodes, 0, sizeof(qnode) * QNODECOUNT);
qs->table = NULL;
}
return qs;
}
// free internals of qstate object
static void
free_qstate(qstate *qs){
if(qs){
loginfo("freeing qstate");
free(qs->qnodes);
free(qs->onodes);
free(qs->table);
free(qs);
}
}
// insert a color from the source image into the octree.
static inline int
insert_color(qstate* qs, uint32_t pixel){
const unsigned r = ncpixel_r(pixel);
const unsigned g = ncpixel_g(pixel);
const unsigned b = ncpixel_b(pixel);
unsigned skey;
const unsigned key = qnode_keys(r, g, b, &skey);
assert(key < QNODECOUNT);
assert(skey < 8);
qnode* q = &qs->qnodes[key];
if(q->q.pop == 0 && q->qlink == 0){ // previously-unused node
q->q.comps[0] = r;
q->q.comps[1] = g;
q->q.comps[2] = b;
q->q.pop = 1;
++qs->smap->colors;
return 0;
}
onode* o;
// it's not a fractured node, but it's been used. check to see if we
// match the secondary key of what's here.
if(q->qlink == 0){
unsigned skeynat;
qnode_keys(q->q.comps[0], q->q.comps[1], q->q.comps[2], &skeynat);
if(skey == skeynat){
++q->q.pop; // pretty good match
return 0;
}
// we want to fracture. if we have no onodes, though, we can't.
// we also need at least one dynamic qnode. note that this means we might
// open an onode just to fail to insert our current lookup; that's fine;
// it's a symmetry between creation and extension.
if(qs->dynnodes_free == 0 || qs->onodes_free == 0){
//fprintf(stderr, "NO FREE ONES %u\n", key);
++q->q.pop; // not a great match, but we're already scattered
return 0;
}
// get the next free onode and zorch it out
o = qs->onodes + qs->onodes_total - qs->onodes_free;
//fprintf(stderr, "o: %p obase: %p %u\n", o, qs->onodes, qs->onodes_total - qs->onodes_free);
memset(o, 0, sizeof(*o));
// get the next free dynnode and assign it to o, account for dnode
o->q[skeynat] = &qs->qnodes[QNODECOUNT + qs->dynnodes_total - qs->dynnodes_free];
--qs->dynnodes_free;
// copy over our own details
memcpy(o->q[skeynat], q, sizeof(*q));
// set qlink to one-biased index of the onode, and account for onode
q->qlink = qs->onodes_total - qs->onodes_free + 1;
--qs->onodes_free;
// reset our own population count
q->q.pop = 0;
}else{
// the node has already been fractured
o = qs->onodes + (q->qlink - 1);
}
if(o->q[skey]){
// our subnode is already present, huzzah. increase its popcount.
++o->q[skey]->q.pop;
return 0;
}
// we try otherwise to insert ourselves into o. this requires a free dynnode.
if(qs->dynnodes_free == 0){
//fprintf(stderr, "NO DYNFREE %u\n", key);
// this should never happen, because we always ought have 8 dynnodes for
// every possible onode.
return -1;
}
// get the next free dynnode and assign it to o, account for dnode
o->q[skey] = &qs->qnodes[QNODECOUNT + qs->dynnodes_total - qs->dynnodes_free];
--qs->dynnodes_free;
o->q[skey]->q.pop = 1;
o->q[skey]->q.comps[0] = r;
o->q[skey]->q.comps[1] = g;
o->q[skey]->q.comps[2] = b;
o->q[skey]->qlink = 0;
o->q[skey]->cidx = 0;
++qs->smap->colors;
//fprintf(stderr, "INSERTED[%u]: %u %u %u\n", key, q->q.comps[0], q->q.comps[1], q->q.comps[2]);
return 0;
}
// resolve the input color to a color table index following any postprocessing
// of the octree.
static inline int
find_color(const qstate* qs, uint32_t pixel){
const unsigned r = ncpixel_r(pixel);
const unsigned g = ncpixel_g(pixel);
const unsigned b = ncpixel_b(pixel);
unsigned skey;
const unsigned key = qnode_keys(r, g, b, &skey);
const qnode* q = &qs->qnodes[key];
if(q->qlink && q->q.pop == 0){
if(qs->onodes[q->qlink - 1].q[skey]){
q = qs->onodes[q->qlink - 1].q[skey];
}else{
logpanic("internal error: no color for 0x%016x", pixel);
return -1;
}
}
return qidx(q);
}
// the P2 parameter on a sixel specifies how unspecified pixels are drawn.
// if P2 is 1, unspecified pixels are transparent. otherwise, they're drawn
// as something else. some terminals (e.g. foot) can draw more quickly if
// P2 is 0, so we set that when we have no transparent pixels -- i.e. when
// all TAM entries are 0. P2 is at a fixed location in the sixel header.
// obviously, the sixel must already exist.
static inline void
change_p2(char* sixel, sixel_p2_e value){
sixel[4] = value + '0';
}
static inline void
write_rle(char* vec, int* voff, int rle, int rep){
if(rle > 2){
*voff += sprintf(vec + *voff, "!%d", rle);
}else if(rle == 2){
vec[(*voff)++] = rep;
}
if(rle){
vec[(*voff)++] = rep;
}
vec[*voff] = '\0';
}
// one for each color in the band we're building. |rle| tracks the number of
// consecutive unwritten instances of the current non-0 rep, which is itself
// tracked in |rep|. |wrote| tracks the number of sixels written out for this
// color. whenever we get a new rep (this only happens for non-zero reps),
// we must write any old rle rep, plus any zero-reps since then.
struct band_extender {
int length; // current length of the vector
int rle; // current rep count of non-zero sixel for this color
int wrote; // number of sixels we've written out
int rep; // representation, 0..63
};
// add the supplied rle section to the appropriate vector, which might
// need to be created. we are writing out [bes->wrote, curx) (i.e. curx
// ought *not* describe the |bes| element, and ought equal |dimx| when
// finalizing the band). caller must update bes->wrote afterwards!
static inline char*
sixelband_extend(char* vec, struct band_extender* bes, int dimx, int curx){
assert(dimx >= bes->rle);
assert(0 <= bes->rle);
assert(0 <= bes->rep);
assert(64 > bes->rep);
if(vec == NULL){
// FIXME for now we make it as big as it could possibly need to be. ps,
// don't try to just base it off how far in we are; wipe/restore could
// change that!
if((vec = malloc(dimx + 1)) == NULL){
return NULL;
}
}
// rle will equal 0 if this is our first non-zero rep, at a non-zero x;
// in that case, rep is guaranteed to be 0; catch it at the bottom.
write_rle(vec, &bes->length, bes->rle, bes->rep + 63);
int clearlen = curx - (bes->rle + bes->wrote);
write_rle(vec, &bes->length, clearlen, '?');
return vec;
}
// write to this cell's auxvec, backing up the pixels cleared by a wipe. wipes
// are requested at cell granularity, broken down into sixelbands, broken down
// by color, and then finally effected at the sixel RLE level. we're thus in
// any given call handling a horizontal contiguous range of sixels for a single
// color. the x range is wholly within the cell to be wiped, but the y range
// might not be, since cells and bands don't necessarily line up. |y| ought be
// the row of the first pixel of the *band*.
//
// we thus need:
// - the starting and ending true x positions for the *portion of this sixel
// contained within the wiped cell*.
// - the true y position at which the sixel starts.
// - the previous sixel rep and the masked sixel rep--the difference between
// the two tells us which rows (offset from y) need be written. they ought
// be the binary forms, not the presentation forms (i.e. [0..63]).
// - the cell-pixel geometry, necessary for computing offset into the auxvec.
// - the color.
//
// precondition: mask is a bitwise proper subset of rep
//
// we find which [1..6] of six rows are affected by examining the difference
// between |rep| and |masked|, the sixel's row within the cell by taking |y|
// modulo |cellpxy|, and the position within the auxvec by multiplying that
// result by |cellpxx| and adding |x| modulo |cellpxx|. we set |len| pixels.
static inline void
write_auxvec(uint8_t* auxvec, uint16_t color, int endy, int y, int x, int len,
char rep, char masked, int cellpxy, int cellpxx){
rep -= 63;
masked -= 63;
const char diff = rep ^ masked;
//fprintf(stderr, "AUXVEC WRITE[%hu] ey: %d y/x: %d/%d:%d r: 0x%x m: 0x%x d: 0x%x total %d\n", color, endy, y, x, len, rep, masked, diff, cellpxy * cellpxx);
const int xoff = x % cellpxx;
const int yoff = y % cellpxy;
int dy = 0;
for(char bitselector = 1 ; bitselector < 0x40 ; bitselector <<= 1u, ++dy){
if((diff & bitselector) == 0){
//if(diff == 0x20)fprintf(stderr, "diff: 0x%x bs: %d\n", diff, bitselector);
continue;
}
if(yoff + dy == endy){ // reached the next cell below
//if(diff == 0x20)fprintf(stderr, "BOUNCING! 0x%x bs: %d %d > %d\n", diff, bitselector, yoff + dy, cellpxy);
break;
}
//fprintf(stderr, " writing to auxrow %d (%d)\n", yoff + dy, bitselector);
const int idx = (((yoff + dy) % cellpxy) * cellpxx + xoff) * AUXVECELEMSIZE;
//fprintf(stderr, " xoff: %d yoff: %d dy: %d ydy: %d idx: %d\n", xoff, yoff, dy, yoff + dy, idx);
for(int i = 0 ; i < len ; ++i){
memcpy(&auxvec[idx + i * AUXVECELEMSIZE], &color, AUXVECELEMSIZE);
}
}
}
// wipe the color within this band from startx to endx - 1, from starty to
// endy - 1 (0-offset in the band, a cell-sized region), writing out the
// auxvec. mask is the allowable sixel, y-wise. returns a positive number if
// pixels were wiped.
static inline int
wipe_color(sixelband* b, int color, int y, int endy, int startx, int endx,
char mask, int dimx, uint8_t* auxvec,
int cellpxy, int cellpxx){
const char* vec = b->vecs[color];
if(vec == NULL){
return 0; // no work to be done here
}
int wiped = 0;
char* newvec = malloc(dimx + 1);
if(newvec == NULL){
return -1;
}
//fprintf(stderr, "color: %d Y: %d-%d X: %d-%d\n", color, starty, endy, startx, endx);
//fprintf(stderr, "s/e: %d/%d mask: %02x WIPE: [%s]\n", starty, endy, mask, vec);
// we decode the color within the sixelband, and rebuild it without the
// wiped pixels.
int rle = 0; // the repetition number for this element
// the x coordinate through which we've checked this band. if x + rle is
// less than startx, this element cannot be affected by the wipe.
// otherwise, starting at startx, it can be affected. once x >= endx, we
// are done, and can copy any remaining elements blindly.
int x = 0;
int voff = 0;
while(*vec){
if(isdigit(*vec)){
rle *= 10;
rle += (*vec - '0');
}else if(*vec == '!'){
rle = 0;
}else{
if(rle == 0){
rle = 1;
}
char rep = *vec;
char masked = ((rep - 63) & mask) + 63;
//fprintf(stderr, "X/RLE/ENDX: %d %d %d\n", x, rle, endx);
if(x + rle <= startx){ // not wiped material; reproduce as-is
write_rle(newvec, &voff, rle, rep);
x += rle;
}else if(masked == rep){ // not changed by wipe; reproduce as-is
write_rle(newvec, &voff, rle, rep);
x += rle;
}else{ // changed by wipe; might have to break it up
wiped = 1;
if(x < startx){
write_rle(newvec, &voff, startx - x, rep);
rle -= startx - x;
x = startx;
}
if(x + rle >= endx){
// FIXME this might equal the prev/next rep, and we ought combine
//fprintf(stderr, "************************* %d %d %d\n", endx - x, x, rle);
write_rle(newvec, &voff, endx - x, masked);
write_auxvec(auxvec, color, endy, y, x, endx - x, rep, masked, cellpxy, cellpxx);
rle -= endx - x;
x = endx;
}else{
write_rle(newvec, &voff, rle, masked);
write_auxvec(auxvec, color, endy, y, x, rle, rep, masked, cellpxy, cellpxx);
x += rle;
rle = 0;
}
if(rle){
write_rle(newvec, &voff, rle, rep);
x += rle;
}
}
rle = 0;
}
++vec;
if(x >= endx){
strcpy(newvec + voff, vec); // there is always room
break;
}
}
//if(strcmp(newvec, b->vecs[color])) fprintf(stderr, "WIPED %d y [%d..%d) x [%d..%d) mask: %d [%s]\n", color, starty, endy, startx, endx, mask, newvec);
free(b->vecs[color]);
if(voff == 0){
// FIXME check for other null vectors; free such, and assign NULL
free(newvec);
newvec = NULL;
}
b->vecs[color] = newvec;
return wiped;
}
// wipe the band from startx to endx - 1, from starty to endy - 1. returns the
// number of pixels actually wiped.
static inline int
wipe_band(sixelmap* smap, int band, int startx, int endx,
int starty, int endy, int dimx, int cellpxy, int cellpxx,
uint8_t* auxvec){
int wiped = 0;
// get 0-offset start and end row bounds for our band.
const int sy = band * 6 < starty ? starty - band * 6 : 0;
const int ey = (band + 1) * 6 > endy ? 6 - ((band + 1) * 6 - endy) : 6;
// we've got a mask that we'll AND with the decoded sixels; set it to
// 0 wherever we're wiping.
unsigned char mask = 63;
// knock out a bit for each row we're wiping within the band
for(int i = 0 ; i < 6 ; ++i){
if(i >= sy && i < ey){
mask &= ~(1u << i);
}
}
//fprintf(stderr, "******************** BAND %d MASK 0x%x ********************8\n", band, mask);
sixelband* b = &smap->bands[band];
// offset into map->data where our color starts
for(int i = 0 ; i < b->size ; ++i){
wiped += wipe_color(b, i, band * 6, endy, startx, endx, mask,
dimx, auxvec, cellpxy, cellpxx);
}
return wiped;
}
// we return -1 because we're not doing a proper wipe -- that's not possible
// using sixel. we just mark it as partially transparent, so that if it's
// redrawn, it's redrawn using P2=1.
int sixel_wipe(sprixel* s, int ycell, int xcell){
//fprintf(stderr, "WIPING %d/%d\n", ycell, xcell);
uint8_t* auxvec = sixel_trans_auxvec(ncplane_pile(s->n));
if(auxvec == NULL){
return -1;
}
const int cellpxy = ncplane_pile(s->n)->cellpxy;
const int cellpxx = ncplane_pile(s->n)->cellpxx;
sixelmap* smap = s->smap;
const int startx = xcell * cellpxx;
const int starty = ycell * cellpxy;
int endx = ((xcell + 1) * cellpxx);
if(endx >= s->pixx){
endx = s->pixx;
}
int endy = ((ycell + 1) * cellpxy);
if(endy >= s->pixy){
endy = s->pixy;
}
const int startband = starty / 6;
const int endband = (endy - 1) / 6;
//fprintf(stderr, "y/x: %d/%d bands: %d-%d start: %d/%d end: %d/%d\n", ycell, xcell, startband, endband - 1, starty, startx, endy, endx);
// walk through each color, and wipe the necessary sixels from each band
int w = 0;
for(int b = startband ; b <= endband ; ++b){
w += wipe_band(smap, b, startx, endx, starty, endy, s->pixx,
cellpxy, cellpxx, auxvec);
}
if(w){
s->wipes_outstanding = true;
}
change_p2(s->glyph.buf, SIXEL_P2_TRANS);
assert(NULL == s->n->tam[s->dimx * ycell + xcell].auxvector);
s->n->tam[s->dimx * ycell + xcell].auxvector = auxvec;
// FIXME this invalidation ought not be necessary, since we're simply
// wiping, and thus a glyph is going to be printed over whatever we've
// just destroyed. in alacritty, however, this isn't sufficient to knock
// out a graphic; we need repaint with the transparency.
// see https://github.com/dankamongmen/notcurses/issues/2142
int absx, absy;
ncplane_abs_yx(s->n, &absy, &absx);
sprixel_invalidate(s, absy, absx);
return 0;
}
// rebuilds the auxiliary vectors, and scrubs the actual pixels, following
// extraction of the palette. doing so allows the new frame's pixels to
// contribute to the solved palette, even if they were wiped in the previous
// frame. pixels ought thus have been set up in sixel_blit(), despite TAM
// entries in the ANNIHILATED state.
static int
scrub_color_table(sprixel* s){
if(s->n && s->n->tam){
// we use the sprixel cell geometry rather than the plane's because this
// is called during our initial blit, before we've resized the plane.
for(unsigned y = 0 ; y < s->dimy ; ++y){
for(unsigned x = 0 ; x < s->dimx ; ++x){
unsigned txyidx = y * s->dimx + x;
sprixcell_e state = s->n->tam[txyidx].state;
if(state == SPRIXCELL_ANNIHILATED || state == SPRIXCELL_ANNIHILATED_TRANS){
//fprintf(stderr, "POSTEXTRACT WIPE %d/%d\n", y, x);
sixel_wipe(s, y, x);
}
}
}
}
return 0;
}
// goes through the needs_refresh matrix, and damages cells needing refreshing.
void sixel_refresh(const ncpile* p, sprixel* s){
if(s->needs_refresh == NULL){
return;
}
int absy, absx;
ncplane_abs_yx(s->n, &absy, &absx);
for(unsigned y = 0 ; y < s->dimy ; ++y){
const unsigned yy = absy + y;
for(unsigned x = 0 ; x < s->dimx ; ++x){
unsigned idx = y * s->dimx + x;
if(s->needs_refresh[idx]){
const unsigned xx = absx + x;
if(xx < p->dimx && yy < p->dimy){
unsigned ridx = yy * p->dimx + xx;
struct crender *r = &p->crender[ridx];
r->s.damaged = 1;
}
}
}
}
free(s->needs_refresh);
s->needs_refresh = NULL;
}
// when we first cross into a new cell, we check its old state, and if it
// was transparent, set the rmatrix low. otherwise, set it high. this should
// only be called for the first pixel in each cell.
static inline void
update_rmatrix(unsigned char* rmatrix, int txyidx, const tament* tam){
if(rmatrix == NULL){
return;
}
sprixcell_e state = tam[txyidx].state;
if(state == SPRIXCELL_TRANSPARENT || state > SPRIXCELL_ANNIHILATED){
rmatrix[txyidx] = 0;
}else{
rmatrix[txyidx] = 1;
}
}
static int
qnodecmp(const void* q0, const void* q1){
const qnode* qa = q0;
const qnode* qb = q1;
return qa->q.pop < qb->q.pop ? -1 : qa->q.pop == qb->q.pop ? 0 : 1;
}
// from the initial set of QNODECOUNT qnodes, extract the number of active
// ones -- our initial (reduced) color count -- and sort. heap allocation.
// precondition: colors > 0
static qnode*
get_active_set(qstate* qs, uint32_t colors){
qnode* act = malloc(sizeof(*act) * colors);
unsigned targidx = 0;
// filter the initial qnodes for pop != 0
unsigned total = QNODECOUNT + (qs->dynnodes_total - qs->dynnodes_free);
//fprintf(stderr, "TOTAL IS %u WITH %u COLORS\n", total, colors);
for(unsigned z = 0 ; z < total && targidx < colors ; ++z){
//fprintf(stderr, "EXTRACT? [%04u] pop %u\n", z, qs->qnodes[z].q.pop);
if(qs->qnodes[z].q.pop){
memcpy(&act[targidx], &qs->qnodes[z], sizeof(*act));
// link it back to the original node's position in the octree
//fprintf(stderr, "LINKING %u to %u\n", targidx, z);
act[targidx].qlink = z;
++targidx;
}else if(qs->qnodes[z].qlink){
const struct onode* o = &qs->onodes[qs->qnodes[z].qlink - 1];
// FIXME i don't think we need the second conditional? in a perfect world?
for(unsigned s = 0 ; s < 8 && targidx < colors ; ++s){
//fprintf(stderr, "o: %p qlink: %u\n", o, qs->qnodes[z].qlink - 1);
if(o->q[s]){
memcpy(&act[targidx], o->q[s], sizeof(*act));
//fprintf(stderr, "O-LINKING %u to %ld[%u]\n", targidx, o->q[s] - qs->qnodes, s);
act[targidx].qlink = o->q[s] - qs->qnodes;
++targidx;
}
}
}
}
//fprintf(stderr, "targidx: %u colors: %u\n", targidx, colors);
assert(targidx == colors);
qsort(act, colors, sizeof(*act), qnodecmp);
return act;
}
static inline int
find_next_lowest_chosen(const qstate* qs, int z, int i, const qnode** hq){
//fprintf(stderr, "FIRST CHOSEN: %u %d\n", z, i);
do{
const qnode* h = &qs->qnodes[z];
//fprintf(stderr, "LOOKING AT %u POP %u QLINK %u CIDX %u\n", z, h->q.pop, h->qlink, h->cidx);
if(h->q.pop == 0 && h->qlink){
const onode* o = &qs->onodes[h->qlink - 1];
while(i >= 0){
h = o->q[i];
if(h && chosen_p(h)){
*hq = h;
//fprintf(stderr, "NEW HQ: %p RET: %u\n", *hq, z * 8 + i);
return z * 8 + i;
}
if(++i == 8){
break;
}
}
}else{
if(chosen_p(h)){
*hq = h;
//fprintf(stderr, "NEW HQ: %p RET: %u\n", *hq, z * 8);
return z * 8;
}
}
++z;
i = 0;
}while(z < QNODECOUNT);
//fprintf(stderr, "RETURNING -1\n");
return -1;
}
static inline void
choose(qstate* qs, qnode* q, int z, int i, int* hi, int* lo,
const qnode** hq, const qnode** lq){
if(!chosen_p(q)){
//fprintf(stderr, "NOT CHOSEN: %u %u %u %u\n", z, qs->qnodes[z].qlink, qs->qnodes[z].q.pop, qs->qnodes[z].cidx);
if(z * 8 > *hi){
*hi = find_next_lowest_chosen(qs, z, i, hq);
}
int cur = z * 8 + (i >= 0 ? i : 4);
if(*lo == -1){
q->cidx = qidx(*hq);
}else if(*hi == -1 || cur - *lo < *hi - cur){
q->cidx = qidx(*lq);
}else{
q->cidx = qidx(*hq);
}
}else{
*lq = q;
*lo = z * 8;
}
}
// we must reduce the number of colors until we're using less than or equal
// to the number of color registers.
static inline int
merge_color_table(qstate* qs){
if(qs->smap->colors == 0){
return 0;
}
qnode* qactive = get_active_set(qs, qs->smap->colors);
if(qactive == NULL){
return -1;
}
// assign color table entries to the most popular colors. use the lowest
// color table entries for the most popular ones, as they're the shortest
// (this is not necessarily an optimizing huristic, but it'll do for now).
int cidx = 0;
//fprintf(stderr, "colors: %u cregs: %u\n", qs->colors, colorregs);
for(int z = qs->smap->colors - 1 ; z >= 0 ; --z){
if(qs->smap->colors >= qs->bargs->u.pixel.colorregs){
if(cidx == qs->bargs->u.pixel.colorregs){
break; // we just ran out of color registers
}
}
qs->qnodes[qactive[z].qlink].cidx = make_chosen(cidx);
++cidx;
}
free(qactive);
if(qs->smap->colors > qs->bargs->u.pixel.colorregs){
// tend to those which couldn't get a color table entry. we start with two
// values, lo and hi, initialized to -1. we iterate over the *static* qnodes,
// descending into onodes to check their qnodes. we thus iterate over all
// used qnodes, in order (and also unused static qnodes). if the node is
// empty, continue. if it is chosen, replace lo. otherwise, if hi is less
// than z, we need find the next lowest chosen one. if there is no next
// lowest, hi is reset to -1. otherwise, set hi. once we have the new hi > z,
// determine which of hi and lo are closer to z, discounting -1 values, and
// link te closer one to z. a toplevel node is worth 8 in terms of distance;
// and lowlevel node is worth 1.
int lo = -1;
int hi = -1;
const qnode* lq = NULL;
const qnode* hq = NULL;
for(int z = 0 ; z < QNODECOUNT ; ++z){
if(qs->qnodes[z].q.pop == 0){
if(qs->qnodes[z].qlink == 0){
continue; // unused
}
// process the onode
const onode* o = &qs->onodes[qs->qnodes[z].qlink - 1];
for(int i = 0 ; i < 8 ; ++i){
if(o->q[i]){
choose(qs, o->q[i], z, i, &hi, &lo, &hq, &lq);
}
}
}else{
choose(qs, &qs->qnodes[z], z, -1, &hi, &lo, &hq, &lq);
}
}
qs->smap->colors = qs->bargs->u.pixel.colorregs;
}
return 0;
}
static inline void
load_color_table(const qstate* qs){
int loaded = 0;
int total = QNODECOUNT + (qs->dynnodes_total - qs->dynnodes_free);
for(int z = 0 ; z < total && loaded < qs->smap->colors ; ++z){
const qnode* q = &qs->qnodes[z];
if(chosen_p(q)){
qs->table[RGBSIZE * qidx(q) + 0] = ss(q->q.comps[0]);
qs->table[RGBSIZE * qidx(q) + 1] = ss(q->q.comps[1]);
qs->table[RGBSIZE * qidx(q) + 2] = ss(q->q.comps[2]);
++loaded;
}
}
//fprintf(stderr, "loaded: %u colors: %u\n", loaded, qs->colors);
assert(loaded == qs->smap->colors);
}
// build up a sixel band from (up to) 6 rows of the source RGBA.
static inline int
build_sixel_band(qstate* qs, int bnum){
//fprintf(stderr, "building band %d\n", bnum);
sixelband* b = &qs->smap->bands[bnum];
b->size = qs->smap->colors;
size_t bsize = sizeof(*b->vecs) * b->size;
size_t mlen = qs->smap->colors * sizeof(struct band_extender);
struct band_extender* meta = malloc(mlen);
if(meta == NULL){
return -1;
}
b->vecs = malloc(bsize);
if(b->vecs == NULL){
free(meta);
return -1;
}
memset(b->vecs, 0, bsize);
memset(meta, 0, mlen);
const int ystart = qs->bargs->begy + bnum * 6;
const int endy = (bnum + 1 == qs->smap->sixelbands ?
qs->leny - qs->bargs->begy : ystart + 6);
struct {
int color; // 0..colormax
int rep; // non-zero representation, 1..63
} active[6];
// we're going to advance horizontally through the sixelband
int x;
// FIXME we could greatly clean this up by tracking, for each color, the active
// rep and the number of times we've seen it...but only write it out either (a)
// when the rep changes (b) when we get the color again after a gap or (c) at the
// end. that way we wouldn't need maintain these prevactive/active sets...
for(x = qs->bargs->begx ; x < (qs->bargs->begx + qs->lenx) ; ++x){ // pixel column
// there are at most 6 colors represented in any given sixel. at each
// sixel, we need to *start tracking* new colors, and colors which changed
// their representation. we also write out what we previously tracked for
// this color: possibly a non-zero rep, possibly followed by a zero-rep (we
// can have zero, either, or both).
int activepos = 0; // number of active entries used
for(int y = ystart ; y < endy ; ++y){
const uint32_t* rgb = (qs->data + (qs->linesize / 4 * y) + x);
if(rgba_trans_p(*rgb, qs->bargs->transcolor)){
continue;
}
int cidx = find_color(qs, *rgb);
if(cidx < 0){
free(meta);
return -1;
}
int act;
for(act = 0 ; act < activepos ; ++act){
if(active[act].color == cidx){
active[act].rep |= (1u << (y - ystart));
break;
}
}
if(act == activepos){ // didn't find it; create new entry