/* vim:expandtab:ts=2 sw=2: */ /* Grafx2 - The Ultimate 256-color bitmap paint program Copyright 2017 Thomas Bernard Copyright 2010 Alexander Filyanov 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 */ #include #ifndef _MSC_VER #include #endif #include #include #include #include #include #include #include "op_c.h" #include "errors.h" #include "colorred.h" // If GRAFX2_QUANTIZE_CLUSTER_POPULATION_SPLIT is defined, // the clusters are splitted in two half of equal (pixel) population. // Otherwise, they are splitted in two half of equal volume. #define GRAFX2_QUANTIZE_CLUSTER_POPULATION_SPLIT // If GRAFX2_QUANTIZE_CLUSTER_SORT_BY_VOLUME is defined // the clusters are sorted by volume. Otherwise, they // are sorted by length of the diagonal //#define GRAFX2_QUANTIZE_CLUSTER_SORT_BY_VOLUME int Convert_24b_bitmap_to_256_fast(T_Bitmap256 dest,T_Bitmap24B source,int width,int height,T_Components * palette); /// Convert RGB to HSL. /// Both input and output are in the 0..255 range to use in the palette screen 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 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); } /// Convert HSL back to RGB /// Input and output are all in range 0..255 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); } /// /// Returns a value that is high when color is near white, /// and low when it's darker. Used for sorting. long Perceptual_lightness(T_Components *color) { return 26*color->R*26*color->R + 55*color->G*55*color->G + 19*color->B*19*color->B; } // Handlers for the occurences tables // This table is used to count the occurence of an (RGB) pixel value in the // source 24bit image. These count are then used by the median cut algorithm to // decide which cluster to split. /// Initialize an occurence table 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); // Set it to 0 } /// Allocate an occurence table for given number of bits 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) { // Copy passed parameters n->nbb_r=nbb_r; n->nbb_g=nbb_g; n->nbb_b=nbb_b; // Compute others 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; // Allocate the table size=(n->rng_r)*(n->rng_g)*(n->rng_b); n->table=(int *)calloc(size, sizeof(int)); if (n->table == NULL) { // Not enough memory ! free(n); n=NULL; } } return n; } /// Delete a table and free the memory void OT_delete(T_Occurrence_table * t) { free(t->table); free(t); t = NULL; } /// Get number of occurences for a given color int OT_get(T_Occurrence_table * t, byte r, byte g, byte b) { int index; // Drop bits as needed index=(r<dec_r) | (g<dec_g) | (b<dec_b); return t->table[index]; } /// Add 1 to the count for a color void OT_inc(T_Occurrence_table * t,byte r,byte g,byte b) { int index; // Drop bits as needed r=(r>>t->red_r); g=(g>>t->red_g); b=(b>>t->red_b); // Compute the address index=(r<dec_r) | (g<dec_g) | (b<dec_b); t->table[index]++; } /// Count the use of each color in a 24bit picture and fill in the table 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); } /// Count the total number of pixels in an occurence table int OT_count_colors(T_Occurrence_table * t) { int val; // Computed return value int nb; // Number of colors to test int i; // Loop index val = 0; nb=(t->rng_r)*(t->rng_g)*(t->rng_b); for (i = 0; i < nb; i++) if (t->table[i]>0) val++; return val; } // Cluster management // Clusters are boxes in the RGB spaces, defined by 6 corner coordinates : // Rmax, Rmin, Vmax (or Gmax), Vmin, Rmax, Rmin // The median cut algorithm start with a single cluster covering the whole // colorspace then split it in two smaller clusters on the longest axis until // there are 256 non-empty clusters (with some tricks if the original image // actually has less than 256 colors) // Each cluster also store the number of pixels that are inside and the // rmin, rmax, vmin, vmax, bmin, bmax values are the first/last values that // actually are used by a pixel in the cluster // When you split a big cluster there may be some space between the splitting // plane and the first pixel actually in a cluster /// Pack a cluster, ie compute its {r,v,b}{min,max} values void Cluster_pack(T_Cluster * c,const T_Occurrence_table * const to) { int rmin,rmax,vmin,vmax,bmin,bmax; int r,g,b; // Find min. and max. values actually used for each component in this cluster // Pre-shift everything to avoid using OT_Get and be faster. // GIMP use only 6 bits for G and B components in this table. rmin=c->rmax << to->dec_r; rmax=c->rmin << to->dec_r; vmin=c->vmax << to->dec_g; vmax=c->vmin << to->dec_g; bmin=c->bmax << to->dec_b; bmax=c->bmin << to->dec_b; c->occurences=0; // Unoptimized code kept here for documentation purpose because the optimized // one is unreadable : run over the whole cluster and find the min and max, // and count the occurences at the same time. /* for (r=c->rmin<dec_r;r<=c->rmax<dec_r;r+=1<dec_r) for (g=c->vmin<dec_g;g<=c->vmax<dec_g;g+=1<dec_g) 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; } } */ // Optimized version : find the extremums one at a time, so we can reduce the // area to seek for the next one. Start at the edges of the cluster and go to // the center until we find a pixel. for(r=c->rmin<dec_r;r<=c->rmax<dec_r;r+=1<dec_r) for(g=c->vmin<dec_g;g<=c->vmax<dec_g;g+=1<dec_g) 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<dec_r;r>=rmin;r-=1<dec_r) for(g=c->vmin<dec_g;g<=c->vmax<dec_g;g+=1<dec_g) 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<dec_g;g<=c->vmax<dec_g;g+=1<dec_g) for(r=rmin;r<=rmax;r+=1<dec_r) 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<dec_g;g>=vmin;g-=1<dec_g) for(r=rmin;r<=rmax;r+=1<dec_r) 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<dec_r) for(g=vmin;g<=vmax;g+=1<dec_g) { 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<dec_r) for(g=vmin;g<=vmax;g+=1<dec_g) { if(to->table[r + g + b]) // OT_get { bmax=b; goto ENDCRUSH; } } ENDCRUSH: // We still need to seek the internal part of the cluster to count pixels // inside it for(r=rmin;r<=rmax;r+=1<dec_r) for(g=vmin;g<=vmax;g+=1<dec_g) for(b=bmin;b<=bmax;b++) { c->occurences+=to->table[r + g + b]; // OT_get } // Unshift the values and put them in the cluster info c->rmin=rmin>>to->dec_r; c->rmax=rmax>>to->dec_r; c->vmin=vmin>>to->dec_g; c->vmax=vmax>>to->dec_g; c->bmin=bmin; c->bmax=bmax; // Find the longest axis to know which way to split the cluster r = c->rmax-c->rmin; g = c->vmax-c->vmin; b = c->bmax-c->bmin; c->data.cut.sqdiag = r*r+g*g+b*b; c->data.cut.volume = (r+1)*(g+1)*(b+1); if (g>=r) { // G>=R if (g>=b) { // G>=R et G>=B c->data.cut.plus_large=1; } else { // G>=R et Gdata.cut.plus_large=2; } } else { // R>G if (r>=b) { // R>G et R>=B c->data.cut.plus_large=0; } else { // R>G et Rdata.cut.plus_large=2; } } } #ifndef GRAFX2_QUANTIZE_CLUSTER_POPULATION_SPLIT /// Split a cluster on its longest axis. /// c = source cluster, c1, c2 = output after split /// the two output cluster have half volume (and not half population) void Cluster_split_volume(T_Cluster * c, T_Cluster * c1, T_Cluster * c2, int hue) { int r,g,b; if (hue == 0) // split on red { r = (c->rmin + c->rmax) / 2; c1->Rmin=c->Rmin; c1->Rmax=r; c1->rmin=c->rmin; c1->rmax=r; 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+1; c2->Rmax=c->Rmax; c2->rmin=r+1; 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) // split on green { g = (c->vmin + c->vmax) / 2; c1->Rmin=c->Rmin; c1->Rmax=c->Rmax; c1->rmin=c->rmin; c1->rmax=c->rmax; c1->Gmin=c->Gmin; c1->Vmax=g; c1->vmin=c->vmin; c1->vmax=g; 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+1; c2->Vmax=c->Vmax; c2->vmin=g+1; c2->vmax=c->vmax; c2->Bmin=c->Bmin; c2->Bmax=c->Bmax; c2->bmin=c->bmin; c2->bmax=c->bmax; } else { b = (c->bmin + c->bmax) / 2; 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; c1->bmin=c->bmin; c1->bmax=b; 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+1; c2->Bmax=c->Bmax; c2->bmin=b+1; c2->bmax=c->bmax; } } #else // GRAFX2_QUANTIZE_CLUSTER_POPULATION_SPLIT /// Split a cluster on its longest axis. /// c = source cluster, c1, c2 = output after split /// the two output clusters have half population (and not half volume) void Cluster_split(T_Cluster * c, T_Cluster * c1, T_Cluster * c2, int hue, const T_Occurrence_table * const to) { int limit; int cumul; int r, g, b; // Split criterion: each of the cluster will have the same number of pixels limit = c->occurences / 2; cumul = 0; if (hue == 0) // split on red { // Run over the cluster until we reach the requested number of pixels for (r = c->rmin<dec_r; r<=c->rmax<dec_r; r+=1<dec_r) { for (g = c->vmin<dec_g; g<=c->vmax<dec_g; g+=1<dec_g) { 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>>=to->dec_r; g>>=to->dec_g; // More than half of the cluster pixel have r = rmin. Ensure we split somewhere anyway. if (r == c->rmin) r++; 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) // split on green { for (g=c->vmin<dec_g;g<=c->vmax<dec_g;g+=1<dec_g) { for (r=c->rmin<dec_r;r<=c->rmax<dec_r;r+=1<dec_r) { 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>>=to->dec_r; g>>=to->dec_g; if (g == c->vmin) g++; 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 // split on blue { for (b=c->bmin;b<=c->bmax;b++) { for (g=c->vmin<dec_g;g<=c->vmax<dec_g;g+=1<dec_g) { for (r=c->rmin<dec_r;r<=c->rmax<dec_r;r+=1<dec_r) { cumul+=to->table[r + g + b]; if (cumul>=limit) break; } if (cumul>=limit) break; } if (cumul>=limit) break; } r>>=to->dec_r; g>>=to->dec_g; if (b == c->bmin) b++; 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; } } #endif // GRAFX2_QUANTIZE_CLUSTER_POPULATION_SPLIT /// Compute the mean R, G, B (for palette generation) and H, L (for palette sorting) 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->data.pal.r=(cumul_r<red_r)/c->occurences; c->data.pal.g=(cumul_g<red_g)/c->occurences; c->data.pal.b=(cumul_b<red_b)/c->occurences; RGB_to_HSL(c->data.pal.r, c->data.pal.g, c->data.pal.b, &c->data.pal.h, &s, &c->data.pal.l); } // Cluster set management // A set of clusters in handled as a list, the median cut algorithm pops a // cluster from the list, split it, and pushes back the two splitted clusters // until the lit grows to 256 items // Debug helper : check if a cluster set has the right count value /* void CS_Check(T_Cluster_set* cs) { int i; T_Cluster* c = cs->clusters; for (i = cs->nb; i > 0; i--) { assert( c != NULL); c = c->next; } assert(c == NULL); } */ /* 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); } */ // translate R G B values to 8 8 8 void CT_set_trad(CT_Tree* colorTree, byte Rmin, byte Gmin, byte Bmin, byte Rmax, byte Gmax, byte Bmax, byte index, const T_Occurrence_table * to) { Rmin <<= to->red_r; Rmax <<= to->red_r; Rmax += ((1 << to->red_r) - 1); Gmin <<= to->red_g; Gmax <<= to->red_g; Gmax += ((1 << to->red_g) - 1); Bmin <<= to->red_b; Bmax <<= to->red_b; Bmax += ((1 << to->red_b) - 1); CT_set(colorTree, Rmin, Gmin, Bmin, Rmax, Gmax, Bmax, index); } /// Setup the first cluster before we start the operations /// This one covers the full palette range void CS_Init(T_Cluster_set * cs, T_Occurrence_table * to) { cs->clusters->Rmin = cs->clusters->rmin = 0; cs->clusters->Gmin = cs->clusters->vmin = 0; cs->clusters->Bmin = cs->clusters->bmin = 0; cs->clusters->Rmax = cs->clusters->rmax = to->rng_r - 1; cs->clusters->Vmax = cs->clusters->vmax = to->rng_g - 1; cs->clusters->Bmax = cs->clusters->bmax = to->rng_b - 1; cs->clusters->next = NULL; Cluster_pack(cs->clusters, to); cs->nb = 1; } /// Allocate a new cluster set 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 != NULL) { // Copy requested params n->nb_max = OT_count_colors(to); // If the number of colors asked is > 256, we ceil it because we know we // don't want more if (n->nb_max > nbmax) { n->nb_max = nbmax; } // Allocate the first cluster n->clusters=(T_Cluster *)malloc(sizeof(T_Cluster)); if (n->clusters != NULL) CS_Init(n, to); else { // No memory free ! Sorry ! free(n); n = NULL; } } return n; } /// Free a cluster set void CS_Delete(T_Cluster_set * cs) { T_Cluster* nxt; while (cs->clusters != NULL) { nxt = cs->clusters->next; free(cs->clusters); cs->clusters = nxt; } free(cs); cs = NULL; } /// Pop a cluster from the cluster list void CS_Get(T_Cluster_set * cs, T_Cluster ** c) { // Just remove and return the first cluster, which has the biggest volume. // or the longest diagonal *c = cs->clusters; cs->clusters = (*c)->next; --cs->nb; } /// Push a copy of a cluster in the list /// return -1 in case of error int CS_Set(T_Cluster_set * cs,T_Cluster * c) { T_Cluster* current = cs->clusters; T_Cluster* prev = NULL; // Search the first cluster that is smaller than ours #ifdef GRAFX2_QUANTIZE_CLUSTER_SORT_BY_VOLUME while (current && current->data.cut.volume > c->data.cut.volume) #else while (current && current->data.cut.sqdiag > c->data.cut.sqdiag) #endif { prev = current; current = current->next; } // Now insert our cluster just before the one we found c -> next = current; current = malloc(sizeof(T_Cluster)); if(current == NULL) return -1; *current = *c ; if (prev) prev->next = current; else cs->clusters = current; cs->nb++; return 0; } /// This is the main median cut algorithm and the function actually called to /// reduce the palette. We get the number of pixels for each collor in the /// occurence table and generate the cluster set from it. // 1) RGB space is a big box // 2) We seek the pixels with extreme values // 3) We split the box in 2 parts on its longest axis // 4) We pack the 2 resulting boxes again to leave no empty space between the box border and the first pixel // 5) We take the box with the biggest number of pixels inside and we split it again // 6) Iterate until there are 256 boxes. Associate each of them to its middle color // At the same time, put the split clusters in the color tree for later palette lookup int CS_Generate(T_Cluster_set * cs, const T_Occurrence_table * const to, CT_Tree* colorTree) { T_Cluster* current; T_Cluster Nouveau1; T_Cluster Nouveau2; // There are less than 256 boxes while (cs->nbnb_max) { // Get the biggest one CS_Get(cs,¤t); //Cluster_Print(current); // We are going to split this cluster, so add it to the color tree. It is a split cluster, // not a final one. We KNOW its two child will get added later (either because they are split, // or because they are part of the final cluster set). So, we add thiscluster with a NULL index. CT_set_trad(colorTree,current->Rmin, current->Gmin, current->Bmin, current->Rmax, current->Vmax, current->Bmax, 0, to); // Split it if (current->data.cut.volume <= 1) { // Sorry, but there's nothing more to split ! // The biggest cluster only has one color... free(current); break; } #ifndef GRAFX2_QUANTIZE_CLUSTER_POPULATION_SPLIT Cluster_split_volume(current, &Nouveau1, &Nouveau2, current->data.cut.plus_large); #else Cluster_split(current, &Nouveau1, &Nouveau2, current->data.cut.plus_large, to); #endif free(current); // Pack the 2 new clusters (the split may leave some empty space between the // box border and the first actual pixel) Cluster_pack(&Nouveau1, to); Cluster_pack(&Nouveau2, to); // Put them back in the list if (Nouveau1.occurences != 0) { if(CS_Set(cs,&Nouveau1) < 0) return -1; } if (Nouveau2.occurences != 0) { if(CS_Set(cs,&Nouveau2) < 0) return -1; } } return 0; } /// Compute the color associated to each box in the list void CS_Compute_colors(T_Cluster_set * cs, T_Occurrence_table * to) { T_Cluster * c; for (c=cs->clusters;c!=NULL;c=c->next) { Cluster_compute_hue(c,to); } } // We sort the clusters on two criterions to get a somewhat coherent palette. // TODO : It would be better to do this in one single pass. /// Sort the clusters by chrominance value void CS_Sort_by_chrominance(T_Cluster_set * cs) { T_Cluster* nc; T_Cluster* prev = NULL; T_Cluster* place; T_Cluster* newlist = NULL; while (cs->clusters) { // Remove the first cluster from the original list nc = cs->clusters; cs->clusters = cs->clusters->next; // Find his position in the new list for (place = newlist; place != NULL; place = place->next) { if (place->data.pal.h > nc->data.pal.h) break; prev = place; } // Chain it there nc->next = place; if (prev) prev->next = nc; else newlist = nc; prev = NULL; } // Put the new list back in place cs->clusters = newlist; } /// Sort the clusters by luminance value void CS_Sort_by_luminance(T_Cluster_set * cs) { T_Cluster* nc; T_Cluster* prev = NULL; T_Cluster* place; T_Cluster* newlist = NULL; while (cs->clusters) { // Remove the first cluster from the original list nc = cs->clusters; cs->clusters = cs->clusters->next; // Find its position in the new list for (place = newlist; place != NULL; place = place->next) { if (place->data.pal.l > nc->data.pal.l) break; prev = place; } // Chain it there nc->next = place; if (prev) prev->next = nc; else newlist = nc; // reset prev pointer prev = NULL; } // Put the new list back in place cs->clusters = newlist; } /// Generates the palette from the clusters, then the conversion table to map (RGB) to a palette index void CS_Generate_color_table_and_palette(T_Cluster_set * cs,CT_Tree* tc,T_Components * palette, T_Occurrence_table * to) { int index; T_Cluster* current = cs->clusters; for (index=0;indexnb;index++) { palette[index].R=current->data.pal.r; palette[index].G=current->data.pal.g; palette[index].B=current->data.pal.b; CT_set_trad(tc,current->Rmin, current->Gmin, current->Bmin, current->Rmax, current->Vmax, current->Bmax, index, to); current = current->next; } } ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////// 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->data.pal.h; ds->gradients[0].max=cs->clusters->data.pal.h; ds->gradients[0].hue=cs->clusters->data.pal.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=NULL; } } 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 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 T_Cluster * current = cs->clusters; // Pour chacun des clusters … traiter do { // 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(current->data.pal.h - (int)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 (current->data.pal.h < ds->gradients[best_gradient].min) ds->gradients[best_gradient].min=current->data.pal.h; if (current->data.pal.h > ds->gradients[best_gradient].max) ds->gradients[best_gradient].max=current->data.pal.h; ds->gradients[best_gradient].hue=((ds->gradients[best_gradient].hue* ds->gradients[best_gradient].nb_colors) +current->data.pal.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=current->data.pal.h; ds->gradients[best_gradient].max=current->data.pal.h; ds->gradients[best_gradient].hue=current->data.pal.h; ds->nb++; } current->data.pal.h=best_gradient; } while((current = current->next)); // On redistribue les valeurs dans les clusters current = cs -> clusters; do current->data.pal.h=ds->gradients[current->data.pal.h].hue; while((current = current ->next)); } /// Compute best palette for given picture. /// /// The picture is first depth-reduced to the given /// r,g,b resolution, then the median cut algorithm is used to find 256 colors which are suitable /// for the given picture. /// /// @returns a conversion tree to be used for converting the picture to indexed with the generated palette (with or without dithering). /// /// @param image The true-color image for which the palette needs to be optimized /// @param size in pixels (number of pixels, the height/width doesn't matter) /// @param palette pointer to the space where the palette will be stored (256 entries at most) /// @param r Resolution for red /// @param g Resolution for green /// @param b Resolution for blue CT_Tree* Optimize_palette(T_Bitmap24B image, int size, T_Components * palette, int r, int g, int b) { T_Occurrence_table * to; CT_Tree* tc; T_Cluster_set * cs; T_Gradient_set * ds; // Allocate all the elements to = 0; tc = 0; cs = 0; ds = 0; to = OT_new(r, g, b); if (to == NULL) return 0; tc = CT_new(); if (tc == NULL) { OT_delete(to); return NULL; } // Count pixels for each color OT_count_occurrences(to, image, size); cs = CS_New(256, to); if (cs == NULL) { CT_delete(tc); OT_delete(to); return NULL; } //CS_Check(cs); // Ok, everything was allocated // Generate the cluster set with median cut algorithm if(CS_Generate(cs, to, tc) < 0) { CS_Delete(cs); CT_delete(tc); OT_delete(to); return NULL; } //CS_Check(cs); // Compute the color data for each cluster (palette entry + HL) CS_Compute_colors(cs, to); //CS_Check(cs); ds = GS_New(cs); if (ds!= NULL) { GS_Generate(ds, cs); GS_Delete(ds); } // Sort the clusters on L and H to get a nice palette CS_Sort_by_luminance(cs); //CS_Check(cs); CS_Sort_by_chrominance(cs); //CS_Check(cs); // And finally generate the conversion table to map RGB > pal. index CS_Generate_color_table_and_palette(cs, tc, palette, to); //CS_Check(cs); CS_Delete(cs); OT_delete(to); return tc; } /// Change a value with proper ceiling and flooring int Modified_value(int value,int modif) { value+=modif; if (value<0) { value=0; } else if (value>255) { value=255; } return value; } /// Convert a 24b image to 256 colors (with a given palette and conversion table) /// This destroys the 24b picture ! /// Uses floyd steinberg dithering. void Convert_24b_bitmap_to_256_Floyd_Steinberg(T_Bitmap256 dest,T_Bitmap24B source,int width,int height,T_Components * palette,CT_Tree* tc) { 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++; } } } /// Converts from 24b to 256c without dithering, using given conversion table void Convert_24b_bitmap_to_256_nearest_neighbor(T_Bitmap256 dest, T_Bitmap24B source, int width, int height, T_Components * palette, CT_Tree* tc) { T_Bitmap24B current; T_Bitmap256 d; int x_pos, y_pos; int red, green, blue; (void)palette; // unused // On initialise les variables de parcours: current =source; // Le pixel dont on s'occupe d =dest; // On parcours chaque pixel: for (y_pos = 0; y_pos < height; y_pos++) { for (x_pos = 0 ;x_pos < width; x_pos++) { // On prends la meilleure couleur de la palette qui traduit la couleur // 24 bits de la source: red = current->R; 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); // On passe au pixel suivant : current++; d++; } } } // Count colors and convert if 256 colors or less are used // return 0 for success int Try_Convert_to_256_Without_Loss(T_Bitmap256 dest,T_Bitmap24B source,int width,int height,T_Components * palette) { int i; int n = 0; // number of colors long index; T_Bitmap24B ptr; for (index = width * height, ptr = source; index > 0; index--, ptr++) { // look for the color in the table for (i = 0; i < n; i++) { if(palette[i].R == ptr->R && palette[i].G == ptr->G && palette[i].B == ptr->B) break; // found ! } if (i >= n) { if (n > 255) { // there are more than 256 colors return 1; } // need to add the color in the palette palette[n].R = ptr->R; palette[n].G = ptr->G; palette[n].B = ptr->B; n++; } *dest = (byte)i; dest++; } // TODO : Sort the palette ? return 0; } // These are the allowed precisions for all the tables. // For some of them only the first one may work because of ugly optimizations 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}; // Give this one a 24b source, get back the 256c bitmap and its palette int Convert_24b_bitmap_to_256(T_Bitmap256 dest,T_Bitmap24B source,int width,int height,T_Components * palette) { CT_Tree* table; // table de conversion int ip; // index de précision pour la conversion if (Try_Convert_to_256_Without_Loss(dest, source, width, height, palette) == 0) return 0; #if defined(__GP2X__) || defined(__gp2x__) || defined(__WIZ__) || defined(__CAANOO__) return Convert_24b_bitmap_to_256_fast(dest, source, width, height, palette); #else // 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], precision_24b[ip+1], precision_24b[ip+2]); if (table != NULL) { break; } } if (table!=NULL) { //Convert_24b_bitmap_to_256_Floyd_Steinberg(dest,source,width,height,palette,table); Convert_24b_bitmap_to_256_nearest_neighbor(dest,source,width,height,palette,table); CT_delete(table); return 0; } else return 1; #endif } //Really small, fast and ugly converter(just for handhelds) #include "global.h" #include #include "engine.h" #include "windows.h" extern void Set_palette_fake_24b(T_Palette palette); /// Really small, fast and dirty convertor(just for handhelds) int Convert_24b_bitmap_to_256_fast(T_Bitmap256 dest,T_Bitmap24B source,int width,int height,T_Components * palette) { int size; Set_palette_fake_24b(palette); size = width*height; while(size--) { //Set palette color index to destination bitmap *dest = ((source->R >> 5) << 5) | ((source->G >> 5) << 2) | ((source->B >> 6)); source++; dest++; } return 0; }