grafX2/op_c.c
Adrien Destugues 428e1c36cf Some more optimizations to the color reduction algorithm. Now runs in a decent time on my laptop :)
There may be some more small optimisations possible in this area... i'm still thinking we could get a better palette by putting more priority on highly saturated clusters, but, as we don't store the saturation property in the clusters, it seems difficult to do. Probably it would require switching to HSL before doing the clustering, and finetune the multiplications done in Cluster_Split. Note: these modifications (and the one in the previous commit) will probably crash badly if using the reduction algorithm with a smaller precision than 24bpp. The program will try to do that if it is unable to malloc the big lookup tables (we need a table of 256^3 = 16Mbytes for the color lookup, and when loading we also have the 24b image in memory, and also the clustermap, and the occurence table, wich is 256^3*4=64Mbytes). So, either we assume every computer out there has 128Mb RAM, or I'll have to change my code back to use a lower precision if the malloc ever fails. Anyways, who's wanting to open files bigger than our marbles.pcx in grafx2 ? and who except me is running a computer without swap memory at all ?


git-svn-id: svn://pulkomandy.tk/GrafX2/trunk@374 416bcca6-2ee7-4201-b75f-2eb2f807beb1
2008-12-09 16:40:36 +00:00

1238 lines
31 KiB
C
Raw Blame History

