/* Grafx2 - The Ultimate 256-color bitmap paint program
Copyright 2007 Adrien Destugues
Copyright 1996-2001 Sunset Design (Guillaume Dorme & Karl Maritaud)
Grafx2 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; version 2
of the License.
Grafx2 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Grafx2; if not, see or
write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include
#include
#include
#include
#include
#include
#include
#include "op_c.h"
#include "erreurs.h"
void RGB_to_HSL(int r,int g,int b,byte * hr,byte * sr,byte* lr)
{
double rd,gd,bd,h,s,l,max,min;
// convert RGB to HSV
rd = r / 255.0; // rd,gd,bd range 0-1 instead of 0-255
gd = g / 255.0;
bd = b / 255.0;
// compute L
// l=(rd*0.30)+(gd*0.59)+(bd*0.11);
// compute maximum of rd,gd,bd
if (rd>=gd)
{
if (rd>=bd)
max = rd;
else
max = bd;
}
else
{
if (gd>=bd)
max = gd;
else
max = bd;
}
// compute minimum of rd,gd,bd
if (rd<=gd)
{
if (rd<=bd)
min = rd;
else
min = bd;
}
else
{
if (gd<=bd)
min = gd;
else
min = bd;
}
l = (max + min) / 2.0;
if(max==min)
s = h = 0;
else
{
if (l<=0.5)
s = (max - min) / (max + min);
else
s = (max - min) / (2 - (max + min));
if (max == rd)
h = 42.5 * (gd-bd)/(max-min);
else if (max == gd)
h = 42.5 * (bd-rd)/(max-min)+85;
else
h = 42.5 * (rd-gd)/(max-min)+170;
if (h<0) h+=255;
}
*hr = h;
*lr = (l*255.0);
*sr = (s*255.0);
}
void HSL_to_RGB(byte h,byte s,byte l, byte* r, byte* g, byte* b)
{
float rf =0 ,gf = 0,bf = 0;
float hf,lf,sf;
float p,q;
if(s==0)
{
*r=*g=*b=l;
return;
}
hf = h / 255.0;
lf = l / 255.0;
sf = s / 255.0;
if (lf<=0.5)
q = lf*(1+sf);
else
q = lf+sf-lf*sf;
p = 2*lf-q;
rf = hf + (1 / 3.0);
gf = hf;
bf = hf - (1 / 3.0);
if (rf < 0) rf+=1;
if (rf > 1) rf-=1;
if (gf < 0) gf+=1;
if (gf > 1) gf-=1;
if (bf < 0) bf+=1;
if (bf > 1) bf-=1;
if (rf < 1/6.0)
rf = p + ((q-p)*6*rf);
else if(rf < 0.5)
rf = q;
else if(rf < 2/3.0)
rf = p + ((q-p)*6*(2/3.0-rf));
else
rf = p;
if (gf < 1/6.0)
gf = p + ((q-p)*6*gf);
else if(gf < 0.5)
gf = q;
else if(gf < 2/3.0)
gf = p + ((q-p)*6*(2/3.0-gf));
else
gf = p;
if (bf < 1/6.0)
bf = p + ((q-p)*6*bf);
else if(bf < 0.5)
bf = q;
else if(bf < 2/3.0)
bf = p + ((q-p)*6*(2/3.0-bf));
else
bf = p;
*r = rf * (255);
*g = gf * (255);
*b = bf * (255);
}
/////////////////////////////////////////////////////////////////////////////
///////////////////////////// Méthodes de gestion des tables de conversion //
/////////////////////////////////////////////////////////////////////////////
T_Conversion_table * CT_new(int nbb_r,int nbb_g,int nbb_b)
{
T_Conversion_table * n;
int size;
n=(T_Conversion_table *)malloc(sizeof(T_Conversion_table));
if (n!=NULL)
{
// On recopie les paramŠtres demand‚s
n->nbb_r=nbb_r;
n->nbb_g=nbb_g;
n->nbb_b=nbb_b;
// On calcule les autres
n->rng_r=(1<rng_g=(1<rng_b=(1<dec_r=nbb_g+nbb_b;
n->dec_g=nbb_b;
n->dec_b=0;
n->red_r=8-nbb_r;
n->red_g=8-nbb_g;
n->red_b=8-nbb_b;
// On tente d'allouer la table
size=(n->rng_r)*(n->rng_g)*(n->rng_b);
n->table=(byte *)malloc(size);
if (n->table!=NULL)
// C'est bon!
memset(n->table,0,size); // Inutile, mais plus propre
else
{
// Table impossible … allouer
free(n);
n=NULL;
}
}
return n;
}
void CT_delete(T_Conversion_table * t)
{
free(t->table);
free(t);
}
byte CT_get(T_Conversion_table * t,int r,int g,int b)
{
int index;
// On réduit le nombre de bits par couleur
r=(r>>t->red_r);
g=(g>>t->red_g);
b=(b>>t->red_b);
// On recherche la couleur la plus proche dans la table de conversion
index=(r<dec_r) | (g<dec_g) | (b<dec_b);
return t->table[index];
}
void CT_set(T_Conversion_table * t,int r,int g,int b,byte i)
{
int index;
index=(r<dec_r) | (g<dec_g) | (b<dec_b);
t->table[index]=i;
}
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////// M‚thodes de gestion des tables d'occurence //
/////////////////////////////////////////////////////////////////////////////
void OT_init(T_Occurrence_table * t)
{
int size;
size=(t->rng_r)*(t->rng_g)*(t->rng_b)*sizeof(int);
memset(t->table,0,size); // On initialise … 0
}
T_Occurrence_table * OT_new(int nbb_r,int nbb_g,int nbb_b)
{
T_Occurrence_table * n;
int size;
n=(T_Occurrence_table *)malloc(sizeof(T_Occurrence_table));
if (n!=0)
{
// On recopie les paramŠtres demand‚s
n->nbb_r=nbb_r;
n->nbb_g=nbb_g;
n->nbb_b=nbb_b;
// On calcule les autres
n->rng_r=(1<rng_g=(1<rng_b=(1<dec_r=nbb_g+nbb_b;
n->dec_g=nbb_b;
n->dec_b=0;
n->red_r=8-nbb_r;
n->red_g=8-nbb_g;
n->red_b=8-nbb_b;
// On tente d'allouer la table
size=(n->rng_r)*(n->rng_g)*(n->rng_b)*sizeof(int);
n->table=(int *)malloc(size);
if (n->table!=0)
// C'est bon! On initialise … 0
OT_init(n);
else
{
// Table impossible … allouer
free(n);
n=0;
}
}
return n;
}
void OT_delete(T_Occurrence_table * t)
{
free(t->table);
free(t);
}
int OT_get(T_Occurrence_table * t,int r,int g,int b)
{
int index;
index=(r<dec_r) | (g<dec_g) | (b<dec_b);
return t->table[index];
}
void OT_set(T_Occurrence_table * t,int r,int g,int b,int i)
{
int index;
r=(r>>t->red_r);
g=(g>>t->red_g);
b=(b>>t->red_b);
index=(r<dec_r) | (g<dec_g) | (b<dec_b);
t->table[index]=i;
}
void OT_inc(T_Occurrence_table * t,int r,int g,int b)
{
int index;
r=(r>>t->red_r);
g=(g>>t->red_g);
b=(b>>t->red_b);
index=(r<dec_r) | (g<dec_g) | (b<dec_b);
t->table[index]++;
}
void OT_count_occurrences(T_Occurrence_table * t,T_Bitmap24B image,int size)
{
T_Bitmap24B ptr;
int index;
for (index=size,ptr=image;index>0;index--,ptr++)
OT_inc(t,ptr->R,ptr->G,ptr->B);
}
int OT_count_colors(T_Occurrence_table * t)
{
int val; // Valeur de retour
int nb; // Nombre de couleurs … tester
int i; // Compteur de couleurs test‚es
val=0;
nb=(t->rng_r)*(t->rng_g)*(t->rng_b);
for (i=0;itable[i]>0)
val++;
return val;
}
/////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////// M‚thodes de gestion des clusters //
/////////////////////////////////////////////////////////////////////////////
void Cluster_analyser(T_Cluster * c,T_Occurrence_table * to)
{
int rmin,rmax,vmin,vmax,bmin,bmax;
int r,g,b;
// On cherche les mins et les maxs de chaque composante sur la couverture
// int nbocc;
// On prédécale tout pour éviter de faire trop de bazar en se forçant à utiliser OT_get, plus rapide
rmin=c->rmax <<16; rmax=c->rmin << 16;
vmin=c->vmax << 8; vmax=c->vmin << 8;
bmin=c->bmax; bmax=c->bmin;
c->occurences=0;
/*
for (r=c->rmin<<16;r<=c->rmax<<16;r+=1<<16)
for (g=c->vmin<<8;g<=c->vmax<<8;g+=1<<8)
for (b=c->bmin;b<=c->bmax;b++)
{
nbocc=to->table[r + g + b]; // OT_get
if (nbocc)
{
if (rrmax) rmax=r;
if (gvmax) vmax=g;
if (bbmax) bmax=b;
c->occurences+=nbocc;
}
}
*/
// On recherche le minimum et le maximum en parcourant le cluster selon chaque composante,
// ça évite des accès mémoires inutiles, de plus chaque boucle est plus petite que la
// précédente puisqu'on connait une borne supplémentaire
for(r=c->rmin<<16;r<=c->rmax<<16;r+=1<<16)
for(g=c->vmin<<8;g<=c->vmax<<8;g+=1<<8)
for(b=c->bmin;b<=c->bmax;b++)
{
if(to->table[r + g + b]) // OT_get
{
rmin=r;
goto RMAX;
}
}
RMAX:
for(r=c->rmax<<16;r>=rmin;r-=1<<16)
for(g=c->vmin<<8;g<=c->vmax<<8;g+=1<<8)
for(b=c->bmin;b<=c->bmax;b++)
{
if(to->table[r + g + b]) // OT_get
{
rmax=r;
goto VMIN;
}
}
VMIN:
for(g=c->vmin<<8;g<=c->vmax<<8;g+=1<<8)
for(r=rmin;r<=rmax;r+=1<<16)
for(b=c->bmin;b<=c->bmax;b++)
{
if(to->table[r + g + b]) // OT_get
{
vmin=g;
goto VMAX;
}
}
VMAX:
for(g=c->vmax<<8;g>=vmin;g-=1<<8)
for(r=rmin;r<=rmax;r+=1<<16)
for(b=c->bmin;b<=c->bmax;b++)
{
if(to->table[r + g + b]) // OT_get
{
vmax=g;
goto BMIN;
}
}
BMIN:
for(b=c->bmin;b<=c->bmax;b++)
for(r=rmin;r<=rmax;r+=1<<16)
for(g=vmin;g<=vmax;g+=1<<8)
{
if(to->table[r + g + b]) // OT_get
{
bmin=b;
goto BMAX;
}
}
BMAX:
for(b=c->bmax;b>=bmin;b--)
for(r=rmin;r<=rmax;r+=1<<16)
for(g=vmin;g<=vmax;g+=1<<8)
{
if(to->table[r + g + b]) // OT_get
{
bmax=b;
goto ENDCRUSH;
}
}
ENDCRUSH:
// Il faut quand même parcourir la partie utile du cluster, pour savoir combien il y a d'occurences
for(r=rmin;r<=rmax;r+=1<<16)
for(g=vmin;g<=vmax;g+=1<<8)
for(b=bmin;b<=bmax;b++)
{
c->occurences+=to->table[r + g + b]; // OT_get
}
c->rmin=rmin>>16; c->rmax=rmax>>16;
c->vmin=vmin>>8; c->vmax=vmax>>8;
c->bmin=bmin; c->bmax=bmax;
// On regarde la composante qui a la variation la plus grande
r=(c->rmax-c->rmin)*299;
g=(c->vmax-c->vmin)*587;
b=(c->bmax-c->bmin)*114;
if (g>=r)
{
// G>=R
if (g>=b)
{
// G>=R et G>=B
c->plus_large=1;
}
else
{
// G>=R et Gplus_large=2;
}
}
else
{
// R>G
if (r>=b)
{
// R>G et R>=B
c->plus_large=0;
}
else
{
// R>G et Rplus_large=2;
}
}
}
void Cluster_split(T_Cluster * c,T_Cluster * c1,T_Cluster * c2,int hue,T_Occurrence_table * to)
{
int limit;
int cumul;
int r,g,b;
limit=(c->occurences)/2;
cumul=0;
if (hue==0)
{
for (r=c->rmin<<16;r<=c->rmax<<16;r+=1<<16)
{
for (g=c->vmin<<8;g<=c->vmax<<8;g+=1<<8)
{
for (b=c->bmin;b<=c->bmax;b++)
{
cumul+=to->table[r + g + b];
if (cumul>=limit)
break;
}
if (cumul>=limit)
break;
}
if (cumul>=limit)
break;
}
r>>=16;
g>>=8;
if (r==c->rmin)
r++;
// R est la valeur de d‚but du 2nd cluster
c1->Rmin=c->Rmin; c1->Rmax=r-1;
c1->rmin=c->rmin; c1->rmax=r-1;
c1->Gmin=c->Gmin; c1->Vmax=c->Vmax;
c1->vmin=c->vmin; c1->vmax=c->vmax;
c1->Bmin=c->Bmin; c1->Bmax=c->Bmax;
c1->bmin=c->bmin; c1->bmax=c->bmax;
c2->Rmin=r; c2->Rmax=c->Rmax;
c2->rmin=r; c2->rmax=c->rmax;
c2->Gmin=c->Gmin; c2->Vmax=c->Vmax;
c2->vmin=c->vmin; c2->vmax=c->vmax;
c2->Bmin=c->Bmin; c2->Bmax=c->Bmax;
c2->bmin=c->bmin; c2->bmax=c->bmax;
}
else
if (hue==1)
{
for (g=c->vmin<<8;g<=c->vmax<<8;g+=1<<8)
{
for (r=c->rmin<<16;r<=c->rmax<<16;r+=1<<16)
{
for (b=c->bmin;b<=c->bmax;b++)
{
cumul+=to->table[r + g + b];
if (cumul>=limit)
break;
}
if (cumul>=limit)
break;
}
if (cumul>=limit)
break;
}
r>>=16; g>>=8;
if (g==c->vmin)
g++;
// G est la valeur de d‚but du 2nd cluster
c1->Rmin=c->Rmin; c1->Rmax=c->Rmax;
c1->rmin=c->rmin; c1->rmax=c->rmax;
c1->Gmin=c->Gmin; c1->Vmax=g-1;
c1->vmin=c->vmin; c1->vmax=g-1;
c1->Bmin=c->Bmin; c1->Bmax=c->Bmax;
c1->bmin=c->bmin; c1->bmax=c->bmax;
c2->Rmin=c->Rmin; c2->Rmax=c->Rmax;
c2->rmin=c->rmin; c2->rmax=c->rmax;
c2->Gmin=g; c2->Vmax=c->Vmax;
c2->vmin=g; c2->vmax=c->vmax;
c2->Bmin=c->Bmin; c2->Bmax=c->Bmax;
c2->bmin=c->bmin; c2->bmax=c->bmax;
}
else
{
for (b=c->bmin;b<=c->bmax;b++)
{
for (g=c->vmin<<8;g<=c->vmax<<8;g+=1<<8)
{
for (r=c->rmin<<16;r<=c->rmax<<16;r+=1<<16)
{
cumul+=to->table[r + g + b];
if (cumul>=limit)
break;
}
if (cumul>=limit)
break;
}
if (cumul>=limit)
break;
}
r>>=16; g>>=8;
if (b==c->bmin)
b++;
// B est la valeur de d‚but du 2nd cluster
c1->Rmin=c->Rmin; c1->Rmax=c->Rmax;
c1->rmin=c->rmin; c1->rmax=c->rmax;
c1->Gmin=c->Gmin; c1->Vmax=c->Vmax;
c1->vmin=c->vmin; c1->vmax=c->vmax;
c1->Bmin=c->Bmin; c1->Bmax=b-1;
c1->bmin=c->bmin; c1->bmax=b-1;
c2->Rmin=c->Rmin; c2->Rmax=c->Rmax;
c2->rmin=c->rmin; c2->rmax=c->rmax;
c2->Gmin=c->Gmin; c2->Vmax=c->Vmax;
c2->vmin=c->vmin; c2->vmax=c->vmax;
c2->Bmin=b; c2->Bmax=c->Bmax;
c2->bmin=b; c2->bmax=c->bmax;
}
}
void Cluster_compute_hue(T_Cluster * c,T_Occurrence_table * to)
{
int cumul_r,cumul_g,cumul_b;
int r,g,b;
int nbocc;
byte s=0;
cumul_r=cumul_g=cumul_b=0;
for (r=c->rmin;r<=c->rmax;r++)
for (g=c->vmin;g<=c->vmax;g++)
for (b=c->bmin;b<=c->bmax;b++)
{
nbocc=OT_get(to,r,g,b);
if (nbocc)
{
cumul_r+=r*nbocc;
cumul_g+=g*nbocc;
cumul_b+=b*nbocc;
}
}
c->r=(cumul_r<red_r)/c->occurences;
c->g=(cumul_g<red_g)/c->occurences;
c->b=(cumul_b<red_b)/c->occurences;
RGB_to_HSL(c->r,c->g,c->b,&c->h,&s,&c->l);
}
/////////////////////////////////////////////////////////////////////////////
//////////////////////////// M‚thodes de gestion des ensembles de clusters //
/////////////////////////////////////////////////////////////////////////////
void CS_Init(T_Cluster_set * cs,T_Occurrence_table * to)
{
cs->clusters[0].Rmin=cs->clusters[0].rmin=0;
cs->clusters[0].Gmin=cs->clusters[0].vmin=0;
cs->clusters[0].Bmin=cs->clusters[0].bmin=0;
cs->clusters[0].Rmax=cs->clusters[0].rmax=to->rng_r-1;
cs->clusters[0].Vmax=cs->clusters[0].vmax=to->rng_g-1;
cs->clusters[0].Bmax=cs->clusters[0].bmax=to->rng_b-1;
Cluster_analyser(cs->clusters+0,to);
// Et hop : le 1er ensemble de couleurs est initialis‚
cs->nb=1;
}
T_Cluster_set * CS_New(int nbmax,T_Occurrence_table * to)
{
T_Cluster_set * n;
n=(T_Cluster_set *)malloc(sizeof(T_Cluster_set));
if (n!=0)
{
// On recopie les paramŠtres demand‚s
n->nb_max=OT_count_colors(to);
// On vient de compter le nombre de couleurs existantes, s'il est plus grand que 256 on limit à 256 (nombre de couleurs voulu au final)
if (n->nb_max>nbmax)
{
n->nb_max=nbmax;
}
// On tente d'allouer la table
n->clusters=(T_Cluster *)malloc(nbmax*sizeof(T_Cluster));
if (n->clusters!=NULL)
// C'est bon! On initialise
CS_Init(n,to);
else
{
// Table impossible … allouer
free(n);
n=0;
}
}
return n;
}
void CS_Delete(T_Cluster_set * cs)
{
free(cs->clusters);
free(cs);
}
void CS_Get(T_Cluster_set * cs,T_Cluster * c)
{
int index;
// On cherche un cluster que l'on peut couper en deux, donc avec au moins deux valeurs
// différentes sur l'une des composantes
for (index=0;indexnb;index++)
if ( (cs->clusters[index].rminclusters[index].rmax) ||
(cs->clusters[index].vminclusters[index].vmax) ||
(cs->clusters[index].bminclusters[index].bmax) )
break;
// On le recopie dans c
*c=cs->clusters[index];
// On décrémente le nombre et on décale tous les clusters suivants
// Sachant qu'on va réinsérer juste après, il me semble que ça serait une bonne idée de gérer les clusters
// comme une liste chainée... on n'a aucun accès direct dedans, que des parcours ...
cs->nb--;
memcpy((cs->clusters+index),(cs->clusters+index+1),(cs->nb-index)*sizeof(T_Cluster));
}
void CS_Set(T_Cluster_set * cs,T_Cluster * c)
{
int index;
// int decalage;
// Le tableau des clusters est trié par nombre d'occurences. Donc on cherche la position du premier cluster
// qui est plus grand que le notre
for (index=0;indexnb;index++)
if (cs->clusters[index].occurencesoccurences)
/*
if (((OPTPAL_Cluster[index].rmax-OPTPAL_Cluster[index].rmin+1)*
(OPTPAL_Cluster[index].gmax-OPTPAL_Cluster[index].gmin+1)*
(OPTPAL_Cluster[index].bmax-OPTPAL_Cluster[index].bmin+1))
<
((Set->rmax-Set->rmin+1)*
(Set->gmax-Set->gmin+1)*
(Set->bmax-Set->bmin+1))
)
*/
break;
if (indexnb)
{
// On distingue ici une insertion plutot qu'un placement en fin de liste.
// On doit donc décaler les ensembles suivants vers la fin pour se faire
// une place dans la liste.
//for (decalage=cs->nb;decalage>index;decalage--)
// memcpy((cs->clusters+decalage),(cs->clusters+decalage-1),sizeof(T_Cluster));
memmove(cs->clusters+index+1,cs->clusters+index,(cs->nb-index)*sizeof(T_Cluster));
}
cs->clusters[index]=*c;
cs->nb++;
}
// Détermination de la meilleure palette en utilisant l'algo Median Cut :
// 1) On considère l'espace (R,G,B) comme 1 boîte
// 2) On cherche les extrêmes de la boîte en (R,G,B)
// 3) On trie les pixels de l'image selon l'axe le plus long parmi (R,G,B)
// 4) On coupe la boîte en deux au milieu, et on compacte pour que chaque bord corresponde bien à un pixel extreme
// 5) On recommence à couper selon le plus grand axe toutes boîtes confondues
// 6) On s'arrête quand on a le nombre de couleurs voulu
void CS_Generate(T_Cluster_set * cs,T_Occurrence_table * to)
{
T_Cluster current;
T_Cluster Nouveau1;
T_Cluster Nouveau2;
// Tant qu'on a moins de 256 clusters
while (cs->nbnb_max)
{
// On récupère le plus grand cluster
CS_Get(cs,¤t);
// On le coupe en deux
Cluster_split(¤t,&Nouveau1,&Nouveau2,current.plus_large,to);
// On compacte ces deux nouveaux (il peut y avoir un espace entre l'endroit de la coupure et les premiers pixels du cluster)
Cluster_analyser(&Nouveau1,to);
Cluster_analyser(&Nouveau2,to);
// On met ces deux nouveaux clusters dans le clusterSet... sauf s'ils sont vides
if(Nouveau1.occurences>0)
CS_Set(cs,&Nouveau1);
if(Nouveau2.occurences>0)
CS_Set(cs,&Nouveau2);
}
}
void CS_Compute_colors(T_Cluster_set * cs,T_Occurrence_table * to)
{
int index;
T_Cluster * c;
for (index=0,c=cs->clusters;indexnb;index++,c++)
Cluster_compute_hue(c,to);
}
void CS_Sort_by_chrominance(T_Cluster_set * cs)
{
int byte_used[256];
int decalages[256];
int index;
T_Cluster * nc;
nc=(T_Cluster *)malloc(cs->nb_max*sizeof(T_Cluster));
// Initialisation de la table d'occurence de chaque octet
for (index=0;index<256;index++)
byte_used[index]=0;
// Comptage du nombre d'occurences de chaque octet
for (index=0;indexnb;index++)
byte_used[cs->clusters[index].h]++;
// Calcul de la table des d‚calages
decalages[0]=0;
for (index=1;index<256;index++)
decalages[index]=decalages[index-1]+byte_used[index-1];
// Copie r‚ordonn‚e dans la nouvelle liste
for (index=0;indexnb;index++)
{
nc[decalages[cs->clusters[index].h]]=cs->clusters[index];
decalages[cs->clusters[index].h]++;
}
// Remplacement de la liste d‚sordonn‚e par celle tri‚e
free(cs->clusters);
cs->clusters=nc;
}
void CS_Sort_by_luminance(T_Cluster_set * cs)
{
int byte_used[256];
int decalages[256];
int index;
T_Cluster * nc;
nc=(T_Cluster *)malloc(cs->nb_max*sizeof(T_Cluster));
// Initialisation de la table d'occurence de chaque octet
for (index=0;index<256;index++)
byte_used[index]=0;
// Comptage du nombre d'occurences de chaque octet
for (index=0;indexnb;index++)
byte_used[cs->clusters[index].l]++;
// Calcul de la table des d‚calages
decalages[0]=0;
for (index=1;index<256;index++)
decalages[index]=decalages[index-1]+byte_used[index-1];
// Copie r‚ordonn‚e dans la nouvelle liste
for (index=0;indexnb;index++)
{
nc[decalages[cs->clusters[index].l]]=cs->clusters[index];
decalages[cs->clusters[index].l]++;
}
// Remplacement de la liste d‚sordonn‚e par celle tri‚e
free(cs->clusters);
cs->clusters=nc;
}
void CS_Generate_color_table_and_palette(T_Cluster_set * cs,T_Conversion_table * tc,T_Components * palette)
{
int index;
int r,g,b;
for (index=0;indexnb;index++)
{
palette[index].R=cs->clusters[index].r;
palette[index].G=cs->clusters[index].g;
palette[index].B=cs->clusters[index].b;
for (r=cs->clusters[index].Rmin;r<=cs->clusters[index].Rmax;r++)
for (g=cs->clusters[index].Gmin;g<=cs->clusters[index].Vmax;g++)
for (b=cs->clusters[index].Bmin;b<=cs->clusters[index].Bmax;b++)
CT_set(tc,r,g,b,index);
}
}
/////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////// Méthodes de gestion des dégradés //
/////////////////////////////////////////////////////////////////////////////
void GS_Init(T_Gradient_set * ds,T_Cluster_set * cs)
{
ds->gradients[0].nb_colors=1;
ds->gradients[0].min=cs->clusters[0].h;
ds->gradients[0].max=cs->clusters[0].h;
ds->gradients[0].hue=cs->clusters[0].h;
// Et hop : le 1er ensemble de d‚grad‚s est initialis‚
ds->nb=1;
}
T_Gradient_set * GS_New(T_Cluster_set * cs)
{
T_Gradient_set * n;
n=(T_Gradient_set *)malloc(sizeof(T_Gradient_set));
if (n!=NULL)
{
// On recopie les paramŠtres demand‚s
n->nb_max=cs->nb_max;
// On tente d'allouer la table
n->gradients=(T_Gradient *)malloc((n->nb_max)*sizeof(T_Gradient));
if (n->gradients!=0)
// C'est bon! On initialise
GS_Init(n,cs);
else
{
// Table impossible … allouer
free(n);
n=0;
}
}
return n;
}
void GS_Delete(T_Gradient_set * ds)
{
free(ds->gradients);
free(ds);
}
void GS_Generate(T_Gradient_set * ds,T_Cluster_set * cs)
{
int ic,id; // Les indexs de parcours des ensembles
int best_gradient; // Meilleur d‚grad‚
int best_diff; // Meilleure diff‚rence de chrominance
int diff; // difference de chrominance courante
// Pour chacun des clusters … traiter
for (ic=1;icnb;ic++)
{
// On recherche le d‚grad‚ le plus proche de la chrominance du cluster
best_gradient=-1;
best_diff=99999999;
for (id=0;idnb;id++)
{
diff=abs(cs->clusters[ic].h - ds->gradients[id].hue);
if ((best_diff>diff) && (diff<16))
{
best_gradient=id;
best_diff=diff;
}
}
// Si on a trouv‚ un d‚grad‚ dans lequel inclure le cluster
if (best_gradient!=-1)
{
// On met … jour le d‚grad‚
if (cs->clusters[ic].h < ds->gradients[best_gradient].min)
ds->gradients[best_gradient].min=cs->clusters[ic].h;
if (cs->clusters[ic].h > ds->gradients[best_gradient].max)
ds->gradients[best_gradient].max=cs->clusters[ic].h;
ds->gradients[best_gradient].hue=((ds->gradients[best_gradient].hue*
ds->gradients[best_gradient].nb_colors)
+cs->clusters[ic].h)
/(ds->gradients[best_gradient].nb_colors+1);
ds->gradients[best_gradient].nb_colors++;
}
else
{
// On cr‚e un nouveau d‚grad‚
best_gradient=ds->nb;
ds->gradients[best_gradient].nb_colors=1;
ds->gradients[best_gradient].min=cs->clusters[ic].h;
ds->gradients[best_gradient].max=cs->clusters[ic].h;
ds->gradients[best_gradient].hue=cs->clusters[ic].h;
ds->nb++;
}
cs->clusters[ic].h=best_gradient;
}
// On redistribue les valeurs dans les clusters
for (ic=0;icnb;ic++)
cs->clusters[ic].h=ds->gradients[cs->clusters[ic].h].hue;
}
T_Conversion_table * Optimize_palette(T_Bitmap24B image,int size,T_Components * palette,int r,int g,int b)
{
T_Occurrence_table * to;
T_Conversion_table * tc;
T_Cluster_set * cs;
T_Gradient_set * ds;
// Création des éléments nécessaires au calcul de palette optimisée:
to=0; tc=0; cs=0; ds=0;
to=OT_new(r,g,b);
if (to!=0)
{
tc=CT_new(r,g,b);
if (tc!=0)
{
// Première étape : on compte les pixels de chaque couleur pour pouvoir trier là dessus
OT_count_occurrences(to,image,size);
cs=CS_New(256,to);
if (cs!=0)
{
// C'est bon, on a pu tout allouer
// On génère les clusters (avec l'algo du median cut)
CS_Generate(cs,to);
// On calcule la teinte de chaque pixel (Luminance et chrominance)
CS_Compute_colors(cs,to);
ds=GS_New(cs);
if (ds!=0)
{
GS_Generate(ds,cs);
GS_Delete(ds);
}
// Enfin on trie les clusters (donc les couleurs de la palette) dans un ordre sympa : par couleur, et par luminosité pour chaque couleur
CS_Sort_by_luminance(cs);
CS_Sort_by_chrominance(cs);
// Enfin on génère la palette et la table de correspondance entre chaque couleur 24b et sa couleur palette associée.
CS_Generate_color_table_and_palette(cs,tc,palette);
CS_Delete(cs);
OT_delete(to);
return tc;
}
CT_delete(tc);
}
OT_delete(to);
}
// Si on arrive ici c'est que l'allocation n'a pas réussi,
// l'appelant devra recommencer avec une précision plus faible (3 derniers paramètres)
return 0;
}
int Modified_value(int value,int modif)
{
value+=modif;
if (value<0)
{
value=0;
}
else if (value>255)
{
value=255;
}
return value;
}
void Convert_24b_bitmap_to_256_Floyd_Steinberg(T_Bitmap256 dest,T_Bitmap24B source,int width,int height,T_Components * palette,T_Conversion_table * tc)
// Cette fonction dégrade au fur et à mesure le bitmap source, donc soit on ne
// s'en ressert pas, soit on passe à la fonction une copie de travail du
// bitmap original.
{
T_Bitmap24B current;
T_Bitmap24B c_plus1;
T_Bitmap24B u_minus1;
T_Bitmap24B next;
T_Bitmap24B u_plus1;
T_Bitmap256 d;
int x_pos,y_pos;
int red,green,blue;
float e_red,e_green,e_blue;
// On initialise les variables de parcours:
current =source; // Le pixel dont on s'occupe
next =current+width; // Le pixel en dessous
c_plus1 =current+1; // Le pixel à droite
u_minus1=next-1; // Le pixel en bas à gauche
u_plus1 =next+1; // Le pixel en bas à droite
d =dest;
// On parcours chaque pixel:
for (y_pos=0;y_posR;
green =current->G;
blue =current->B;
// Cherche la couleur correspondant dans la palette et la range dans l'image de destination
*d=CT_get(tc,red,green,blue);
// Puis on calcule pour chaque composante l'erreur dûe à l'approximation
red-=palette[*d].R;
green -=palette[*d].G;
blue -=palette[*d].B;
// Et dans chaque pixel voisin on propage l'erreur
// A droite:
e_red=(red*7)/16.0;
e_green =(green *7)/16.0;
e_blue =(blue *7)/16.0;
if (x_pos+1R=Modified_value(c_plus1->R,e_red);
c_plus1->G=Modified_value(c_plus1->G,e_green );
c_plus1->B=Modified_value(c_plus1->B,e_blue );
}
// En bas à gauche:
if (y_pos+10)
{
u_minus1->R=Modified_value(u_minus1->R,e_red);
u_minus1->G=Modified_value(u_minus1->G,e_green );
u_minus1->B=Modified_value(u_minus1->B,e_blue );
}
// En bas:
e_red=(red*5/16.0);
e_green =(green*5 /16.0);
e_blue =(blue*5 /16.0);
next->R=Modified_value(next->R,e_red);
next->G=Modified_value(next->G,e_green );
next->B=Modified_value(next->B,e_blue );
// En bas à droite:
if (x_pos+1R=Modified_value(u_plus1->R,e_red);
u_plus1->G=Modified_value(u_plus1->G,e_green );
u_plus1->B=Modified_value(u_plus1->B,e_blue );
}
}
// On passe au pixel suivant :
current++;
c_plus1++;
u_minus1++;
next++;
u_plus1++;
d++;
}
}
}
static const byte precision_24b[]=
{
8,8,8,
6,6,6,
6,6,5,
5,6,5,
5,5,5,
5,5,4,
4,5,4,
4,4,4,
4,4,3,
3,4,3,
3,3,3,
3,3,2};
// Convertie avec le plus de précision possible une image 24b en 256c
// Renvoie s'il y a eu une erreur ou pas..
// Cette fonction utilise l'algorithme "median cut" (Optimize_palette) pour trouver la palette, et diffuse les erreurs avec floyd-steinberg.
int Convert_24b_bitmap_to_256(T_Bitmap256 dest,T_Bitmap24B source,int width,int height,T_Components * palette)
{
T_Conversion_table * table; // table de conversion
int ip; // index de précision pour la conversion
// On essaye d'obtenir une table de conversion qui loge en mémoire, avec la
// meilleure précision possible
for (ip=0;ip<(10*3);ip+=3)
{
table=Optimize_palette(source,width*height,palette,precision_24b[ip+0],
precision_24b[ip+1],precision_24b[ip+2]);
if (table!=0)
break;
}
if (table!=0)
{
Convert_24b_bitmap_to_256_Floyd_Steinberg(dest,source,width,height,palette,table);
CT_delete(table);
return 0;
}
else
return 1;
}