Rework the color reduction algorithm:

* Split clusters according to their volume (narrow covering) instead of occurence count. This ensures clusters representating a lot 
of colors are split; and a lot of pixels with near colors are grouped together, which is what we want for low-color work.
 * Rework memory management of clusters. Some of them were not malloc'ed but put on the stack, and then inserted in the list. Also 
avoid copying+deleting a cluster in CT_Get, return the original instead.
 * Use an union in the cluster struct to pack it a bit, since some data is used only in the color reduction phase, and some only in 
the palette lookup.

The result is maybe slower, but looks much, much better. Fixes issues 63 and 441.


git-svn-id: svn://pulkomandy.tk/GrafX2/trunk@1878 416bcca6-2ee7-4201-b75f-2eb2f807beb1
This commit is contained in:
Adrien Destugues 2011-11-28 22:31:06 +00:00
parent dae1bc856b
commit 0236d5342a
2 changed files with 110 additions and 99 deletions

View File

@ -439,18 +439,20 @@ ENDCRUSH:
g=(c->vmax-c->vmin); g=(c->vmax-c->vmin);
b=(c->bmax-c->bmin); b=(c->bmax-c->bmin);
c->data.cut.volume = r*g*b;
if (g>=r) if (g>=r)
{ {
// G>=R // G>=R
if (g>=b) if (g>=b)
{ {
// G>=R et G>=B // G>=R et G>=B
c->plus_large=1; c->data.cut.plus_large=1;
} }
else else
{ {
// G>=R et G<B // G>=R et G<B
c->plus_large=2; c->data.cut.plus_large=2;
} }
} }
else else
@ -459,12 +461,12 @@ ENDCRUSH:
if (r>=b) if (r>=b)
{ {
// R>G et R>=B // R>G et R>=B
c->plus_large=0; c->data.cut.plus_large=0;
} }
else else
{ {
// R>G et R<B // R>G et R<B
c->plus_large=2; c->data.cut.plus_large=2;
} }
} }
} }
@ -504,12 +506,9 @@ void Cluster_split(T_Cluster * c, T_Cluster * c1, T_Cluster * c2, int hue,
r>>=16; r>>=16;
g>>=8; g>>=8;
// We tried to split on red, but found half of the pixels with r = rmin // More than half of the cluster pixel have r = rmin. Ensure we split somewhere anyway.
// so we enforce some split to happen anyway, instead of creating an empty if (r == c->rmin) r++;
// c2 and c1 == c
if (r==c->rmin)
r++;
c1->Rmin=c->Rmin; c1->Rmax=r-1; c1->Rmin=c->Rmin; c1->Rmax=r-1;
c1->rmin=c->rmin; c1->rmax=r-1; c1->rmin=c->rmin; c1->rmax=r-1;
@ -547,9 +546,8 @@ void Cluster_split(T_Cluster * c, T_Cluster * c1, T_Cluster * c2, int hue,
} }
r>>=16; g>>=8; r>>=16; g>>=8;
if (g==c->vmin) if (g == c->vmin) g++;
g++;
c1->Rmin=c->Rmin; c1->Rmax=c->Rmax; c1->Rmin=c->Rmin; c1->Rmax=c->Rmax;
c1->rmin=c->rmin; c1->rmax=c->rmax; c1->rmin=c->rmin; c1->rmax=c->rmax;
@ -586,9 +584,8 @@ void Cluster_split(T_Cluster * c, T_Cluster * c1, T_Cluster * c2, int hue,
} }
r>>=16; g>>=8; r>>=16; g>>=8;
if (b==c->bmin) if (b == c->bmin) b++;
b++;
c1->Rmin=c->Rmin; c1->Rmax=c->Rmax; c1->Rmin=c->Rmin; c1->Rmax=c->Rmax;
c1->rmin=c->rmin; c1->rmax=c->rmax; c1->rmin=c->rmin; c1->rmax=c->rmax;
@ -630,10 +627,10 @@ void Cluster_compute_hue(T_Cluster * c,T_Occurrence_table * to)
} }
} }
c->r=(cumul_r<<to->red_r)/c->occurences; c->data.pal.r=(cumul_r<<to->red_r)/c->occurences;
c->g=(cumul_g<<to->red_g)/c->occurences; c->data.pal.g=(cumul_g<<to->red_g)/c->occurences;
c->b=(cumul_b<<to->red_b)/c->occurences; c->data.pal.b=(cumul_b<<to->red_b)/c->occurences;
RGB_to_HSL(c->r, c->g, c->b, &c->h, &s, &c->l); RGB_to_HSL(c->data.pal.r, c->data.pal.g, c->data.pal.b, &c->data.pal.h, &s, &c->data.pal.l);
} }
@ -659,6 +656,15 @@ void CS_Check(T_Cluster_set* cs)
} }
*/ */
/*
void Cluster_Print(T_Cluster* node)
{
printf("R %d %d\tG %d %d\tB %d %d\n",
node->Rmin, node->Rmax, node->Gmin, node->Vmax,
node->Bmin, node->Bmax);
}
*/
/// Setup the first cluster before we start the operations /// Setup the first cluster before we start the operations
/// This one covers the full palette range /// This one covers the full palette range
void CS_Init(T_Cluster_set * cs, T_Occurrence_table * to) void CS_Init(T_Cluster_set * cs, T_Occurrence_table * to)
@ -703,7 +709,6 @@ T_Cluster_set * CS_New(int nbmax, T_Occurrence_table * to)
n = NULL; n = NULL;
} }
} }
return n; return n;
} }
@ -723,39 +728,13 @@ void CS_Delete(T_Cluster_set * cs)
/// Pop a cluster from the cluster list /// Pop a cluster from the cluster list
void CS_Get(T_Cluster_set * cs, T_Cluster * c) void CS_Get(T_Cluster_set * cs, T_Cluster ** c)
{ {
T_Cluster* current = cs->clusters; // Just remove and return the first cluster, which has the biggest volume.
T_Cluster* prev = NULL; *c = cs->clusters;
// Search a cluster with at least 2 distinct colors so we can split it cs->clusters = (*c)->next;
// Clusters are sorted by number of occurences, so a cluster may end up --cs->nb;
// with a lot of pixelsand on top of the list, but only one color. We can't
// split it in that case. It should probably be stored on a list of unsplittable
// clusters to avoid running on it again on each iteration.
do
{
if ( (current->rmin < current->rmax) ||
(current->vmin < current->vmax) ||
(current->bmin < current->bmax) )
break;
prev = current;
} while((current = current -> next));
// copy it to c
*c = *current;
// remove it from the list
cs->nb--;
if(prev)
prev->next = current->next;
else
cs->clusters = current->next;
free(current);
current = NULL;
} }
@ -765,8 +744,8 @@ void CS_Set(T_Cluster_set * cs,T_Cluster * c)
T_Cluster* current = cs->clusters; T_Cluster* current = cs->clusters;
T_Cluster* prev = NULL; T_Cluster* prev = NULL;
// Search the first cluster that is smaller than ours (less pixels) // Search the first cluster that is smaller than ours
while (current && current->occurences > c->occurences) while (current && current->data.cut.volume > c->data.cut.volume)
{ {
prev = current; prev = current;
current = current->next; current = current->next;
@ -796,31 +775,43 @@ void CS_Set(T_Cluster_set * cs,T_Cluster * c)
// At the same time, put the split clusters in the color tree for later palette lookup // At the same time, put the split clusters in the color tree for later palette lookup
void CS_Generate(T_Cluster_set * cs, T_Occurrence_table * to, CT_Tree* colorTree) void CS_Generate(T_Cluster_set * cs, T_Occurrence_table * to, CT_Tree* colorTree)
{ {
T_Cluster current; T_Cluster* current;
T_Cluster Nouveau1; T_Cluster* Nouveau1 = malloc(sizeof(T_Cluster));
T_Cluster Nouveau2; T_Cluster* Nouveau2 = malloc(sizeof(T_Cluster));
// There are less than 256 boxes // There are less than 256 boxes
while (cs->nb<cs->nb_max) while (cs->nb<cs->nb_max)
{ {
// Get the biggest one // Get the biggest one
CS_Get(cs,&current); CS_Get(cs,&current);
//Cluster_Print(current);
// We're going to split, so add the cluster to the colortree // We're going to split, so add the cluster to the colortree
CT_set(colorTree,current.Rmin, current.Gmin, current.Bmin, CT_set(colorTree,current->Rmin, current->Gmin, current->Bmin,
current.Rmax, current.Vmax, current.Bmax, 0); current->Rmax, current->Vmax, current->Bmax, 0);
// Split it // Split it
Cluster_split(&current, &Nouveau1, &Nouveau2, current.plus_large, to); if (current->data.cut.volume <= 1)
{
// Sorry, but there's nothing more to split !
// The biggest cluster only has one color...
free(current);
break;
}
Cluster_split(current, Nouveau1, Nouveau2, current->data.cut.plus_large, to);
free(current);
// Pack the 2 new clusters (the split may leave some empty space between the // Pack the 2 new clusters (the split may leave some empty space between the
// box border and the first actual pixel) // box border and the first actual pixel)
Cluster_pack(&Nouveau1, to); Cluster_pack(Nouveau1, to);
Cluster_pack(&Nouveau2, to); Cluster_pack(Nouveau2, to);
// Put them back in the list // Put them back in the list
CS_Set(cs,&Nouveau1); if (Nouveau1->occurences != 0)
CS_Set(cs,&Nouveau2); CS_Set(cs,Nouveau1);
if (Nouveau2->occurences != 0)
CS_Set(cs,Nouveau2);
} }
} }
@ -856,7 +847,7 @@ void CS_Sort_by_chrominance(T_Cluster_set * cs)
// Find his position in the new list // Find his position in the new list
for (place = newlist; place != NULL; place = place->next) for (place = newlist; place != NULL; place = place->next)
{ {
if (place->h > nc->h) break; if (place->data.pal.h > nc->data.pal.h) break;
prev = place; prev = place;
} }
@ -890,7 +881,7 @@ void CS_Sort_by_luminance(T_Cluster_set * cs)
// Find its position in the new list // Find its position in the new list
for (place = newlist; place != NULL; place = place->next) for (place = newlist; place != NULL; place = place->next)
{ {
if (place->l > nc->l) break; if (place->data.pal.l > nc->data.pal.l) break;
prev = place; prev = place;
} }
@ -916,9 +907,9 @@ void CS_Generate_color_table_and_palette(T_Cluster_set * cs,CT_Tree* tc,T_Compon
for (index=0;index<cs->nb;index++) for (index=0;index<cs->nb;index++)
{ {
palette[index].R=current->r; palette[index].R=current->data.pal.r;
palette[index].G=current->g; palette[index].G=current->data.pal.g;
palette[index].B=current->b; palette[index].B=current->data.pal.b;
CT_set(tc,current->Rmin, current->Gmin, current->Bmin, CT_set(tc,current->Rmin, current->Gmin, current->Bmin,
current->Rmax, current->Vmax, current->Bmax, index); current->Rmax, current->Vmax, current->Bmax, index);
@ -933,9 +924,9 @@ void CS_Generate_color_table_and_palette(T_Cluster_set * cs,CT_Tree* tc,T_Compon
void GS_Init(T_Gradient_set * ds,T_Cluster_set * cs) void GS_Init(T_Gradient_set * ds,T_Cluster_set * cs)
{ {
ds->gradients[0].nb_colors=1; ds->gradients[0].nb_colors=1;
ds->gradients[0].min=cs->clusters->h; ds->gradients[0].min=cs->clusters->data.pal.h;
ds->gradients[0].max=cs->clusters->h; ds->gradients[0].max=cs->clusters->data.pal.h;
ds->gradients[0].hue=cs->clusters->h; ds->gradients[0].hue=cs->clusters->data.pal.h;
// Et hop : le 1er ensemble de dgrads est initialis // Et hop : le 1er ensemble de dgrads est initialis
ds->nb=1; ds->nb=1;
} }
@ -988,7 +979,7 @@ void GS_Generate(T_Gradient_set * ds,T_Cluster_set * cs)
best_diff=99999999; best_diff=99999999;
for (id=0;id<ds->nb;id++) for (id=0;id<ds->nb;id++)
{ {
diff=abs(current->h - ds->gradients[id].hue); diff=abs(current->data.pal.h - ds->gradients[id].hue);
if ((best_diff>diff) && (diff<16)) if ((best_diff>diff) && (diff<16))
{ {
best_gradient=id; best_gradient=id;
@ -1000,13 +991,13 @@ void GS_Generate(T_Gradient_set * ds,T_Cluster_set * cs)
if (best_gradient!=-1) if (best_gradient!=-1)
{ {
// On met … jour le dgrad // On met … jour le dgrad
if (current->h < ds->gradients[best_gradient].min) if (current->data.pal.h < ds->gradients[best_gradient].min)
ds->gradients[best_gradient].min=current->h; ds->gradients[best_gradient].min=current->data.pal.h;
if (current->h > ds->gradients[best_gradient].max) if (current->data.pal.h > ds->gradients[best_gradient].max)
ds->gradients[best_gradient].max=current->h; ds->gradients[best_gradient].max=current->data.pal.h;
ds->gradients[best_gradient].hue=((ds->gradients[best_gradient].hue* ds->gradients[best_gradient].hue=((ds->gradients[best_gradient].hue*
ds->gradients[best_gradient].nb_colors) ds->gradients[best_gradient].nb_colors)
+current->h) +current->data.pal.h)
/(ds->gradients[best_gradient].nb_colors+1); /(ds->gradients[best_gradient].nb_colors+1);
ds->gradients[best_gradient].nb_colors++; ds->gradients[best_gradient].nb_colors++;
} }
@ -1015,18 +1006,18 @@ void GS_Generate(T_Gradient_set * ds,T_Cluster_set * cs)
// On cre un nouveau dgrad // On cre un nouveau dgrad
best_gradient=ds->nb; best_gradient=ds->nb;
ds->gradients[best_gradient].nb_colors=1; ds->gradients[best_gradient].nb_colors=1;
ds->gradients[best_gradient].min=current->h; ds->gradients[best_gradient].min=current->data.pal.h;
ds->gradients[best_gradient].max=current->h; ds->gradients[best_gradient].max=current->data.pal.h;
ds->gradients[best_gradient].hue=current->h; ds->gradients[best_gradient].hue=current->data.pal.h;
ds->nb++; ds->nb++;
} }
current->h=best_gradient; current->data.pal.h=best_gradient;
} while((current = current->next)); } while((current = current->next));
// On redistribue les valeurs dans les clusters // On redistribue les valeurs dans les clusters
current = cs -> clusters; current = cs -> clusters;
do do
current->h=ds->gradients[current->h].hue; current->data.pal.h=ds->gradients[current->data.pal.h].hue;
while((current = current ->next)); while((current = current ->next));
} }

View File

@ -65,26 +65,46 @@ typedef struct
///////////////////////////////////////// Définition d'un ensemble de couleur ///////////////////////////////////////// Définition d'un ensemble de couleur
typedef struct S_Cluster struct S_Cluster_CutData
{ {
int occurences; // Nb total d'occurences des couleurs de l'ensemble // informations used while median-cutting
int volume; // volume of narrow covering (without margins where there are no pixels)
// Widest component : 0 red, 1 green, 2 blue
byte plus_large;
};
// Grande couverture struct S_Cluster_PalData
byte Rmin,Rmax; {
byte Gmin,Vmax; // information used while color reducing
byte Bmin,Bmax;
// Couverture minimale
byte rmin,rmax;
byte vmin,vmax;
byte bmin,bmax;
byte plus_large; // Composante ayant la plus grande variation (0=red,1=green,2=blue)
byte r,g,b; // color synthétisant l'ensemble byte r,g,b; // color synthétisant l'ensemble
byte h; // Chrominance byte h; // Chrominance
byte l; // Luminosité byte l; // Luminosité
};
union U_Cluster_Data
{
struct S_Cluster_CutData cut;
struct S_Cluster_PalData pal;
};
typedef struct S_Cluster
{
struct S_Cluster* next; struct S_Cluster* next;
int occurences; // Numbers of pixels in picture part of this cluster
// Narrow covering (remove margins that don't hold any pixel)
byte rmin,rmax;
byte vmin,vmax;
byte bmin,bmax;
// Wide covering (how far it extends before touching another cluster wide covering)
byte Rmin,Rmax;
byte Gmin,Vmax;
byte Bmin,Bmax;
union U_Cluster_Data data;
} T_Cluster; } T_Cluster;
@ -156,7 +176,7 @@ void Cluster_compute_hue(T_Cluster * c,T_Occurrence_table * to);
void CS_Init(T_Cluster_set * cs,T_Occurrence_table * to); void CS_Init(T_Cluster_set * cs,T_Occurrence_table * to);
T_Cluster_set * CS_New(int nbmax,T_Occurrence_table * to); T_Cluster_set * CS_New(int nbmax,T_Occurrence_table * to);
void CS_Delete(T_Cluster_set * cs); void CS_Delete(T_Cluster_set * cs);
void CS_Get(T_Cluster_set * cs,T_Cluster * c); void CS_Get(T_Cluster_set * cs,T_Cluster ** c);
void CS_Set(T_Cluster_set * cs,T_Cluster * c); void CS_Set(T_Cluster_set * cs,T_Cluster * c);
void CS_Generate(T_Cluster_set * cs,T_Occurrence_table * to, CT_Tree* colorTree); void CS_Generate(T_Cluster_set * cs,T_Occurrence_table * to, CT_Tree* colorTree);
void CS_Compute_colors(T_Cluster_set * cs,T_Occurrence_table * to); void CS_Compute_colors(T_Cluster_set * cs,T_Occurrence_table * to);