/* * This software is copyrighted as noted below. It may be freely copied, * modified, and redistributed, provided that the copyright notice is * preserved on all copies. * * There is no warranty or other guarantee of fitness for this software, * it is provided solely "as is". Bug reports or fixes may be sent * to the author, who may or may not act on them as he desires. * * You may not include this software in a program or other software product * without supplying the source, or without informing the end-user that the * source is available for no extra charge. * * If you modify this software, you should include a notice giving the * name of the person performing the modification, the date of modification, * and the reason for such modification. */ /* * quantize.c - quantize a bitmap. * * Author: Xiaolin Wu * Dept. of Computer Science * Univ. of Western Ontario * London, Ontario N6A 5B7 * wu@csd.uwo.ca * * Lug's author: Raul Rivero * Mathematics Dept. * University of Oviedo * Date: Fri Jun 26 1992 */ /********************************************************************** C Implementation of Wu's Color Quantizer (v. 2) (see Graphics Gems vol. II, pp. 126-133) Author: Xiaolin Wu Dept. of Computer Science Univ. of Western Ontario London, Ontario N6A 5B7 wu@csd.uwo.ca Algorithm: Greedy orthogonal bipartition of RGB space for variance minimization aided by inclusion-exclusion tricks. For speed no nearest neighbor search is done. Slightly better performance can be expected by more sophisticated but more expensive versions. The author thanks Tom Lane at Tom_Lane@G.GP.CS.CMU.EDU for much of additional documentation and a cure to a previous bug. Free to distribute, comments and suggestions are appreciated. **********************************************************************/ /* * History: * * Raul Rivero - Fri Jun 26 1992 * One problem was detected when multiple calls to the * quantizer. The resulting color map was incoherent. The * problem was fixed reseting all the static buffers. * */ #include #include #define MAXCOLOR 256 #define RED 2 #define GREEN 1 #define BLUE 0 struct box { int r0; /* min value, exclusive */ int r1; /* max value, inclusive */ int g0; int g1; int b0; int b1; int vol; }; /* * Histogram is in elements 1..HISTSIZE along each axis, * element 0 is for base or marginal value * NB: these must start out 0! */ float m2[33][33][33]; long int wt[33][33][33], mr[33][33][33], mg[33][33][33], mb[33][33][33]; byte *Ir, *Ig, *Ib; int size; /*image size*/ int K; /*color look-up table size*/ ushort *Qadd; extern int LUGverbose; float Var(); float Maximize(); long Vol(); long Bottom(); long Top(); quantize(inbitmap, outbitmap, no_colors) bitmap_hdr *inbitmap; bitmap_hdr *outbitmap; int no_colors; { struct box cube[MAXCOLOR]; byte *tag; byte lut_r[MAXCOLOR], lut_g[MAXCOLOR], lut_b[MAXCOLOR]; int next; register long int i, weight; register int k; float vv[MAXCOLOR], temp; byte *ptr; if ( inbitmap->magic != LUGUSED ) error( 19 ); /* set the image */ K = no_colors; Ir = inbitmap->r; Ig = inbitmap->g; Ib = inbitmap->b; size = inbitmap->xsize * inbitmap->ysize; /* Reset the buffers */ bzero( m2, 33*33*33*sizeof(float) ); bzero( wt, 33*33*33*sizeof(long) ); bzero( mr, 33*33*33*sizeof(long) ); bzero( mg, 33*33*33*sizeof(long) ); bzero( mb, 33*33*33*sizeof(long) ); Hist3d(wt, mr, mg, mb, m2); VPRINTF(stderr, "Histogram done\n"); M3d(wt, mr, mg, mb, m2); VPRINTF(stderr, "Moments done\n"); cube[0].r0 = cube[0].g0 = cube[0].b0 = 0; cube[0].r1 = cube[0].g1 = cube[0].b1 = 32; next = 0; for( i = 1; i < K; ++i ) { if ( Cut(&cube[next], &cube[i]) ) { /* volume test ensures we won't try to cut one-cell box */ vv[next] = (cube[next].vol>1) ? Var(&cube[next]) : 0.0; vv[i] = (cube[i].vol>1) ? Var(&cube[i]) : 0.0; } else { vv[next] = 0.0; /* don't try to split this box again */ i--; /* didn't create box i */ } next = 0; temp = vv[0]; for ( k = 1; k <= i; ++k ) if ( vv[k] > temp ) { temp = vv[k]; next = k; } if (temp <= 0.0) { K = i+1; VPRINTF(stderr, "Only got %d boxes\n", K); break; } } VPRINTF(stderr, "Partition done\n"); /* the space for array m2 can be freed now */ tag = (byte *) Malloc(33*33*33); for(k = 0; k < K; ++k ){ Mark( &cube[k], k, tag ); weight = Vol( &cube[k], wt ); if ( weight ) { lut_r[k] = Vol(&cube[k], mr) / weight; lut_g[k] = Vol(&cube[k], mg) / weight; lut_b[k] = Vol(&cube[k], mb) / weight; } else { VPRINTF(stderr, "bogus box %d\n", k); lut_r[k] = lut_g[k] = lut_b[k] = 0; } } outbitmap->magic = LUGUSED; outbitmap->xsize = inbitmap->xsize; outbitmap->ysize = inbitmap->ysize; outbitmap->depth = 1 + no_bits( no_colors ); outbitmap->colors = no_colors; ptr = outbitmap->cmap = (byte *) Malloc( 3 * no_colors ); for( k = 0; k < K; ++k ) { *ptr++ = lut_r[k]; *ptr++ = lut_g[k]; *ptr++ = lut_b[k]; } ptr = outbitmap->r = (byte *) Malloc( size ); for( i = 0; i < size; ++i ) *ptr++ = tag[Qadd[i]]; free(Qadd); free(tag); } Hist3d(vwt, vmr, vmg, vmb, m2) /* build 3-D color histogram of counts, r/g/b, c^2 */ long int *vwt, *vmr, *vmg, *vmb; float *m2; { register int ind, r, g, b; int inr, ing, inb, table[256]; register long int i; for ( i = 0; i < 256; ++i ) table[i] = i * i; Qadd = (ushort *) Malloc( sizeof(short) * size ); for( i = 0; i < size; ++i ) { r = Ir[i]; g = Ig[i]; b = Ib[i]; inr = (r>>3) + 1; ing = (g>>3) + 1; inb = (b>>3) + 1; Qadd[i] = ind = (inr<<10) + (inr<<6) + inr + (ing<<5) + ing + inb; /*[inr][ing][inb]*/ ++vwt[ind]; vmr[ind] += r; vmg[ind] += g; vmb[ind] += b; m2[ind] += (float)(table[r]+table[g]+table[b]); } } /* At conclusion of the histogram step, we can interpret * wt[r][g][b] = sum over voxel of P(c) * mr[r][g][b] = sum over voxel of r*P(c) , similarly for mg, mb * m2[r][g][b] = sum over voxel of c^2*P(c) * Actually each of these should be divided by 'size' to give the usual * interpretation of P() as ranging from 0 to 1, but we needn't do that here. */ /* We now convert histogram into moments so that we can rapidly calculate * the sums of the above quantities over any desired box. */ M3d(vwt, vmr, vmg, vmb, m2) /* compute cumulative moments. */ long int *vwt, *vmr, *vmg, *vmb; float *m2; { register unsigned short int ind1, ind2; register byte i, r, g, b; long int line, line_r, line_g, line_b, area[33], area_r[33], area_g[33], area_b[33]; float line2, area2[33]; for ( r = 1; r <= 32; ++r ) { for ( i = 0; i <= 32; ++i ) area2[i] = area[i] = area_r[i] = area_g[i] = area_b[i] = 0; for ( g = 1; g <= 32; ++g ) { line2 = line = line_r = line_g = line_b = 0; for ( b = 1; b <= 32; ++b ) { ind1 = (r<<10) + (r<<6) + r + (g<<5) + g + b; /* [r][g][b] */ line += vwt[ind1]; line_r += vmr[ind1]; line_g += vmg[ind1]; line_b += vmb[ind1]; line2 += m2[ind1]; area[b] += line; area_r[b] += line_r; area_g[b] += line_g; area_b[b] += line_b; area2[b] += line2; ind2 = ind1 - 1089; /* [r-1][g][b] */ vwt[ind1] = vwt[ind2] + area[b]; vmr[ind1] = vmr[ind2] + area_r[b]; vmg[ind1] = vmg[ind2] + area_g[b]; vmb[ind1] = vmb[ind2] + area_b[b]; m2[ind1] = m2[ind2] + area2[b]; } } } } long Vol(cube, mmt) /* Compute sum over a box of any given statistic */ struct box *cube; long int mmt[33][33][33]; { return( mmt[cube->r1][cube->g1][cube->b1] -mmt[cube->r1][cube->g1][cube->b0] -mmt[cube->r1][cube->g0][cube->b1] +mmt[cube->r1][cube->g0][cube->b0] -mmt[cube->r0][cube->g1][cube->b1] +mmt[cube->r0][cube->g1][cube->b0] +mmt[cube->r0][cube->g0][cube->b1] -mmt[cube->r0][cube->g0][cube->b0] ); } /* The next two routines allow a slightly more efficient calculation * of Vol() for a proposed subbox of a given box. The sum of Top() * and Bottom() is the Vol() of a subbox split in the given direction * and with the specified new upper bound. */ long Bottom(cube, dir, mmt) /* Compute part of Vol(cube, mmt) that doesn't depend on r1, g1, or b1 */ /* (depending on dir) */ struct box *cube; byte dir; long int mmt[33][33][33]; { switch(dir){ case RED: return( -mmt[cube->r0][cube->g1][cube->b1] +mmt[cube->r0][cube->g1][cube->b0] +mmt[cube->r0][cube->g0][cube->b1] -mmt[cube->r0][cube->g0][cube->b0] ); break; case GREEN: return( -mmt[cube->r1][cube->g0][cube->b1] +mmt[cube->r1][cube->g0][cube->b0] +mmt[cube->r0][cube->g0][cube->b1] -mmt[cube->r0][cube->g0][cube->b0] ); break; case BLUE: return( -mmt[cube->r1][cube->g1][cube->b0] +mmt[cube->r1][cube->g0][cube->b0] +mmt[cube->r0][cube->g1][cube->b0] -mmt[cube->r0][cube->g0][cube->b0] ); break; } } long Top(cube, dir, pos, mmt) /* Compute remainder of Vol(cube, mmt), substituting pos for */ /* r1, g1, or b1 (depending on dir) */ struct box *cube; byte dir; int pos; long int mmt[33][33][33]; { switch(dir){ case RED: return( mmt[pos][cube->g1][cube->b1] -mmt[pos][cube->g1][cube->b0] -mmt[pos][cube->g0][cube->b1] +mmt[pos][cube->g0][cube->b0] ); break; case GREEN: return( mmt[cube->r1][pos][cube->b1] -mmt[cube->r1][pos][cube->b0] -mmt[cube->r0][pos][cube->b1] +mmt[cube->r0][pos][cube->b0] ); break; case BLUE: return( mmt[cube->r1][cube->g1][pos] -mmt[cube->r1][cube->g0][pos] -mmt[cube->r0][cube->g1][pos] +mmt[cube->r0][cube->g0][pos] ); break; } } float Var(cube) /* Compute the weighted variance of a box */ /* NB: as with the raw statistics, this is really the variance * size */ struct box *cube; { float dr, dg, db, xx; dr = Vol(cube, mr); dg = Vol(cube, mg); db = Vol(cube, mb); xx = m2[cube->r1][cube->g1][cube->b1] -m2[cube->r1][cube->g1][cube->b0] -m2[cube->r1][cube->g0][cube->b1] +m2[cube->r1][cube->g0][cube->b0] -m2[cube->r0][cube->g1][cube->b1] +m2[cube->r0][cube->g1][cube->b0] +m2[cube->r0][cube->g0][cube->b1] -m2[cube->r0][cube->g0][cube->b0]; return( xx - (dr*dr+dg*dg+db*db) / (float)Vol(cube,wt) ); } /* We want to minimize the sum of the variances of two subboxes. * The sum(c^2) terms can be ignored since their sum over both subboxes * is the same (the sum for the whole box) no matter where we split. * The remaining terms have a minus sign in the variance formula, * so we drop the minus sign and MAXIMIZE the sum of the two terms. */ float Maximize(cube, dir, first, last, cut, whole_r, whole_g, whole_b, whole_w) struct box *cube; byte dir; int first, last, *cut; long int whole_r, whole_g, whole_b, whole_w; { register long int half_r, half_g, half_b, half_w; long int base_r, base_g, base_b, base_w; register int i; register float temp, max; base_r = Bottom(cube, dir, mr); base_g = Bottom(cube, dir, mg); base_b = Bottom(cube, dir, mb); base_w = Bottom(cube, dir, wt); max = 0.0; *cut = -1; for( i = first; i < last; ++i ) { half_r = base_r + Top(cube, dir, i, mr); half_g = base_g + Top(cube, dir, i, mg); half_b = base_b + Top(cube, dir, i, mb); half_w = base_w + Top(cube, dir, i, wt); /* now half_x is sum over lower half of box, if split at i */ if ( half_w == 0 ) { /* subbox could be empty of pixels! */ continue; /* never split into an empty box */ } else temp = ((float)half_r*half_r + (float)half_g*half_g + (float)half_b*half_b)/half_w; half_r = whole_r - half_r; half_g = whole_g - half_g; half_b = whole_b - half_b; half_w = whole_w - half_w; if ( half_w == 0 ) { /* subbox could be empty of pixels! */ continue; /* never split into an empty box */ } else temp += ((float)half_r*half_r + (float)half_g*half_g + (float)half_b*half_b)/half_w; if ( temp > max ) { max = temp; *cut=i; } } return(max); } int Cut(set1, set2) struct box *set1, *set2; { byte dir; int cutr, cutg, cutb; float maxr, maxg, maxb; long int whole_r, whole_g, whole_b, whole_w; whole_r = Vol(set1, mr); whole_g = Vol(set1, mg); whole_b = Vol(set1, mb); whole_w = Vol(set1, wt); maxr = Maximize( set1, RED, set1->r0+1, set1->r1, &cutr, whole_r, whole_g, whole_b, whole_w ); maxg = Maximize( set1, GREEN, set1->g0+1, set1->g1, &cutg, whole_r, whole_g, whole_b, whole_w ); maxb = Maximize( set1, BLUE, set1->b0+1, set1->b1, &cutb, whole_r, whole_g, whole_b, whole_w ); if( (maxr >= maxg) && (maxr >= maxb) ) { dir = RED; if ( cutr < 0 ) return 0; /* can't split the box */ } else if( (maxg >= maxr) && (maxg >= maxb) ) dir = GREEN; else dir = BLUE; set2->r1 = set1->r1; set2->g1 = set1->g1; set2->b1 = set1->b1; switch (dir){ case RED: set2->r0 = set1->r1 = cutr; set2->g0 = set1->g0; set2->b0 = set1->b0; break; case GREEN: set2->g0 = set1->g1 = cutg; set2->r0 = set1->r0; set2->b0 = set1->b0; break; case BLUE: set2->b0 = set1->b1 = cutb; set2->r0 = set1->r0; set2->g0 = set1->g0; break; } set1->vol = (set1->r1-set1->r0) * (set1->g1-set1->g0) * (set1->b1-set1->b0); set2->vol = (set2->r1-set2->r0) * (set2->g1-set2->g0) * (set2->b1-set2->b0); return 1; } Mark(cube, label, tag) struct box *cube; int label; byte *tag; { register int r, g, b; for ( r = cube->r0+1; r <= cube->r1; ++r ) for( g = cube->g0+1; g <= cube->g1; ++g ) for( b = cube->b0+1; b <= cube->b1; ++b ) tag[ (r<<10) + (r<<6) + r + (g<<5) + g + b ] = label; }