1 /********************************************************************************
2 *                                                                               *
3 *                     W U   C o l o r   Q u a n t i z a t i o n                 *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 2004,2005 by Jeroen van der Zijp.   All Rights Reserved.        *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or                 *
9 * modify it under the terms of the GNU Lesser General Public                    *
10 * License as published by the Free Software Foundation; either                  *
11 * version 2.1 of the License, or (at your option) any later version.            *
12 *                                                                               *
13 * This library is distributed in the hope that it will be useful,               *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of                *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU             *
16 * Lesser General Public License for more details.                               *
17 *                                                                               *
18 * You should have received a copy of the GNU Lesser General Public              *
19 * License along with this library; if not, write to the Free Software           *
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.    *
21 *********************************************************************************
22 * $Id: fxwuquantize.cpp,v 1.8 2005/01/16 16:06:07 fox Exp $                     *
23 ********************************************************************************/
24 #include "xincs.h"
25 #include "fxver.h"
26 #include "fxdefs.h"
27 #include "fxpriv.h"
28 
29 /*
30   Notes:
31 
32   - This code is due to: Xiaolin Wu, Dept. of Computer Science, Univ. of
33     Western Ontario, London, Ontario N6A 5B7 (wu@csd.uwo.ca).
34     Original algorithm can be found in Graphics Gems vol. II, pp. 126-133.
35 
36   - Algorithm: Greedy orthogonal bipartition of RGB space for variance
37     minimization aided by inclusion-exclusion tricks.
38     For speed no nearest neighbor search is done. Slightly
39     better performance can be expected by more sophisticated
40     but more expensive versions.
41 
42   - Modified by Jeroen for FOX; don't blame the original author if I
43     broke it.
44 
45 */
46 
47 #define MAXCOLOR  256
48 #define	RED       2
49 #define	GREEN     1
50 #define BLUE      0
51 
52 
53 using namespace FX;
54 
55 /*******************************************************************************/
56 
57 namespace FX {
58 
59 
60 // Sub bpx
61 struct box {
62   FXint r0;                     // Min value, exclusive
63   FXint r1;                     // Max value, inclusive
64   FXint g0;
65   FXint g1;
66   FXint b0;
67   FXint b1;
68   FXint vol;                    // Volume
69   };
70 
71 
72 // To pass around
73 struct WU {
74   FXfloat m2[33][33][33];       // Histogram is in elements 1..HISTSIZE along each axis,
75   FXint   wt[33][33][33];       // element 0 is for base or marginal value
76   FXint   mr[33][33][33];       // NB: these must start out 0!
77   FXint   mg[33][33][33];
78   FXint   mb[33][33][33];
79   };
80 
81 
82 
83 // At conclusion of the histogram step, we can interpret
84 //   wt[r][g][b] = sum over voxel of P(c)
85 //   mr[r][g][b] = sum over voxel of r*P(c)  ,  similarly for mg, mb
86 //   m2[r][g][b] = sum over voxel of c^2*P(c)
87 // Actually each of these should be divided by 'size' to give the usual
88 // interpretation of P() as ranging from 0 to 1, but we needn't do that here.
89 
90 // Build 3-D color histogram of counts, r/g/b, c^2
histogram(WU & wu,const FXColor * data,FXint size)91 static void histogram(WU& wu,const FXColor *data,FXint size){
92   register FXint r,g,b,inr,ing,inb,i;
93 
94   // Clear counters
95   memset(&wu,0,sizeof(wu));
96 
97   // Build histogram
98   for(i=0; i<size; ++i){
99     r=((const FXuchar*)(data+i))[0];
100     g=((const FXuchar*)(data+i))[1];
101     b=((const FXuchar*)(data+i))[2];
102     inr=(r>>3)+1;
103     ing=(g>>3)+1;
104     inb=(b>>3)+1;
105     wu.wt[inr][ing][inb]+=1;
106     wu.mr[inr][ing][inb]+=r;
107     wu.mg[inr][ing][inb]+=g;
108     wu.mb[inr][ing][inb]+=b;
109     wu.m2[inr][ing][inb]+=(FXfloat)(r*r+g*g+b*b);
110     }
111   }
112 
113 
114 // Compute cumulative moments
moments(WU & wu)115 static void moments(WU& wu){
116   register FXint linet,liner,lineg,lineb,i,r,g,b;
117   FXint areat[33],arear[33],areag[33],areab[33];
118   FXfloat line2,area2[33];
119   for(r=1; r<=32; ++r){
120     for(i=0; i<=32; ++i){
121       areat[i]=0;
122       arear[i]=0;
123       areag[i]=0;
124       areab[i]=0;
125       area2[i]=0.0f;
126       }
127     for(g=1; g<=32; ++g){
128       linet=0;
129       liner=0;
130       lineg=0;
131       lineb=0;
132       line2=0.0f;
133       for(b=1; b<=32; ++b){
134         linet+=wu.wt[r][g][b];
135         liner+=wu.mr[r][g][b];
136         lineg+=wu.mg[r][g][b];
137         lineb+=wu.mb[r][g][b];
138         line2+=wu.m2[r][g][b];
139         areat[b]+=linet;
140         arear[b]+=liner;
141         areag[b]+=lineg;
142         areab[b]+=lineb;
143         area2[b]+=line2;
144         wu.wt[r][g][b]=wu.wt[r-1][g][b]+areat[b];
145         wu.mr[r][g][b]=wu.mr[r-1][g][b]+arear[b];
146         wu.mg[r][g][b]=wu.mg[r-1][g][b]+areag[b];
147         wu.mb[r][g][b]=wu.mb[r-1][g][b]+areab[b];
148         wu.m2[r][g][b]=wu.m2[r-1][g][b]+area2[b];
149         }
150       }
151     }
152   }
153 
154 
155 // Compute sum over a box of any given statistic
volume(box & cube,FXint mmt[33][33][33])156 static int volume(box& cube,FXint mmt[33][33][33]){
157   return  mmt[cube.r1][cube.g1][cube.b1]
158          -mmt[cube.r1][cube.g1][cube.b0]
159          -mmt[cube.r1][cube.g0][cube.b1]
160          +mmt[cube.r1][cube.g0][cube.b0]
161          -mmt[cube.r0][cube.g1][cube.b1]
162          +mmt[cube.r0][cube.g1][cube.b0]
163          +mmt[cube.r0][cube.g0][cube.b1]
164          -mmt[cube.r0][cube.g0][cube.b0];
165   }
166 
167 
168 // The next two routines allow a slightly more efficient calculation
169 // of Vol() for a proposed subbox of a given box.  The sum of Top()
170 // and Bottom() is the Vol() of a subbox split in the given direction
171 // and with the specified new upper bound.
172 
173 // Compute part of Vol(cube, mmt) that doesn't depend
174 // on r1, g1, or b1 (depending on dir)
bottom(box & cube,FXuchar dir,FXint mmt[33][33][33])175 static FXint bottom(box& cube,FXuchar dir,FXint mmt[33][33][33]){
176   register FXint result=0;
177   switch(dir){
178     case RED:
179       result= -mmt[cube.r0][cube.g1][cube.b1]
180               +mmt[cube.r0][cube.g1][cube.b0]
181               +mmt[cube.r0][cube.g0][cube.b1]
182               -mmt[cube.r0][cube.g0][cube.b0];
183       break;
184     case GREEN:
185       result= -mmt[cube.r1][cube.g0][cube.b1]
186               +mmt[cube.r1][cube.g0][cube.b0]
187               +mmt[cube.r0][cube.g0][cube.b1]
188               -mmt[cube.r0][cube.g0][cube.b0];
189       break;
190     case BLUE:
191       result= -mmt[cube.r1][cube.g1][cube.b0]
192               +mmt[cube.r1][cube.g0][cube.b0]
193               +mmt[cube.r0][cube.g1][cube.b0]
194               -mmt[cube.r0][cube.g0][cube.b0];
195       break;
196     }
197   return result;
198   }
199 
200 
201 // Compute remainder of Vol(cube, mmt), substituting pos
202 // for r1, g1, or b1 (depending on dir)
top(box & cube,FXuchar dir,FXint pos,FXint mmt[33][33][33])203 static FXint top(box& cube,FXuchar dir,FXint pos,FXint mmt[33][33][33]){
204   register FXint result=0;
205   switch(dir){
206     case RED:
207       result= mmt[pos][cube.g1][cube.b1]
208              -mmt[pos][cube.g1][cube.b0]
209              -mmt[pos][cube.g0][cube.b1]
210              +mmt[pos][cube.g0][cube.b0];
211       break;
212     case GREEN:
213       result= mmt[cube.r1][pos][cube.b1]
214              -mmt[cube.r1][pos][cube.b0]
215              -mmt[cube.r0][pos][cube.b1]
216              +mmt[cube.r0][pos][cube.b0];
217       break;
218     case BLUE:
219       result= mmt[cube.r1][cube.g1][pos]
220              -mmt[cube.r1][cube.g0][pos]
221              -mmt[cube.r0][cube.g1][pos]
222              +mmt[cube.r0][cube.g0][pos];
223       break;
224     }
225   return result;
226   }
227 
228 
229 // Compute the weighted variance of a box
230 // NB: as with the raw statistics, this is really the variance * size
variance(WU & wu,box & cube)231 static FXfloat variance(WU& wu,box& cube){
232   register FXfloat dr,dg,db,xx;
233 
234   dr = (FXfloat)volume(cube,wu.mr);
235   dg = (FXfloat)volume(cube,wu.mg);
236   db = (FXfloat)volume(cube,wu.mb);
237 
238   xx =  wu.m2[cube.r1][cube.g1][cube.b1]
239        -wu.m2[cube.r1][cube.g1][cube.b0]
240        -wu.m2[cube.r1][cube.g0][cube.b1]
241        +wu.m2[cube.r1][cube.g0][cube.b0]
242        -wu.m2[cube.r0][cube.g1][cube.b1]
243        +wu.m2[cube.r0][cube.g1][cube.b0]
244        +wu.m2[cube.r0][cube.g0][cube.b1]
245        -wu.m2[cube.r0][cube.g0][cube.b0];
246 
247   return xx-(dr*dr+dg*dg+db*db)/(FXfloat)volume(cube,wu.wt);
248   }
249 
250 
251 // We want to minimize the sum of the variances of two subboxes.
252 // The sum(c^2) terms can be ignored since their sum over both subboxes
253 // is the same (the sum for the whole box) no matter where we split.
254 // The remaining terms have a minus sign in the variance formula,
255 // so we drop the minus sign and MAXIMIZE the sum of the two terms.
maximize(WU & wu,box & cube,FXuchar dir,FXint first,FXint last,FXint * cut,FXint whole_r,FXint whole_g,FXint whole_b,FXint whole_w)256 static FXfloat maximize(WU& wu,box& cube,FXuchar dir,FXint first,FXint last,FXint *cut,FXint whole_r,FXint whole_g,FXint whole_b,FXint whole_w){
257   register FXint half_r,half_g,half_b,half_w,base_r,base_g,base_b,base_w,i;
258   register FXfloat temp,max;
259 
260   base_r=bottom(cube,dir,wu.mr);
261   base_g=bottom(cube,dir,wu.mg);
262   base_b=bottom(cube,dir,wu.mb);
263   base_w=bottom(cube,dir,wu.wt);
264 
265   max=0.0f;
266   *cut=-1;
267   for(i=first; i<last; ++i){
268 
269     half_r=base_r+top(cube,dir,i,wu.mr);
270     half_g=base_g+top(cube,dir,i,wu.mg);
271     half_b=base_b+top(cube,dir,i,wu.mb);
272     half_w=base_w+top(cube,dir,i,wu.wt);
273 
274     // Now half_x is sum over lower half of box, if split at i
275 
276     // Subbox could be empty of pixels; never split into an empty box
277     if(half_w==0) continue;
278 
279     temp=((FXfloat)half_r*half_r + (FXfloat)half_g*half_g + (FXfloat)half_b*half_b)/half_w;
280 
281     half_r=whole_r-half_r;
282     half_g=whole_g-half_g;
283     half_b=whole_b-half_b;
284     half_w=whole_w-half_w;
285 
286     // Subbox could be empty of pixels; never split into an empty box
287     if(half_w==0) continue;
288 
289     temp += ((FXfloat)half_r*half_r + (FXfloat)half_g*half_g + (FXfloat)half_b*half_b)/half_w;
290 
291     if(temp>max){ max=temp; *cut=i; }
292     }
293   return max;
294   }
295 
296 
297 // Find best split
cut(WU & wu,box & set1,box & set2)298 static FXint cut(WU& wu,box& set1,box& set2){
299   FXint cutr, cutg, cutb;
300   FXfloat maxr, maxg, maxb;
301   FXint whole_r, whole_g, whole_b, whole_w;
302   FXuchar dir;
303 
304   // Totals
305   whole_r=volume(set1,wu.mr);
306   whole_g=volume(set1,wu.mg);
307   whole_b=volume(set1,wu.mb);
308   whole_w=volume(set1,wu.wt);
309 
310   // Find most beneficial split direction
311   maxr=maximize(wu,set1,  RED,set1.r0+1,set1.r1,&cutr,whole_r,whole_g,whole_b,whole_w);
312   maxg=maximize(wu,set1,GREEN,set1.g0+1,set1.g1,&cutg,whole_r,whole_g,whole_b,whole_w);
313   maxb=maximize(wu,set1, BLUE,set1.b0+1,set1.b1,&cutb,whole_r,whole_g,whole_b,whole_w);
314 
315   // Direction of split?
316   if((maxr>=maxg) && (maxr>=maxb)){
317     if(cutr<0) return 0;        // Can't split the box
318     dir=RED;
319     }
320   else if((maxg>=maxr) && (maxg>=maxb)){
321     dir=GREEN;
322     }
323   else{
324     dir=BLUE;
325     }
326 
327   set2.r1=set1.r1;
328   set2.g1=set1.g1;
329   set2.b1=set1.b1;
330 
331   switch(dir){
332     case RED:
333       set2.r0=set1.r1=cutr;
334       set2.g0=set1.g0;
335       set2.b0=set1.b0;
336       break;
337     case GREEN:
338       set2.g0=set1.g1=cutg;
339       set2.r0=set1.r0;
340       set2.b0=set1.b0;
341       break;
342     case BLUE:
343       set2.b0=set1.b1=cutb;
344       set2.r0=set1.r0;
345       set2.g0=set1.g0;
346       break;
347     }
348 
349   set1.vol=(set1.r1-set1.r0)*(set1.g1-set1.g0)*(set1.b1-set1.b0);
350   set2.vol=(set2.r1-set2.r0)*(set2.g1-set2.g0)*(set2.b1-set2.b0);
351   return 1;
352   }
353 
354 
355 // Each entry in box maps to label
mark(box & cube,FXint label,FXuchar map[33][33][33])356 static void mark(box& cube,FXint label,FXuchar map[33][33][33]){
357   register FXint r,g,b;
358   for(r=cube.r0+1; r<=cube.r1; ++r){
359     for(g=cube.g0+1; g<=cube.g1; ++g){
360       for(b=cube.b0+1; b<=cube.b1; ++b){
361         map[r][g][b]=label;
362         }
363       }
364     }
365   }
366 
367 
368 // Wu's quantization method based on recursive partitioning
fxwuquantize(FXuchar * dst,const FXColor * src,FXColor * colormap,FXint & actualcolors,FXint w,FXint h,FXint maxcolors)369 FXbool fxwuquantize(FXuchar* dst,const FXColor* src,FXColor* colormap,FXint& actualcolors,FXint w,FXint h,FXint maxcolors){
370   register FXint i,k,weight,next,size,r,g,b;
371   register FXfloat temp;
372   FXuchar  map[33][33][33];
373   FXfloat  vv[MAXCOLOR];
374   box      cube[MAXCOLOR];
375   WU       wu;
376 
377   // Size of image
378   size=w*h;
379 
380   // Compute histogram
381   histogram(wu,src,size);
382   FXTRACE((1,"done hist\n"));
383 
384   // Compute moments
385   moments(wu);
386 
387   // Recursively split boxes
388   next=0;
389   cube[0].r0=cube[0].g0=cube[0].b0=0;
390   cube[0].r1=cube[0].g1=cube[0].b1=32;
391   for(i=1; i<maxcolors; ++i){
392     if(cut(wu,cube[next],cube[i])){
393       vv[next]=(cube[next].vol>1)?variance(wu,cube[next]):0.0f; // Volume test ensures we won't try to cut one-cell box
394       vv[i]=(cube[i].vol>1)?variance(wu,cube[i]):0.0f;
395       }
396     else{
397       vv[next]=0.0f;                                            // Don't try to split this box again
398       i--;                                                      // Didn't create box i
399       }
400     next=0;
401     temp=vv[0];
402     for(k=1; k<=i; ++k){
403       if(vv[k]>temp){
404         temp=vv[k];
405         next=k;
406         }
407       }
408     if(temp<=0.0f){
409       maxcolors=i+1;
410       FXTRACE((1,"Only got %d boxes\n",maxcolors));
411       break;
412       }
413     }
414 
415   FXTRACE((1,"done partition\n"));
416 
417   // Construct colormap
418   for(k=0; k<maxcolors; ++k){
419     mark(cube[k],k,map);
420     weight=volume(cube[k],wu.wt);
421     if(weight){
422       ((FXuchar*)(colormap+k))[0]=volume(cube[k],wu.mr)/weight;
423       ((FXuchar*)(colormap+k))[1]=volume(cube[k],wu.mg)/weight;
424       ((FXuchar*)(colormap+k))[2]=volume(cube[k],wu.mb)/weight;
425       ((FXuchar*)(colormap+k))[3]=255;
426       }
427     else{
428       FXTRACE((1,"bogus box %d\n",k));
429       ((FXuchar*)(colormap+k))[0]=0;
430       ((FXuchar*)(colormap+k))[1]=0;
431       ((FXuchar*)(colormap+k))[2]=0;
432       ((FXuchar*)(colormap+k))[3]=0;
433       }
434     }
435 
436   FXTRACE((1,"done mapping\n"));
437 
438   // Build histogram
439   for(i=0; i<size; ++i){
440     r=((const FXuchar*)(src+i))[0];
441     g=((const FXuchar*)(src+i))[1];
442     b=((const FXuchar*)(src+i))[2];
443     dst[i]=map[(r>>3)+1][(g>>3)+1][(b>>3)+1];
444     }
445 
446   FXTRACE((1,"done transform\n"));
447 
448   // Return actual number of colors
449   actualcolors=maxcolors;
450 
451   return TRUE;
452   }
453 
454 }
455