Skip to content

Commit

Permalink
[sixel] first-order merge from octrees #2503
Browse files Browse the repository at this point in the history
  • Loading branch information
dankamongmen committed Jan 1, 2022
1 parent 4949eb9 commit 8ccf0a6
Showing 1 changed file with 71 additions and 34 deletions.
105 changes: 71 additions & 34 deletions src/lib/sixel.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,33 @@ typedef struct qsample {
// lowest samples for each node. maybe we do low/high in the future, who knows?
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.
// 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;

// have we been chosen for the color table?
static inline bool
chosen_p(const qnode* q){
return q->cidx & 0xf000u;
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 & ~0xf000u;
return q->cidx & ~0x8000u;
}

#define QNODECOUNT (1lu << 12)
Expand Down Expand Up @@ -79,7 +91,7 @@ find_color(const qnode* qtree, uint32_t pixel){
const unsigned key = color_key(r, g, b);
const qnode* q = &qtree[key];
while(!chosen_p(q)){
//fprintf(stderr, "qidx[%u (%u)]: %u\n", key, pixel, qidx(q));
//fprintf(stderr, "qidx[%u (0x%08x)]: %u\n", key, pixel, qidx(q));
const qnode* newq = &qtree[qidx(q)];
assert(newq != q);
q = newq;
Expand Down Expand Up @@ -351,46 +363,72 @@ update_rmatrix(unsigned char* rmatrix, int txyidx, const tament* tam){
}
}

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(qnode* q, uint32_t colors){
qnode* act = malloc(sizeof(*act) * colors);
unsigned targidx = 0;
// filter the initial qnodes for pop != 0
for(unsigned z = 0 ; z < QNODECOUNT && targidx < colors ; ++z){
if(q[z].q.pop){
memcpy(&act[targidx], &q[z], sizeof(*act));
// link it back to the original node's position in the octree
act[targidx].qlink = z;
++targidx;
}
}
qsort(act, colors, sizeof(*act), qnodecmp);
return act;
}

// 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(qnode* q, uint32_t* colors, uint32_t colorregs){
while(*colors > colorregs){
//fprintf(stderr, "MERGING: %u > %u\n", *colors, colorregs);
// iterate over the chunks. find small ones and merge them FIXME.
for(unsigned z = 0 ; z < QNODECOUNT ; ++z){
if(q[z].q.pop){
fprintf(stderr, "COLOR: %u/%u/%u pop: %u\n",
q[z].q.comps[0], q[z].q.comps[1], q[z].q.comps[2], q[z].q.pop);
}
}
break; // FIXME
if(*colors == 0){
return 0;
}
if(*colors > colorregs){ // FIXME
qnode* qactive = get_active_set(q, *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).
unsigned cidx = 0;
for(unsigned z = 0 ; z < QNODECOUNT ; ++z){
if(q[z].q.pop){
q[z].cidx = 0xf000u | cidx;
++cidx;
//fprintf(stderr, "QIDX[%u]: %u\n", z, qidx(&q[z]));
//fprintf(stderr, "colors: %u cregs: %u\n", *colors, colorregs);
for(int z = *colors - 1 ; z >= 0 ; --z){
if(*colors >= colorregs){
if(cidx == colorregs){
break; // we just ran out of color registers
}
}
}
return 0;
}

// expand nodes having at least two colors until we've used all available
// color registers, or all colors are exact.
// FIXME until we track multiple colors per chunk, no point in relaxing!
static inline void
relax_color_table(qnode* q, uint32_t* colors, uint32_t colorregs){
bool workavail = false; // were there any to relax?
while(*colors < colorregs){
if(!workavail){
break;
qactive[z].cidx = make_chosen(cidx);
++cidx;
}
// tend to those which couldn't get a color table entry.
// FIXME for now they all go to the most popular. this must change.
for(unsigned z = 0 ; z < *colors ; ++z){
q[qactive[z].qlink].cidx = qactive[z].cidx;
if(!chosen_p(&q[qactive[z].qlink])){
q[qactive[z].qlink].cidx = qactive[*colors - 1].qlink;
//fprintf(stderr, "NOT CHOSEN: %u %u %u %u\n", z, qactive[z].qlink, qactive[z].q.pop, qactive[z].cidx);
}
}
if(*colors > colorregs){
*colors = colorregs;
}
free(qactive); // FIXME probably want to preserve for relaxation
return 0;
}

// convert rgb [0..255] to sixel [0..100]
Expand All @@ -404,14 +442,14 @@ load_color_table(const qnode* qtree, uint32_t colors, unsigned char* table){
uint32_t loaded = 0;
for(unsigned z = 0 ; z < QNODECOUNT && loaded < colors ; ++z){
const qnode* q = &qtree[z];
//fprintf(stderr, "%u\n", z);
if(chosen_p(q)){
table[CENTSIZE * qidx(q) + 0] = ss(q->q.comps[0]);
table[CENTSIZE * qidx(q) + 1] = ss(q->q.comps[1]);
table[CENTSIZE * qidx(q) + 2] = ss(q->q.comps[2]);
++loaded;
}
}
//fprintf(stderr, "loaded: %u colors: %u\n", loaded, colors);
assert(loaded == colors);
}

Expand Down Expand Up @@ -550,7 +588,6 @@ extract_color_table(const uint32_t* data, int linesize, int cols,
if(merge_color_table(q, &octets, stab->colorregs)){
return -1;
}
relax_color_table(q, &octets, stab->colorregs);
if(build_data_table(q, octets, stab, data, linesize, begy, begx, leny, lenx,
bargs->transcolor)){
return -1;
Expand Down

0 comments on commit 8ccf0a6

Please sign in to comment.