/* 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 <http://www.gnu.org/licenses/> or
write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <math.h>
#include "op_c.h"
#include "erreurs.h"
#include "graph.h"
#undef OPTIMISATIONS_ASSEMBLEUR
void RGBtoHSL(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 HSLtoRGB(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 //
/////////////////////////////////////////////////////////////////////////////
Table_conversion * TC_New(int nbb_r,int nbb_v,int nbb_b)
{
Table_conversion * n;
int taille;
n=(Table_conversion *)malloc(sizeof(Table_conversion));
if (n!=NULL)
{
// On recopie les param<61>tres demand<6E>s
n->nbb_r=nbb_r;
n->nbb_v=nbb_v;
n->nbb_b=nbb_b;
// On calcule les autres
n->rng_r=(1<<nbb_r);
n->rng_v=(1<<nbb_v);
n->rng_b=(1<<nbb_b);
n->dec_r=nbb_v+nbb_b;
n->dec_v=nbb_b;
n->dec_b=0;
n->red_r=8-nbb_r;
n->red_v=8-nbb_v;
n->red_b=8-nbb_b;
// On tente d'allouer la table
taille=(n->rng_r)*(n->rng_v)*(n->rng_b);
n->table=(byte *)malloc(taille);
if (n->table!=NULL)
// C'est bon!
memset(n->table,0,taille); // Inutile, mais plus propre
else
{
// Table impossible <20> allouer
free(n);
n=NULL;
}
}
return n;
}
void TC_Delete(Table_conversion * t)
{
free(t->table);
free(t);
}
byte TC_Get(Table_conversion * t,int r,int v,int b)
{
int indice;
// On r<>duit le nombre de bits par couleur
r=(r>>t->red_r);
v=(v>>t->red_v);
b=(b>>t->red_b);
// On recherche la couleur la plus proche dans la table de conversion
indice=(r<<t->dec_r) | (v<<t->dec_v) | (b<<t->dec_b);
return t->table[indice];
}
void TC_Set(Table_conversion * t,int r,int v,int b,byte i)
{
int indice;
indice=(r<<t->dec_r) | (v<<t->dec_v) | (b<<t->dec_b);
t->table[indice]=i;
}
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////// M<>thodes de gestion des tables d'occurence //
/////////////////////////////////////////////////////////////////////////////
void TO_Init(Table_occurence * t)
{
int taille;
taille=(t->rng_r)*(t->rng_v)*(t->rng_b)*sizeof(int);
memset(t->table,0,taille); // On initialise <20> 0
}
Table_occurence * TO_New(int nbb_r,int nbb_v,int nbb_b)
{
Table_occurence * n;
int taille;
n=(Table_occurence *)malloc(sizeof(Table_occurence));
if (n!=0)
{
// On recopie les param<61>tres demand<6E>s
n->nbb_r=nbb_r;
n->nbb_v=nbb_v;
n->nbb_b=nbb_b;
// On calcule les autres
n->rng_r=(1<<nbb_r);
n->rng_v=(1<<nbb_v);
n->rng_b=(1<<nbb_b);
n->dec_r=nbb_v+nbb_b;
n->dec_v=nbb_b;
n->dec_b=0;
n->red_r=8-nbb_r;
n->red_v=8-nbb_v;
n->red_b=8-nbb_b;
// On tente d'allouer la table
taille=(n->rng_r)*(n->rng_v)*(n->rng_b)*sizeof(int);
n->table=(int *)malloc(taille);
if (n->table!=0)
// C'est bon! On initialise <20> 0
TO_Init(n);
else
{
// Table impossible <20> allouer
free(n);
n=0;
}
}
return n;
}
void TO_Delete(Table_occurence * t)
{
free(t->table);
free(t);
}
int TO_Get(Table_occurence * t,int r,int v,int b)
{
int indice;
indice=(r<<t->dec_r) | (v<<t->dec_v) | (b<<t->dec_b);
return t->table[indice];
}
void TO_Set(Table_occurence * t,int r,int v,int b,int i)
{
int indice;
r=(r>>t->red_r);
v=(v>>t->red_v);
b=(b>>t->red_b);
indice=(r<<t->dec_r) | (v<<t->dec_v) | (b<<t->dec_b);
t->table[indice]=i;
}
void TO_Inc(Table_occurence * t,int r,int v,int b)
{
int indice;
r=(r>>t->red_r);
v=(v>>t->red_v);
b=(b>>t->red_b);
indice=(r<<t->dec_r) | (v<<t->dec_v) | (b<<t->dec_b);
t->table[indice]++;
}
void TO_Compter_occurences(Table_occurence * t,Bitmap24B image,int taille)
{
Bitmap24B ptr;
int indice;
for (indice=taille,ptr=image;indice>0;indice--,ptr++)
TO_Inc(t,ptr->R,ptr->V,ptr->B);
}
int TO_Compter_couleurs(Table_occurence * t)
{
int val; // Valeur de retour
int nb; // Nombre de couleurs <20> tester
int i; // Compteur de couleurs test<73>es
val=0;
nb=(t->rng_r)*(t->rng_v)*(t->rng_b);
for (i=0;i<nb;i++)
if (t->table[i]>0)
val++;
return val;
}
/////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////// M<>thodes de gestion des clusters //
/////////////////////////////////////////////////////////////////////////////
void Cluster_Analyser(Cluster * c,Table_occurence * to)
{
int rmin,rmax,vmin,vmax,bmin,bmax;
int r,v,b;
// On cherche les mins et les maxs de chaque composante sur la couverture
// int nbocc;
// On pr<70>d<EFBFBD>cale tout pour <20>viter de faire trop de bazar en se for<6F>ant <20> utiliser TO_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 (v=c->vmin<<8;v<=c->vmax<<8;v+=1<<8)
for (b=c->bmin;b<=c->bmax;b++)
{
nbocc=to->table[r + v + b]; // TO_Get
if (nbocc)
{
if (r<rmin) rmin=r;
else if (r>rmax) rmax=r;
if (v<vmin) vmin=v;
else if (v>vmax) vmax=v;
if (b<bmin) bmin=b;
else if (b>bmax) bmax=b;
c->occurences+=nbocc;
}
}
*/
// On recherche le minimum et le maximum en parcourant le cluster selon chaque composante,
// <20>a <20>vite des acc<63>s m<>moires inutiles, de plus chaque boucle est plus petite que la
// pr<70>c<EFBFBD>dente puisqu'on connait une borne suppl<70>mentaire
for(r=c->rmin<<16;r<=c->rmax<<16;r+=1<<16)
for(v=c->vmin<<8;v<=c->vmax<<8;v+=1<<8)
for(b=c->bmin;b<=c->bmax;b++)
{
if(to->table[r + v + b]) // TO_Get
{
rmin=r;
goto RMAX;
}
}
RMAX:
for(r=c->rmax<<16;r>=rmin;r-=1<<16)
for(v=c->vmin<<8;v<=c->vmax<<8;v+=1<<8)
for(b=c->bmin;b<=c->bmax;b++)
{
if(to->table[r + v + b]) // TO_Get
{
rmax=r;
goto VMIN;
}
}
VMIN:
for(v=c->vmin<<8;v<=c->vmax<<8;v+=1<<8)
for(r=rmin;r<=rmax;r+=1<<16)
for(b=c->bmin;b<=c->bmax;b++)
{
if(to->table[r + v + b]) // TO_Get
{
vmin=v;
goto VMAX;
}
}
VMAX:
for(v=c->vmax<<8;v>=vmin;v-=1<<8)
for(r=rmin;r<=rmax;r+=1<<16)
for(b=c->bmin;b<=c->bmax;b++)
{
if(to->table[r + v + b]) // TO_Get
{
vmax=v;
goto BMIN;
}
}
BMIN:
for(b=c->bmin;b<=c->bmax;b++)
for(r=rmin;r<=rmax;r+=1<<16)
for(v=vmin;v<=vmax;v+=1<<8)
{
if(to->table[r + v + b]) // TO_Get
{
bmin=b;
goto BMAX;
}
}
BMAX:
for(b=c->bmax;b>=bmin;b--)
for(r=rmin;r<=rmax;r+=1<<16)
for(v=vmin;v<=vmax;v+=1<<8)
{
if(to->table[r + v + b]) // TO_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(v=vmin;v<=vmax;v+=1<<8)
for(b=bmin;b<=bmax;b++)
{
c->occurences+=to->table[r + v + b]; // TO_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;
v=(c->vmax-c->vmin)*587;
b=(c->bmax-c->bmin)*114;
if (v>=r)
{
// V>=R
if (v>=b)
{
// V>=R et V>=B
c->plus_large=1;
}
else
{
// V>=R et V<B
c->plus_large=2;
}
}
else
{
// R>V
if (r>=b)
{
// R>V et R>=B
c->plus_large=0;
}
else
{
// R>V et R<B
c->plus_large=2;
}
}
}
void Cluster_Split(Cluster * c,Cluster * c1,Cluster * c2,int teinte,Table_occurence * to)
{
int limite;
int cumul;
int r,v,b;
limite=(c->occurences)/2;
cumul=0;
if (teinte==0)
{
for (r=c->rmin<<16;r<=c->rmax<<16;r+=1<<16)
{
for (v=c->vmin<<8;v<=c->vmax<<8;v+=1<<8)
{
for (b=c->bmin;b<=c->bmax;b++)
{
cumul+=to->table[r + v + b];
if (cumul>=limite)
break;
}
if (cumul>=limite)
break;
}
if (cumul>=limite)
break;
}
r>>=16;
v>>=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->Vmin=c->Vmin; 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->Vmin=c->Vmin; 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 (teinte==1)
{
for (v=c->vmin<<8;v<=c->vmax<<8;v+=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 + v + b];
if (cumul>=limite)
break;
}
if (cumul>=limite)
break;
}
if (cumul>=limite)
break;
}
r>>=16; v>>=8;
if (v==c->vmin)
v++;
// V 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->Vmin=c->Vmin; c1->Vmax=v-1;
c1->vmin=c->vmin; c1->vmax=v-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->Vmin=v; c2->Vmax=c->Vmax;
c2->vmin=v; 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 (v=c->vmin<<8;v<=c->vmax<<8;v+=1<<8)
{
for (r=c->rmin<<16;r<=c->rmax<<16;r+=1<<16)
{
cumul+=to->table[r + v + b];
if (cumul>=limite)
break;
}
if (cumul>=limite)
break;
}
if (cumul>=limite)
break;
}
r>>=16; v>>=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->Vmin=c->Vmin; 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->Vmin=c->Vmin; 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_Calculer_teinte(Cluster * c,Table_occurence * to)
{
int cumulR,cumulV,cumulB;
int r,v,b;
int nbocc;
byte s=0;
cumulR=cumulV=cumulB=0;
for (r=c->rmin;r<=c->rmax;r++)
for (v=c->vmin;v<=c->vmax;v++)
for (b=c->bmin;b<=c->bmax;b++)
{
nbocc=TO_Get(to,r,v,b);
if (nbocc)
{
cumulR+=r*nbocc;
cumulV+=v*nbocc;
cumulB+=b*nbocc;
}
}
c->r=(cumulR<<to->red_r)/c->occurences;
c->v=(cumulV<<to->red_v)/c->occurences;
c->b=(cumulB<<to->red_b)/c->occurences;
RGBtoHSL(c->r,c->v,c->b,&c->h,&s,&c->l);
}
/////////////////////////////////////////////////////////////////////////////
//////////////////////////// M<>thodes de gestion des ensembles de clusters //
/////////////////////////////////////////////////////////////////////////////
void CS_Init(ClusterSet * cs,Table_occurence * to)
{
cs->clusters[0].Rmin=cs->clusters[0].rmin=0;
cs->clusters[0].Vmin=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_v-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<69>
cs->nb=1;
}
ClusterSet * CS_New(int nbmax,Table_occurence * to)
{
ClusterSet * n;
n=(ClusterSet *)malloc(sizeof(ClusterSet));
if (n!=0)
{
// On recopie les param<61>tres demand<6E>s
n->nbmax=TO_Compter_couleurs(to);
// On vient de compter le nombre de couleurs existantes, s'il est plus grand que 256 on limite <20> 256 (nombre de couleurs voulu au final)
if (n->nbmax>nbmax)
{
n->nbmax=nbmax;
}
// On tente d'allouer la table
n->clusters=(Cluster *)malloc(nbmax*sizeof(Cluster));
if (n->clusters!=NULL)
// C'est bon! On initialise
CS_Init(n,to);
else
{
// Table impossible <20> allouer
free(n);
n=0;
}
}
return n;
}
void CS_Delete(ClusterSet * cs)
{
free(cs->clusters);
free(cs);
}
void CS_Get(ClusterSet * cs,Cluster * c)
{
int indice;
// On cherche un cluster que l'on peut couper en deux, donc avec au moins deux valeurs
// diff<66>rentes sur l'une des composantes
for (indice=0;indice<cs->nb;indice++)
if ( (cs->clusters[indice].rmin<cs->clusters[indice].rmax) ||
(cs->clusters[indice].vmin<cs->clusters[indice].vmax) ||
(cs->clusters[indice].bmin<cs->clusters[indice].bmax) )
break;
// On le recopie dans c
*c=cs->clusters[indice];
// On d<>cr<63>mente le nombre et on d<>cale tous les clusters suivants
// Sachant qu'on va r<>ins<6E>rer juste apr<70>s, il me semble que <20>a serait une bonne id<69>e de g<>rer les clusters
// comme une liste chain<69>e... on n'a aucun acc<63>s direct dedans, que des parcours ...
cs->nb--;
memcpy((cs->clusters+indice),(cs->clusters+indice+1),(cs->nb-indice)*sizeof(Cluster));
}
void CS_Set(ClusterSet * cs,Cluster * c)
{
int indice;
// int decalage;
// Le tableau des clusters est tri<72> par nombre d'occurences. Donc on cherche la position du premier cluster
// qui est plus grand que le notre
for (indice=0;indice<cs->nb;indice++)
if (cs->clusters[indice].occurences<c->occurences)
/*
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 (indice<cs->nb)
{
// 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>indice;decalage--)
// memcpy((cs->clusters+decalage),(cs->clusters+decalage-1),sizeof(Cluster));
memmove(cs->clusters+indice+1,cs->clusters+indice,(cs->nb-indice)*sizeof(Cluster));
}
cs->clusters[indice]=*c;
cs->nb++;
}
// D<>termination de la meilleure palette en utilisant l'algo Median Cut :
// 1) On consid<69>re l'espace (R,V,B) comme 1 bo<62>te
// 2) On cherche les extr<74>mes de la bo<62>te en (R,V,B)
// 3) On trie les pixels de l'image selon l'axe le plus long parmi (R,V,B)
// 4) On coupe la bo<62>te en deux au milieu, et on compacte pour que chaque bord corresponde bien <20> un pixel extreme
// 5) On recommence <20> couper selon le plus grand axe toutes bo<62>tes confondues
// 6) On s'arr<72>te quand on a le nombre de couleurs voulu
void CS_Generer(ClusterSet * cs,Table_occurence * to)
{
Cluster Courant;
Cluster Nouveau1;
Cluster Nouveau2;
// Tant qu'on a moins de 256 clusters
while (cs->nb<cs->nbmax)
{
// On r<>cup<75>re le plus grand cluster
CS_Get(cs,&Courant);
// On le coupe en deux
Cluster_Split(&Courant,&Nouveau1,&Nouveau2,Courant.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_Calculer_teintes(ClusterSet * cs,Table_occurence * to)
{
int indice;
Cluster * c;
for (indice=0,c=cs->clusters;indice<cs->nb;indice++,c++)
Cluster_Calculer_teinte(c,to);
}
void CS_Trier_par_chrominance(ClusterSet * cs)
{
int byte_used[256];
int decalages[256];
int index;
Cluster * nc;
nc=(Cluster *)malloc(cs->nbmax*sizeof(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;index<cs->nb;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<6E>e dans la nouvelle liste
for (index=0;index<cs->nb;index++)
{
nc[decalages[cs->clusters[index].h]]=cs->clusters[index];
decalages[cs->clusters[index].h]++;
}
// Remplacement de la liste d<>sordonn<6E>e par celle tri<72>e
free(cs->clusters);
cs->clusters=nc;
}
void CS_Trier_par_luminance(ClusterSet * cs)
{
int byte_used[256];
int decalages[256];
int index;
Cluster * nc;
nc=(Cluster *)malloc(cs->nbmax*sizeof(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;index<cs->nb;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<6E>e dans la nouvelle liste
for (index=0;index<cs->nb;index++)
{
nc[decalages[cs->clusters[index].l]]=cs->clusters[index];
decalages[cs->clusters[index].l]++;
}
// Remplacement de la liste d<>sordonn<6E>e par celle tri<72>e
free(cs->clusters);
cs->clusters=nc;
}
void CS_Generer_TC_et_Palette(ClusterSet * cs,Table_conversion * tc,struct Composantes * palette)
{
int indice;
int r,v,b;
for (indice=0;indice<cs->nb;indice++)
{
palette[indice].R=cs->clusters[indice].r;
palette[indice].V=cs->clusters[indice].v;
palette[indice].B=cs->clusters[indice].b;
for (r=cs->clusters[indice].Rmin;r<=cs->clusters[indice].Rmax;r++)
for (v=cs->clusters[indice].Vmin;v<=cs->clusters[indice].Vmax;v++)
for (b=cs->clusters[indice].Bmin;b<=cs->clusters[indice].Bmax;b++)
TC_Set(tc,r,v,b,indice);
}
}
/////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////// M<>thodes de gestion des d<>grad<61>s //
/////////////////////////////////////////////////////////////////////////////
void DS_Init(DegradeSet * ds,ClusterSet * cs)
{
ds->degrades[0].nbcouleurs=1;
ds->degrades[0].min=cs->clusters[0].h;
ds->degrades[0].max=cs->clusters[0].h;
ds->degrades[0].hue=cs->clusters[0].h;
// Et hop : le 1er ensemble de d<>grad<61>s est initialis<69>
ds->nb=1;
}
DegradeSet * DS_New(ClusterSet * cs)
{
DegradeSet * n;
n=(DegradeSet *)malloc(sizeof(DegradeSet));
if (n!=NULL)
{
// On recopie les param<61>tres demand<6E>s
n->nbmax=cs->nbmax;
// On tente d'allouer la table
n->degrades=(Degrade *)malloc((n->nbmax)*sizeof(Degrade));
if (n->degrades!=0)
// C'est bon! On initialise
DS_Init(n,cs);
else
{
// Table impossible <20> allouer
free(n);
n=0;
}
}
return n;
}
void DS_Delete(DegradeSet * ds)
{
free(ds->degrades);
free(ds);
}
void DS_Generer(DegradeSet * ds,ClusterSet * cs)
{
int ic,id; // Les indices de parcours des ensembles
int mdegr; // Meilleur d<>grad<61>
int mdiff; // Meilleure diff<66>rence de chrominance
int diff; // Diff<66>rence de chrominance courante
// Pour chacun des clusters <20> traiter
for (ic=1;ic<cs->nb;ic++)
{
// On recherche le d<>grad<61> le plus proche de la chrominance du cluster
mdegr=-1;
mdiff=99999999;
for (id=0;id<ds->nb;id++)
{
diff=abs(cs->clusters[ic].h - ds->degrades[id].hue);
if ((mdiff>diff) && (diff<16))
{
mdegr=id;
mdiff=diff;
}
}
// Si on a trouv<75> un d<>grad<61> dans lequel inclure le cluster
if (mdegr!=-1)
{
// On met <20> jour le d<>grad<61>
if (cs->clusters[ic].h < ds->degrades[mdegr].min)
ds->degrades[mdegr].min=cs->clusters[ic].h;
if (cs->clusters[ic].h > ds->degrades[mdegr].max)
ds->degrades[mdegr].max=cs->clusters[ic].h;
ds->degrades[mdegr].hue=((ds->degrades[mdegr].hue*
ds->degrades[mdegr].nbcouleurs)
+cs->clusters[ic].h)
/(ds->degrades[mdegr].nbcouleurs+1);
ds->degrades[mdegr].nbcouleurs++;
}
else
{
// On cr<63>e un nouveau d<>grad<61>
mdegr=ds->nb;
ds->degrades[mdegr].nbcouleurs=1;
ds->degrades[mdegr].min=cs->clusters[ic].h;
ds->degrades[mdegr].max=cs->clusters[ic].h;
ds->degrades[mdegr].hue=cs->clusters[ic].h;
ds->nb++;
}
cs->clusters[ic].h=mdegr;
}
// On redistribue les valeurs dans les clusters
for (ic=0;ic<cs->nb;ic++)
cs->clusters[ic].h=ds->degrades[cs->clusters[ic].h].hue;
}
Table_conversion * Optimiser_palette(Bitmap24B image,int taille,struct Composantes * palette,int r,int v,int b)
{
Table_occurence * to;
Table_conversion * tc;
ClusterSet * cs;
DegradeSet * ds;
// Cr<43>ation des <20>l<EFBFBD>ments n<>cessaires au calcul de palette optimis<69>e:
to=0; tc=0; cs=0; ds=0;
to=TO_New(r,v,b);
if (to!=0)
{
tc=TC_New(r,v,b);
if (tc!=0)
{
// Premi<6D>re <20>tape : on compte les pixels de chaque couleur pour pouvoir trier l<> dessus
TO_Compter_occurences(to,image,taille);
cs=CS_New(256,to);
if (cs!=0)
{
// C'est bon, on a pu tout allouer
// On g<>n<EFBFBD>re les clusters (avec l'algo du median cut)
CS_Generer(cs,to);
// On calcule la teinte de chaque pixel (Luminance et chrominance)
CS_Calculer_teintes(cs,to);
ds=DS_New(cs);
if (ds!=0)
{
DS_Generer(ds,cs);
DS_Delete(ds);
}
// Enfin on trie les clusters (donc les couleurs de la palette) dans un ordre sympa : par couleur, et par luminosit<69> pour chaque couleur
CS_Trier_par_luminance(cs);
CS_Trier_par_chrominance(cs);
// Enfin on g<>n<EFBFBD>re la palette et la table de correspondance entre chaque couleur 24b et sa couleur palette associ<63>e.
CS_Generer_TC_et_Palette(cs,tc,palette);
CS_Delete(cs);
TO_Delete(to);
return tc;
}
TC_Delete(tc);
}
TO_Delete(to);
}
// Si on arrive ici c'est que l'allocation n'a pas r<>ussi,
// l'appelant devra recommencer avec une pr<70>cision plus faible (3 derniers param<61>tres)
return 0;
}
int Valeur_modifiee(int valeur,int modif)
{
valeur+=modif;
if (valeur<0)
{
valeur=0;
}
else if (valeur>255)
{
valeur=255;
}
return valeur;
}
void Convert_bitmap_24B_to_256_Floyd_Steinberg(Bitmap256 Dest,Bitmap24B Source,int largeur,int hauteur,struct Composantes * palette,Table_conversion * tc)
// Cette fonction d<>grade au fur et <20> mesure le bitmap source, donc soit on ne
// s'en ressert pas, soit on passe <20> la fonction une copie de travail du
// bitmap original.
{
Bitmap24B Courant;
Bitmap24B C_plus1;
Bitmap24B S_moins1;
Bitmap24B Suivant;
Bitmap24B S_plus1;
Bitmap256 D;
int Pos_X,Pos_Y;
int Rouge,Vert,Bleu;
float ERouge,EVert,EBleu;
// On initialise les variables de parcours:
Courant =Source; // Le pixel dont on s'occupe
Suivant =Courant+largeur; // Le pixel en dessous
C_plus1 =Courant+1; // Le pixel <20> droite
S_moins1=Suivant-1; // Le pixel en bas <20> gauche
S_plus1 =Suivant+1; // Le pixel en bas <20> droite
D =Dest;
// On parcours chaque pixel:
for (Pos_Y=0;Pos_Y<hauteur;Pos_Y++)
{
for (Pos_X=0;Pos_X<largeur;Pos_X++)
{
// On prends la meilleure couleur de la palette qui traduit la couleur
// 24 bits de la source:
Rouge=Courant->R;
Vert =Courant->V;
Bleu =Courant->B;
// Cherche la couleur correspondant dans la palette et la range dans l'image de destination
*D=TC_Get(tc,Rouge,Vert,Bleu);
// Puis on calcule pour chaque composante l'erreur d<>e <20> l'approximation
Rouge-=palette[*D].R;
Vert -=palette[*D].V;
Bleu -=palette[*D].B;
// Et dans chaque pixel voisin on propage l'erreur
// A droite:
ERouge=(Rouge*7)/16.0;
EVert =(Vert *7)/16.0;
EBleu =(Bleu *7)/16.0;
if (Pos_X+1<largeur)
{
// Valeur_modifiee fait la somme des 2 params en bornant sur [0,255]
C_plus1->R=Valeur_modifiee(C_plus1->R,ERouge);
C_plus1->V=Valeur_modifiee(C_plus1->V,EVert );
C_plus1->B=Valeur_modifiee(C_plus1->B,EBleu );
}
// En bas <20> gauche:
if (Pos_Y+1<hauteur)
{
ERouge=(Rouge*3)/16.0;
EVert =(Vert *3)/16.0;
EBleu =(Bleu *3)/16.0;
if (Pos_X>0)
{
S_moins1->R=Valeur_modifiee(S_moins1->R,ERouge);
S_moins1->V=Valeur_modifiee(S_moins1->V,EVert );
S_moins1->B=Valeur_modifiee(S_moins1->B,EBleu );
}
// En bas:
ERouge=(Rouge*5/16.0);
EVert =(Vert*5 /16.0);
EBleu =(Bleu*5 /16.0);
Suivant->R=Valeur_modifiee(Suivant->R,ERouge);
Suivant->V=Valeur_modifiee(Suivant->V,EVert );
Suivant->B=Valeur_modifiee(Suivant->B,EBleu );
// En bas <20> droite:
if (Pos_X+1<largeur)
{
ERouge=(Rouge/16.0);
EVert =(Vert /16.0);
EBleu =(Bleu /16.0);
S_plus1->R=Valeur_modifiee(S_plus1->R,ERouge);
S_plus1->V=Valeur_modifiee(S_plus1->V,EVert );
S_plus1->B=Valeur_modifiee(S_plus1->B,EBleu );
}
}
// On passe au pixel suivant :
Courant++;
C_plus1++;
S_moins1++;
Suivant++;
S_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<70>cision possible une image 24b en 256c
// Renvoie s'il y a eu une erreur ou pas..
// Cette fonction utilise l'algorithme "median cut" (Optimiser_palette) pour trouver la palette, et diffuse les erreurs avec floyd-steinberg.
int Convert_bitmap_24B_to_256(Bitmap256 Dest,Bitmap24B Source,int largeur,int hauteur,struct Composantes * palette)
{
Table_conversion * table; // table de conversion
int ip; // Indice de pr<70>cision pour la conversion
// On essaye d'obtenir une table de conversion qui loge en m<>moire, avec la
// meilleure pr<70>cision possible
for (ip=0;ip<(10*3);ip+=3)
{
table=Optimiser_palette(Source,largeur*hauteur,palette,precision_24b[ip+0],
precision_24b[ip+1],precision_24b[ip+2]);
if (table!=0)
break;
}
if (table!=0)
{
Convert_bitmap_24B_to_256_Floyd_Steinberg(Dest,Source,largeur,hauteur,palette,table);
TC_Delete(table);
return 0;
}
else
return 1;
}