From ac866655db6ca8f32bbb8fe5f8b3a060bfdbcc7a Mon Sep 17 00:00:00 2001 From: nick black Date: Tue, 16 Mar 2021 02:37:21 -0400 Subject: [PATCH] [sixel] relaxation algorithm #1391 --- src/lib/blit.c | 11 ---- src/lib/internal.h | 11 ++++ src/lib/sixel.c | 140 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 121 insertions(+), 41 deletions(-) diff --git a/src/lib/blit.c b/src/lib/blit.c index 392253931..107c63dea 100644 --- a/src/lib/blit.c +++ b/src/lib/blit.c @@ -155,17 +155,6 @@ tria_blit(ncplane* nc, int linesize, const void* data, int begy, int begx, return total; } -// get a non-negative "distance" between two rgb values -static inline uint32_t -rgb_diff(unsigned r1, unsigned g1, unsigned b1, unsigned r2, unsigned g2, unsigned b2){ - uint32_t distance = 0; - distance += r1 > r2 ? r1 - r2 : r2 - r1; - distance += g1 > g2 ? g1 - g2 : g2 - g1; - distance += b1 > b2 ? b1 - b2 : b2 - b1; -//fprintf(stderr, "RGBDIFF %u %u %u %u %u %u: %u\n", r1, g1, b1, r2, g2, b2, distance); - return distance; -} - // once we find the closest pair of colors, we need look at the other two // colors, and determine whether either belongs with us rather with them. // if so, take the closer, and trilerp it in with us. otherwise, lerp the diff --git a/src/lib/internal.h b/src/lib/internal.h index a9bac3e4b..f5dbeca37 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -1228,6 +1228,17 @@ rgba_trans_p(unsigned alpha){ return false; } +// get a non-negative "manhattan distance" between two rgb values +static inline uint32_t +rgb_diff(unsigned r1, unsigned g1, unsigned b1, unsigned r2, unsigned g2, unsigned b2){ + uint32_t distance = 0; + distance += r1 > r2 ? r1 - r2 : r2 - r1; + distance += g1 > g2 ? g1 - g2 : g2 - g1; + distance += b1 > b2 ? b1 - b2 : b2 - b1; +//fprintf(stderr, "RGBDIFF %u %u %u %u %u %u: %u\n", r1, g1, b1, r2, g2, b2, distance); + return distance; +} + static inline uint64_t ncdirect_channels(const ncdirect* nc){ return nc->channels; diff --git a/src/lib/sixel.c b/src/lib/sixel.c index 93361a800..1cd7af128 100644 --- a/src/lib/sixel.c +++ b/src/lib/sixel.c @@ -97,6 +97,39 @@ find_color(sixeltable* stab, unsigned char comps[static RGBSIZE]){ //return ctable_to_dtable(stab->table + i * CENTSIZE); } +static void +update_deets(uint32_t rgb, cdetails* deets){ + unsigned char comps[RGBSIZE]; + deets->sums[0] += ncpixel_r(rgb); + deets->sums[1] += ncpixel_g(rgb); + deets->sums[2] += ncpixel_b(rgb); + comps[0] = ss(ncpixel_r(rgb), 0xff); + comps[1] = ss(ncpixel_g(rgb), 0xff); + comps[2] = ss(ncpixel_b(rgb), 0xff); + if(deets->count == 0){ + deets->lo[0] = deets->hi[0] = comps[0]; + deets->lo[1] = deets->hi[1] = comps[1]; + deets->lo[2] = deets->hi[2] = comps[2]; + }else{ + if(deets->hi[0] < comps[0]){ + deets->hi[0] = comps[0]; + }else if(deets->lo[0] > comps[0]){ + deets->lo[0] = comps[0]; + } + if(deets->hi[1] < comps[1]){ + deets->hi[1] = comps[1]; + }else if(deets->lo[1] > comps[1]){ + deets->lo[1] = comps[1]; + } + if(deets->hi[2] < comps[2]){ + deets->hi[2] = comps[2]; + }else if(deets->lo[2] > comps[2]){ + deets->lo[2] = comps[2]; + } + } + ++deets->count; +} + // no matter the input palette, we can always get a maximum of 64 colors if we // mask at 0xc0 on each component (this partitions each component into 4 chunks, // and 4 * 4 * 4 -> 64). so this will never overflow our color register table @@ -123,34 +156,7 @@ extract_color_table(const uint32_t* data, int linesize, int begy, int begx, return -1; } stab->data[c * stab->sixelcount + pos] |= (1u << (sy - visy)); - stab->deets[c].sums[0] += ncpixel_r(*rgb); - stab->deets[c].sums[1] += ncpixel_g(*rgb); - stab->deets[c].sums[2] += ncpixel_b(*rgb); - comps[0] = ss(ncpixel_r(*rgb), 0xff); - comps[1] = ss(ncpixel_g(*rgb), 0xff); - comps[2] = ss(ncpixel_b(*rgb), 0xff); - if(stab->deets[c].count == 0){ - stab->deets[c].lo[0] = stab->deets[c].hi[0] = comps[0]; - stab->deets[c].lo[1] = stab->deets[c].hi[1] = comps[1]; - stab->deets[c].lo[2] = stab->deets[c].hi[2] = comps[2]; - }else{ - if(stab->deets[c].hi[0] < comps[0]){ - stab->deets[c].hi[0] = comps[0]; - }else if(stab->deets[c].lo[0] > comps[0]){ - stab->deets[c].lo[0] = comps[0]; - } - if(stab->deets[c].hi[1] < comps[1]){ - stab->deets[c].hi[1] = comps[1]; - }else if(stab->deets[c].lo[1] > comps[1]){ - stab->deets[c].lo[1] = comps[1]; - } - if(stab->deets[c].hi[2] < comps[2]){ - stab->deets[c].hi[2] = comps[2]; - }else if(stab->deets[c].lo[2] > comps[2]){ - stab->deets[c].lo[2] = comps[2]; - } - } - ++stab->deets[c].count; + update_deets(*rgb, &stab->deets[c]); //fprintf(stderr, "color %d pos %d: 0x%x\n", c, pos, stab->data[c * stab->sixelcount + pos]); //fprintf(stderr, " sums: %u %u %u count: %d r/g/b: %u %u %u\n", stab->deets[c].sums[0], stab->deets[c].sums[1], stab->deets[c].sums[2], stab->deets[c].count, ncpixel_r(*rgb), ncpixel_g(*rgb), ncpixel_b(*rgb)); } @@ -160,10 +166,83 @@ extract_color_table(const uint32_t* data, int linesize, int begy, int begx, return 0; } -// relax segment |coloridx|. we must have room for a new color. +// run through the sixels matching color |src|, going to color |stab->colors|, +// keeping those under |r||g||b|, and putting those above it into the new +// color. rebuilds both sixel groups and color details. +static void +unzip_color(const uint32_t* data, int linesize, int begy, int begx, + int leny, int lenx, sixeltable* stab, int src, + unsigned r, unsigned g, unsigned b){ + unsigned char* tcrec = stab->table + CENTSIZE * stab->colors; + dtable_to_ctable(stab->colors, tcrec); + cdetails* deets = stab->deets + src; + cdetails* targdeets = stab->deets + stab->colors; + unsigned char* crec = stab->table + CENTSIZE * src; + int didx = ctable_to_dtable(crec); + unsigned char* srcsixels = stab->data + stab->sixelcount * didx; + unsigned char* dstsixels = stab->data + stab->sixelcount * stab->colors; +fprintf(stderr, "counts: src: %d dst: %d\n", deets->count, targdeets->count); + int sixel = 0; + memset(deets, 0, sizeof(*deets)); + for(int visy = begy ; visy < (begy + leny) ; visy += 6){ + for(int visx = begx ; visx < (begx + lenx) ; visx += 1, ++sixel){ + if(srcsixels[sixel]){ + for(int sy = visy ; sy < (begy + leny) && sy < visy + 6 ; ++sy){ + if(srcsixels[sixel] & (1u << (sy - visy))){ + const uint32_t* rgb = (const uint32_t*)(data + (linesize / 4 * sy) + visx); + unsigned char comps[RGBSIZE]; + break_sixel_comps(comps, *rgb, 0xff); + if(comps[0] > r || comps[1] > g || comps[2] > b){ + ++targdeets->count; + dstsixels[sixel] |= (1u << (sy - visy)); + srcsixels[sixel] &= ~(1u << (sy - visy)); + update_deets(*rgb, targdeets); +//fprintf(stderr, "%u/%u/%u comps: [%u/%u/%u]\n", r, g, b, comps[0], comps[1], comps[2]); +//fprintf(stderr, "match sixel %d %u %u\n", sixel, srcsixels[sixel], 1u << (sy - visy)); + }else{ + ++deets->count; + update_deets(*rgb, deets); + } + } + } + } + } + } +fprintf(stderr, "counts: src: %d dst: %d\n", deets->count, targdeets->count); +} + +// relax segment |coloridx|. we must have room for a new color. we find the +// biggest component gap, and split our color entry in half there. we know +// the elements can't go into any preexisting color entry, so the only +// choices are staying where they are, or going to the new one. "unzip" the +// sixels from the data table by looking back to the sources and classifying +// them in one or the other centry. rebuild our sums, sixels, hi/lo, and +// counts as we do so. anaphase, baybee! target always gets the upper range. static void refine_color(const uint32_t* data, int linesize, int begy, int begx, int leny, int lenx, sixeltable* stab, int color){ + unsigned char* crec = stab->table + CENTSIZE * color; + int didx = ctable_to_dtable(crec); + cdetails* deets = stab->deets + didx; + int rdelt = deets->hi[0] - deets->lo[0]; + int gdelt = deets->hi[1] - deets->lo[1]; + int bdelt = deets->hi[2] - deets->lo[2]; + unsigned rmax = deets->hi[0]; + unsigned gmax = deets->hi[1]; + unsigned bmax = deets->hi[2]; + if(gdelt >= rdelt && gdelt >= bdelt){ // split on green +fprintf(stderr, "[%d->%d] SPLIT ON GREEN %d %d\n", color, stab->colors, deets->hi[1], deets->lo[1]); + gmax = deets->lo[1] + (deets->hi[1] - deets->lo[1]) / 2; + }else if(rdelt >= gdelt && rdelt >= bdelt){ // split on red +fprintf(stderr, "[%d->%d] SPLIT ON RED %d %d\n", color, stab->colors, deets->hi[0], deets->lo[0]); + rmax = deets->lo[0] + (deets->hi[0] - deets->lo[0]) / 2; + }else{ // split on blue +fprintf(stderr, "[%d->%d] SPLIT ON BLUE %d %d\n", color, stab->colors, deets->hi[2], deets->lo[2]); + bmax = deets->lo[2] + (deets->hi[2] - deets->lo[2]) / 2; + } + unzip_color(data, linesize, begy, begx, leny, lenx, stab, color, + rmax, gmax, bmax); + ++stab->colors; } // relax the details down into free color registers @@ -176,7 +255,7 @@ refine_color_table(const uint32_t* data, int linesize, int begy, int begx, unsigned char* crec = stab->table + CENTSIZE * i; int didx = ctable_to_dtable(crec); cdetails* deets = stab->deets + didx; -fprintf(stderr, "hi: %d %d %d lo: %d %d %d\n", deets->hi[0], deets->hi[1], deets->hi[2], deets->hi[0], deets->hi[1], deets->hi[2]); +fprintf(stderr, "[%d->%d] hi: %d %d %d lo: %d %d %d\n", i, didx, deets->hi[0], deets->hi[1], deets->hi[2], deets->lo[0], deets->lo[1], deets->lo[2]); if(memcmp(deets->hi, deets->lo, RGBSIZE)){ fprintf(stderr, "good try on %d->%d (%d)\n", i, didx, stab->colorregs); refine_color(data, linesize, begy, begx, leny, lenx, stab, i); @@ -184,6 +263,7 @@ fprintf(stderr, "good try on %d->%d (%d)\n", i, didx, stab->colorregs); fprintf(stderr, "filled table!\n"); break; } + refined = true; } } if(!refined){ // no more possible work