This is colorquant.c in view mode; [Download] [Up]
/* * 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. */ /* * colorquant.c * * Perform variance-based color quantization on a "full color" image. * Author: Craig Kolb * Department of Mathematics * Yale University * kolb@yale.edu * Date: Tue Aug 22 1989 * Copyright (C) 1989 Craig E. Kolb * $Id: colorquant.c,v 3.0.1.2 90/11/29 15:18:04 spencer Exp $ * * $Log: colorquant.c,v $ * Revision 3.0.1.2 90/11/29 15:18:04 spencer * Remove a typo. * * * Revision 3.0.1.1 90/11/19 16:59:48 spencer * Use inv_cmap instead of find_colors. * Changes to process multiple files into one colormap (accum_hist arg). * Delete 'otherimages' argument -- unnecessary with faster inv_cmap code. * * * Revision 3.0 90/08/03 15:20:11 spencer * Establish version 3.0 base. * * Revision 1.6 90/07/29 08:06:06 spencer * If HUGE isn't defined, make it HUGE_VAL (for ansi). * * Revision 1.5 90/07/26 17:25:48 rgb * Added a parameter to colorquant for rgbmap construction. * * Revision 1.4 90/07/13 14:53:31 spencer * Get rid of ARB_ARG sh*t. * Change a couple of vars to double so that HUGE won't cause problems. * * Revision 1.3 90/06/28 21:42:56 spencer * Declare internal functions properly. * Delete unused global variable. * * Revision 1.2 90/06/28 13:18:56 spencer * Make internal functions static. * Build entire RGB cube, not just those colors used by this image. This * is so dithering will work. * * Revision 1.1 90/06/18 20:45:17 spencer * Initial revision * * Revision 1.3 89/12/03 18:27:16 craig * Removed bogus integer casts in distance calculation in makenearest(). * * Revision 1.2 89/12/03 18:13:12 craig * FindCutpoint now returns FALSE if the given box cannot be cut. This * to avoid overflow problems in CutBox. * "whichbox" in GreatestVariance() is now initialized to 0. * */ #include <math.h> #include <stdio.h> /* Ansi uses HUGE_VAL. */ #ifndef HUGE #define HUGE HUGE_VAL #endif static void QuantHistogram(); static void BoxStats(); static void UpdateFrequencies(); static void ComputeRGBMap(); static void SetRGBmap(); #ifdef slow_color_map static void find_colors(); static int getneighbors(); static int makenearest(); #else extern void inv_cmap(); #endif static int CutBoxes(); static int CutBox(); static int GreatestVariance(); static int FindCutpoint(); /* * The colorquant() routine has been tested on an Iris 4D70 workstation, * a Sun 3/60 workstation, and (to some extent), a Macintosh. * * Calls to bzero() may have to be replaced with the appropriate thing on * your system. (bzero(ptr, len) writes 'len' 0-bytes starting at the location * pointed to by ptr.) * * Although I've tried to avoid integer overflow problems where ever possible, * it's likely I've missed a spot where an 'int' should really be a 'long'. * (On the machine this was developed on, an int == long == 32 bits.) * * Note that it's quite easy to optimize this code for a given value for * 'bits'. In addition, if you assume bits is small and * that the total number of pixels is relatively small, there are several * places that integer arithmetic may be substituted for floating-point. * One such place is the loop in BoxStats -- mean and var need not necessary * be floats. * * As things stand, the maximum number of colors to which an image may * be quantized is 256. This limit may be overcome by changing rgbmap and * colormap from pointers to characters to pointers to something larger. */ /* * Maximum number of colormap entries. To make larger than 2^8, the rgbmap * type will have to be changed from unsigned chars to something larger. */ #define MAXCOLORS 256 /* * Value corrersponding to full intensity in colormap. The values placed * in the colormap are scaled to be between zero and this number. Note * that anything larger than 255 is going to lead to problems, as the * colormap is declared as an unsigned char. */ #define FULLINTENSITY 255 #define MAX(x,y) ((x) > (y) ? (x) : (y)) /* * Readability constants. */ #define REDI 0 #define GREENI 1 #define BLUEI 2 #define TRUE 1 #define FALSE 0 typedef struct { float weightedvar, /* weighted variance */ mean[3]; /* centroid */ unsigned long weight, /* # of pixels in box */ freq[3][MAXCOLORS]; /* Projected frequencies */ int low[3], high[3]; /* Box extent */ } Box; static unsigned long *Histogram, /* image histogram */ NPixels, /* # of pixels in an image*/ SumPixels; /* total # of pixels */ static unsigned int Bits, /* # significant input bits */ ColormaxI; /* # of colors, 2^Bits */ static Box *Boxes; /* Array of color boxes. */ /* * Perform variance-based color quantization on a 24-bit image. * * Input consists of: * red, green, blue Arrays of red, green and blue pixel * intensities stored as unsigned characters. * The color of the ith pixel is given * by red[i] green[i] and blue[i]. 0 indicates * zero intensity, 255 full intensity. * pixels The length of the red, green and blue arrays * in bytes, stored as an unsigned long int. * colormap Points to the colormap. The colormap * consists of red, green and blue arrays. * The red/green/blue values of the ith * colormap entry are given respectively by * colormap[0][i], colormap[1][i] and * colormap[2][i]. Each entry is an unsigned char. * colors The number of colormap entries, stored * as an integer. * bits The number of significant bits in each entry * of the red, greena and blue arrays. An integer. * rgbmap An array of unsigned chars of size (2^bits)^3. * This array is used to map from pixels to * colormap entries. The 'prequantized' red, * green and blue components of a pixel * are used as an index into rgbmap to retrieve * the index which should be used into the colormap * to represent the pixel. In short: * index = rgbmap[(((r << bits) | g) << bits) | b]; * fast If non-zero, the rgbmap will be constructed * quickly. If zero, the rgbmap will be built * much slower, but more accurately. In most * cases, fast should be non-zero, as the error * introduced by the approximation is usually * small. 'Fast' is stored as an integer. * accum_hist If non-zero the histogram will accumulate and * reflect pixels from multiple images. * when 1, the histogram will be initialized and * summed, but not thrown away OR processed. when * 2 the image RGB will be added to it. When 3 * Boxes are cut and a colormap and rgbmap * are be returned, Histogram is freed too. * When zero, all code is executed as per normal. * * colorquant returns the number of colors to which the image was * quantized. */ #define INIT_HIST 1 #define USE_HIST 2 #define PROCESS_HIST 3 int colorquant(red,green,blue,pixels,colormap,colors,bits,rgbmap,fast,accum_hist) unsigned char *red, *green, *blue; unsigned long pixels; unsigned char *colormap[3]; int colors, bits; unsigned char *rgbmap; int fast, accum_hist; { int i, /* Counter */ OutColors, /* # of entries computed */ Colormax; /* quantized full-intensity */ float Cfactor; /* Conversion factor */ if (accum_hist < 0 || accum_hist > PROCESS_HIST) fprintf(stderr, "colorquant: bad value for accum_hist\n"); ColormaxI = 1 << bits; /* 2 ^ Bits */ Colormax = ColormaxI - 1; Bits = bits; NPixels = pixels; Cfactor = (float)FULLINTENSITY / Colormax; if (! accum_hist || accum_hist == INIT_HIST) { Histogram = (unsigned long *)calloc(ColormaxI*ColormaxI*ColormaxI, sizeof(long)); Boxes = (Box *)malloc(colors * sizeof(Box)); /* * Zero-out the projected frequency arrays of the largest box. */ bzero(Boxes->freq[0], ColormaxI * sizeof(unsigned long)); bzero(Boxes->freq[1], ColormaxI * sizeof(unsigned long)); bzero(Boxes->freq[2], ColormaxI * sizeof(unsigned long)); SumPixels = 0; } SumPixels += NPixels; if ( accum_hist != PROCESS_HIST ) QuantHistogram(red, green, blue, &Boxes[0]); if ( !accum_hist || accum_hist == PROCESS_HIST) { OutColors = CutBoxes(Boxes, colors); /* * We now know the set of representative colors. We now * must fill in the colormap and convert the representatives * from their 'prequantized' range to 0-FULLINTENSITY. */ for (i = 0; i < OutColors; i++) { colormap[0][i] = (unsigned char)(Boxes[i].mean[REDI] * Cfactor + 0.5); colormap[1][i] = (unsigned char)(Boxes[i].mean[GREENI] * Cfactor + 0.5); colormap[2][i] = (unsigned char)(Boxes[i].mean[BLUEI] * Cfactor + 0.5); } ComputeRGBMap(Boxes, OutColors, rgbmap, bits, colormap, fast); free((char *)Histogram); free((char *)Boxes); return OutColors; /* Return # of colormap entries */ } return 0; } /* * Compute the histogram of the image as well as the projected frequency * arrays for the first world-encompassing box. */ static void QuantHistogram(r, g, b, box) register unsigned char *r, *g, *b; Box *box; { unsigned long *rf, *gf, *bf, i; rf = box->freq[0]; gf = box->freq[1]; bf = box->freq[2]; /* * We compute both the histogram and the proj. frequencies of * the first box at the same time to save a pass through the * entire image. */ for (i = 0; i < NPixels; i++) { rf[*r]++; gf[*g]++; bf[*b]++; Histogram[((((*r++)<<Bits)|(*g++))<<Bits)|(*b++)]++; } } /* * Interatively cut the boxes. */ static CutBoxes(boxes, colors) Box *boxes; int colors; { int curbox; boxes[0].low[REDI] = boxes[0].low[GREENI] = boxes[0].low[BLUEI] = 0; boxes[0].high[REDI] = boxes[0].high[GREENI] = boxes[0].high[BLUEI] = ColormaxI; boxes[0].weight = SumPixels; BoxStats(&boxes[0]); for (curbox = 1; curbox < colors; curbox++) { if (CutBox(&boxes[GreatestVariance(boxes, curbox)], &boxes[curbox]) == FALSE) break; } return curbox; } /* * Return the number of the box in 'boxes' with the greatest variance. * Restrict the search to those boxes with indices between 0 and n-1. */ static GreatestVariance(boxes, n) Box *boxes; int n; { register int i, whichbox = 0; float max; max = -1; for (i = 0; i < n; i++) { if (boxes[i].weightedvar > max) { max = boxes[i].weightedvar; whichbox = i; } } return whichbox; } /* * Compute mean and weighted variance of the given box. */ static void BoxStats(box) register Box *box; { register int i, color; unsigned long *freq; float mean, var; if(box->weight == 0) { box->weightedvar = 0; return; } box->weightedvar = 0.; for (color = 0; color < 3; color++) { var = mean = 0; i = box->low[color]; freq = &box->freq[color][i]; for (; i < box->high[color]; i++, freq++) { mean += i * *freq; var += i*i* *freq; } box->mean[color] = mean / (float)box->weight; box->weightedvar += var - box->mean[color]*box->mean[color]* (float)box->weight; } box->weightedvar /= SumPixels; } /* * Cut the given box. Returns TRUE if the box could be cut, FALSE otherwise. */ static CutBox(box, newbox) Box *box, *newbox; { int i; double totalvar[3]; Box newboxes[3][2]; if (box->weightedvar == 0. || box->weight == 0) /* * Can't cut this box. */ return FALSE; /* * Find 'optimal' cutpoint along each of the red, green and blue * axes. Sum the variances of the two boxes which would result * by making each cut and store the resultant boxes for * (possible) later use. */ for (i = 0; i < 3; i++) { if (FindCutpoint(box, i, &newboxes[i][0], &newboxes[i][1])) totalvar[i] = newboxes[i][0].weightedvar + newboxes[i][1].weightedvar; else totalvar[i] = HUGE; } /* * Find which of the three cuts minimized the total variance * and make that the 'real' cut. */ if (totalvar[REDI] <= totalvar[GREENI] && totalvar[REDI] <= totalvar[BLUEI]) { *box = newboxes[REDI][0]; *newbox = newboxes[REDI][1]; } else if (totalvar[GREENI] <= totalvar[REDI] && totalvar[GREENI] <= totalvar[BLUEI]) { *box = newboxes[GREENI][0]; *newbox = newboxes[GREENI][1]; } else { *box = newboxes[BLUEI][0]; *newbox = newboxes[BLUEI][1]; } return TRUE; } /* * Compute the 'optimal' cutpoint for the given box along the axis * indcated by 'color'. Store the boxes which result from the cut * in newbox1 and newbox2. */ static FindCutpoint(box, color, newbox1, newbox2) Box *box, *newbox1, *newbox2; int color; { float u, v, max; int i, maxindex, minindex, cutpoint; unsigned long optweight, curweight; if (box->low[color] + 1 == box->high[color]) return FALSE; /* Cannot be cut. */ minindex = (int)((box->low[color] + box->mean[color]) * 0.5); maxindex = (int)((box->mean[color] + box->high[color]) * 0.5); cutpoint = minindex; optweight = box->weight; curweight = 0; for (i = box->low[color] ; i < minindex ; i++) curweight += box->freq[color][i]; u = 0.; max = -1; for (i = minindex; i <= maxindex ; i++) { curweight += box->freq[color][i]; if (curweight == box->weight) break; u += (float)(i * box->freq[color][i]) / (float)box->weight; v = ((float)curweight / (float)(box->weight-curweight)) * (box->mean[color]-u)*(box->mean[color]-u); if (v > max) { max = v; cutpoint = i; optweight = curweight; } } cutpoint++; *newbox1 = *newbox2 = *box; newbox1->weight = optweight; newbox2->weight -= optweight; newbox1->high[color] = cutpoint; newbox2->low[color] = cutpoint; UpdateFrequencies(newbox1, newbox2); BoxStats(newbox1); BoxStats(newbox2); return TRUE; /* Found cutpoint. */ } /* * Update projected frequency arrays for two boxes which used to be * a single box. */ static void UpdateFrequencies(box1, box2) register Box *box1, *box2; { register unsigned long myfreq, *h; register int b, g, r; int roff; bzero(box1->freq[0], ColormaxI * sizeof(unsigned long)); bzero(box1->freq[1], ColormaxI * sizeof(unsigned long)); bzero(box1->freq[2], ColormaxI * sizeof(unsigned long)); for (r = box1->low[0]; r < box1->high[0]; r++) { roff = r << Bits; for (g = box1->low[1];g < box1->high[1]; g++) { b = box1->low[2]; h = Histogram + (((roff | g) << Bits) | b); for (; b < box1->high[2]; b++) { if ((myfreq = *h++) == 0) continue; box1->freq[0][r] += myfreq; box1->freq[1][g] += myfreq; box1->freq[2][b] += myfreq; box2->freq[0][r] -= myfreq; box2->freq[1][g] -= myfreq; box2->freq[2][b] -= myfreq; } } } } /* * Compute RGB to colormap index map. */ static void ComputeRGBMap(boxes, colors, rgbmap, bits, colormap, fast) Box *boxes; int colors; unsigned char *rgbmap, *colormap[3]; int bits, fast; { register int i; if (fast) { /* * The centroid of each box serves as the representative * for each color in the box. */ for (i = 0; i < colors; i++) SetRGBmap(i, &boxes[i], rgbmap, bits); } else /* * Find the 'nearest' representative for each * pixel. */ #ifdef slow_color_map find_colors(boxes, colors, rgbmap, bits, colormap, 0); #else inv_cmap(colors, colormap, bits, Histogram, rgbmap); #endif } /* * Make the centroid of "boxnum" serve as the representative for * each color in the box. */ static void SetRGBmap(boxnum, box, rgbmap, bits) int boxnum; Box *box; unsigned char *rgbmap; int bits; { register int r, g, b; for (r = box->low[REDI]; r < box->high[REDI]; r++) { for (g = box->low[GREENI]; g < box->high[GREENI]; g++) { for (b = box->low[BLUEI]; b < box->high[BLUEI]; b++) { rgbmap[(((r<<bits)|g)<<bits)|b]=(char)boxnum; } } } } #ifdef slow_color_map /* * Form colormap and NearestColor arrays. */ static void find_colors(boxes, colors, rgbmap, bits, colormap, otherimages) int colors; Box *boxes; unsigned char *rgbmap; int bits; unsigned char *colormap[3]; int otherimages; { register int i; int num, *neighbors; neighbors = (int *)malloc(colors * sizeof(int)); /* * Form map of representative (nearest) colors. */ for (i = 0; i < colors; i++) { /* * Create list of candidate neighbors and * find closest representative for each * color in the box. */ num = getneighbors(boxes, i, neighbors, colors, colormap); makenearest(boxes, i, num, neighbors, rgbmap, bits, colormap, otherimages); } free((char *)neighbors); } /* * In order to minimize our search for 'best representative', we form the * 'neighbors' array. This array contains the number of the boxes whose * centroids *might* be used as a representative for some color in the * current box. We need only consider those boxes whose centroids are closer * to one or more of the current box's corners than is the centroid of the * current box. 'Closeness' is measured by Euclidean distance. */ static getneighbors(boxes, num, neighbors, colors, colormap) Box *boxes; int num, colors, *neighbors; unsigned char *colormap[3]; { register int i, j; Box *bp; float dist, LowR, LowG, LowB, HighR, HighG, HighB, ldiff, hdiff; bp = &boxes[num]; ldiff = bp->low[REDI] - bp->mean[REDI]; ldiff *= ldiff; hdiff = bp->high[REDI] - bp->mean[REDI]; hdiff *= hdiff; dist = MAX(ldiff, hdiff); ldiff = bp->low[GREENI] - bp->mean[GREENI]; ldiff *= ldiff; hdiff = bp->high[GREENI] - bp->mean[GREENI]; hdiff *= hdiff; dist += MAX(ldiff, hdiff); ldiff = bp->low[BLUEI] - bp->mean[BLUEI]; ldiff *= ldiff; hdiff = bp->high[BLUEI] - bp->mean[BLUEI]; hdiff *= hdiff; dist += MAX(ldiff, hdiff); #ifdef IRIS dist = fsqrt(dist); #else dist = (float)sqrt((double)dist); #endif /* * Loop over all colors in the colormap, the ith entry of which * corresponds to the ith box. * * If the centroid of a box is as close to any corner of the * current box as is the centroid of the current box, add that * box to the list of "neighbors" of the current box. */ HighR = (float)bp->high[REDI] + dist; HighG = (float)bp->high[GREENI] + dist; HighB = (float)bp->high[BLUEI] + dist; LowR = (float)bp->low[REDI] - dist; LowG = (float)bp->low[GREENI] - dist; LowB = (float)bp->low[BLUEI] - dist; for (i = j = 0, bp = boxes; i < colors; i++, bp++) { if (LowR <= bp->mean[REDI] && HighR >= bp->mean[REDI] && LowG <= bp->mean[GREENI] && HighG >= bp->mean[GREENI] && LowB <= bp->mean[BLUEI] && HighB >= bp->mean[BLUEI]) neighbors[j++] = i; } return j; /* Return the number of neighbors found. */ } /* * Assign representative colors to every pixel in a given box through * the construction of the NearestColor array. For each color in the * given box, we look at the list of neighbors passed to find the * one whose centroid is closest to the current color. */ static makenearest(boxes, boxnum, nneighbors, neighbors, rgbmap, bits, colormap, otherimages) Box *boxes; int boxnum; int nneighbors, *neighbors, bits; unsigned char *rgbmap, *colormap[3]; int otherimages; { register int n, b, g, r; double rdist, gdist, bdist, dist, mindist; int which, *np; Box *box; box = &boxes[boxnum]; for (r = box->low[REDI]; r < box->high[REDI]; r++) { for (g = box->low[GREENI]; g < box->high[GREENI]; g++) { for (b = box->low[BLUEI]; b < box->high[BLUEI]; b++) { /* * The following "if" is to avoid doing extra work when the RGBmap is * not going to be used for other images. */ if ((!otherimages) && (Histogram[(((r<<bits)|g)<<bits)|b] == 0)) continue; mindist = HUGE; /* * Find the colormap entry which is * closest to the current color. */ np = neighbors; for (n = 0; n < nneighbors; n++, np++) { rdist = r-boxes[*np].mean[REDI]; gdist = g-boxes[*np].mean[GREENI]; bdist = b-boxes[*np].mean[BLUEI]; dist = rdist*rdist + gdist*gdist + bdist*bdist; if (dist < mindist) { mindist = dist; which = *np; } } /* * The colormap entry closest to this * color is used as a representative. */ rgbmap[(((r<<bits)|g)<<bits)|b] = which; } } } } #endif /* slow_color_map */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.