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