home *** CD-ROM | disk | FTP | other *** search
- /************************************************************************
- * *
- * Copyright (c) 1991, Frank van der Hulst *
- * All Rights Reserved *
- * *
- * Authors: *
- * FvdH - Frank van der Hulst (Wellington, NZ) *
- * *
- * Versions: *
- * V1.1 910626 FvdH - QUANT released for DBW_RENDER *
- * V1.2 911021 FvdH - QUANT released for PoV Ray *
- * V1.4 920303 FvdH - Ported to GNU *
- * *
- ************************************************************************/
- /*
- * 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 1.3 89/12/03 18:27:16 craig Exp $
- *
- * $Log: colorquant.c,v $
- *
- * Revision 1.4 91/06/26 16:00:00 Frank van der Hulst
- * Ported to Turbo C;
- * Call farmalloc rather than malloc
- * Virtual memory added to swap box data to/from disk
- * Rewritten in ANSI C
- * Removed call to QuantHistogram() from colorquant, to allow two
- * image files to create one palette
- * Changed QuantHistogram() to read from file, rather than from an
- * array
- * Changed format of palette to conform with the VGA palette
- *
- * 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.
- *
- */
-
- #ifdef __TURBOC__
- #include <mem.h>
- #define HUGE 1.79e308
- #endif
- #include <math.h>
-
- #include "quant.h"
- #include "heckbert.h"
-
- #define MAX(x,y) ((x) > (y) ? (x) : (y))
-
- static unsigned long NPixels = 0L; /* total # of pixels */
-
- static int neighbours[MAXCOLORS];
-
- /*
- * Compute the histogram of the image as well as the projected frequency
- * arrays for the first world-encompassing box.
- * 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.
- * The projected frequency arrays of the largest box are zeroed out as
- * as part of open_box_file(), called from main().
- */
-
- void QuantHistogram(Box *box)
- {
- unsigned long *rf, *gf, *bf;
- UCHAR pixel[3];
-
- rf = box->freq[RED];
- gf = box->freq[GREEN];
- bf = box->freq[BLUE];
-
- while (get_pixel(pixel)) {
- rf[pixel[RED]]++;
- gf[pixel[GREEN]]++;
- bf[pixel[BLUE]]++;
- Histogram[(((pixel[RED]<<INPUT_BITS)|pixel[GREEN])<<INPUT_BITS)|pixel[BLUE]]++;
- NPixels++;
- }
- }
-
- /*
- * Compute mean and weighted variance of the given box.
- */
- void BoxStats(Box HUGE_PTR box)
- {
- int i, color;
- unsigned long *freq;
- double mean, var;
-
- if(box->weight == 0) {
- box->weightedvar = 0;
- return;
- }
-
- box->weightedvar = 0.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 / box->weight;
- box->weightedvar += var - box->mean[color]*box->mean[color]*box->weight;
- }
- box->weightedvar /= NPixels;
- }
-
- /*
- * 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.
- */
- int GreatestVariance(int n)
- {
- int i, whichbox = 0;
- double max;
- Box *box;
-
- max = -1;
- for (i = 0; i < n; i++) {
- box = get_box_tmp(i);
- if (box->weightedvar > max) {
- max = box->weightedvar;
- whichbox = i;
- }
- }
- return whichbox;
- }
-
- /*
- * Update projected frequency arrays for two boxes which used to be
- * a single box.
- */
- void UpdateFrequencies(Box HUGE_PTR box1, Box HUGE_PTR box2)
- {
- unsigned long myfreq, HUGE_PTR h;
- int b, g, r;
- int roff;
-
- memset(box1->freq[0], 0, IN_COLOURS * sizeof(unsigned long));
- memset(box1->freq[1], 0, IN_COLOURS * sizeof(unsigned long));
- memset(box1->freq[2], 0, IN_COLOURS * sizeof(unsigned long));
-
- for (r = box1->low[0]; r < box1->high[0]; r++) {
- roff = r << INPUT_BITS;
- for (g = box1->low[1];g < box1->high[1]; g++) {
- b = box1->low[2];
- h = Histogram + (((roff | g) << INPUT_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 the 'optimal' cutpoint for the given box along the axis
- * indicated by 'color'. Store the boxes which result from the cut
- * in newbox1 and newbox2.
- */
- int FindCutpoint(Box HUGE_PTR box, int color, Box HUGE_PTR newbox1, Box HUGE_PTR newbox2)
- {
- double 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 += (double)(i * box->freq[color][i]) / box->weight;
- v = ((double)curweight / (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. */
- }
-
- /*
- * Cut the given box. Returns TRUE if the box could be cut, FALSE otherwise.
- */
- int CutBox(Box HUGE_PTR box, Box HUGE_PTR newbox)
- {
- int i;
- double totalvar[3];
- static Box newboxes[3][2]; /* Only used by CutBox, but don't want it on stack */
-
- 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[RED] <= totalvar[GREEN] &&
- totalvar[RED] <= totalvar[BLUE]) {
- *box = newboxes[RED][0];
- *newbox = newboxes[RED][1];
- } else if (totalvar[GREEN] <= totalvar[RED] &&
- totalvar[GREEN] <= totalvar[BLUE]) {
- *box = newboxes[GREEN][0];
- *newbox = newboxes[GREEN][1];
- } else {
- *box = newboxes[BLUE][0];
- *newbox = newboxes[BLUE][1];
- }
-
- return TRUE;
- }
-
- /*
- * Iteratively cut the boxes.
- */
- CutBoxes(int colors)
- {
- int curbox, varbox;
- Box *box = get_box(0);
-
- box->low[RED] = box->low[GREEN] = box->low[BLUE] = 0;
- box->high[RED] = box->high[GREEN] = box->high[BLUE] = IN_COLOURS;
- box->weight = NPixels;
-
- BoxStats(box);
- free_box(0);
-
- printf("%d Boxes: cutting box: ", colors);
- for (curbox = 1; curbox < colors; curbox++) {
- printf("%3d\b\b\b", curbox);
- varbox = GreatestVariance(curbox);
- if (CutBox(get_box(varbox), get_box(curbox)) == FALSE)
- break;
- free_box(curbox);
- free_box(varbox);
- }
- printf("Done\n");
-
- return curbox;
- }
-
- /*
- * Make the centroid of "boxnum" serve as the representative for
- * each color in the box.
- */
- void SetRGBmap(int boxnum, Box *box, int bits)
- {
- int r, g, b;
-
- for (r = box->low[RED]; r < box->high[RED]; r++) {
- for (g = box->low[GREEN]; g < box->high[GREEN]; g++) {
- for (b = box->low[BLUE]; b < box->high[BLUE]; b++) {
- RGBmap[(((r<<bits)|g)<<bits)|b]=(char)boxnum;
- }
- }
- }
- }
-
- /*
- * 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.
- */
- int getneighbors(int num, int colors)
- {
- int i, j;
- Box *bp;
- double dist, LowR, LowG, LowB, HighR, HighG, HighB, ldiff, hdiff;
-
- bp = get_box_tmp(num);
-
- ldiff = bp->low[RED] - bp->mean[RED];
- ldiff *= ldiff;
- hdiff = bp->high[RED] - bp->mean[RED];
- hdiff *= hdiff;
- dist = MAX(ldiff, hdiff);
-
- ldiff = bp->low[GREEN] - bp->mean[GREEN];
- ldiff *= ldiff;
- hdiff = bp->high[GREEN] - bp->mean[GREEN];
- hdiff *= hdiff;
- dist += MAX(ldiff, hdiff);
-
- ldiff = bp->low[BLUE] - bp->mean[BLUE];
- ldiff *= ldiff;
- hdiff = bp->high[BLUE] - bp->mean[BLUE];
- hdiff *= hdiff;
- dist += MAX(ldiff, hdiff);
-
- dist = sqrt(dist);
-
- /*
- * 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 = (double)bp->high[RED] + dist;
- HighG = (double)bp->high[GREEN] + dist;
- HighB = (double)bp->high[BLUE] + dist;
- LowR = (double)bp->low[RED] - dist;
- LowG = (double)bp->low[GREEN] - dist;
- LowB = (double)bp->low[BLUE] - dist;
- for (i = j = 0; i < colors; i++) {
- bp = get_box_tmp(i);
- if (LowR <= bp->mean[RED] && HighR >= bp->mean[RED] &&
- LowG <= bp->mean[GREEN] && HighG >= bp->mean[GREEN] &&
- LowB <= bp->mean[BLUE] && HighB >= bp->mean[BLUE])
- neighbours[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.
- */
- void makenearest(int boxnum, int nneighbors, int bits)
- {
- int n, b, g, r;
- double rdist, gdist, bdist, dist, mindist;
- int which, *np;
- Box *box, *bp;
-
- box = get_box(boxnum);
-
- for (r = box->low[RED]; r < box->high[RED]; r++) {
- for (g = box->low[GREEN]; g < box->high[GREEN]; g++) {
- for (b = box->low[BLUE]; b < box->high[BLUE]; b++) {
- /*
- * The following two lines should be commented out if the RGBmap is going
- * to be used for images other than the one given.
- */
- if (Histogram[(((r<<bits)|g)<<bits)|b] == 0)
- continue;
- mindist = HUGE;
- /*
- * Find the colormap entry which is
- * closest to the current color.
- */
- np = neighbours;
- for (n = 0; n < nneighbors; n++, np++) {
- bp = get_box_tmp(*np);
- rdist = r - bp->mean[RED];
- gdist = g - bp->mean[GREEN];
- bdist = b - bp->mean[BLUE];
- 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;
- }
- }
- }
- free_box(boxnum);
- }
- /*
- * Form colormap and NearestColor arrays.
- */
- void find_colors(int colors, int bits)
- {
- int i;
- int num;
-
- /*
- * Form map of representative (nearest) colors.
- */
- printf("Mapping colours for box: ");
- for (i = 0; i < colors; i++) {
- printf("%3d\b\b\b", i);
- /*
- * Create list of candidate neighbors and
- * find closest representative for each
- * color in the box.
- */
- num = getneighbors(i, colors);
- makenearest(i, num, bits);
- }
- printf("Done\n");
- }
-
- /*
- * Compute RGB to colormap index map.
- */
- void ComputeRGBMap(int colors, int bits, int fast)
- {
- 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, get_box_tmp(i), bits);
- } else
- /*
- * Find the 'nearest' representative for each pixel.
- */
- find_colors(colors, bits);
- }
-
- /*
- * Perform variance-based color quantization on a 24-bit image.
- *
- * Input consists of:
- * in_file Pointer to file containing of red, green and blue pixel
- * intensities stored as unsigned characters.
- * The color of the ith pixel is given consecutive bytes, in the
- * order red, then green, then blue. Only the LS 4 bits are used.
- * 0 indicates zero intensity, 15 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, green 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.
- * Cfactor Conversion factor.
- *
- * colorquant returns the number of colors to which the image was
- * quantized.
- */
-
- int colorquant(int colors, int bits, int fast, double Cfactor)
- {
- int i, /* Counter */
- OutColors; /* # of entries computed */
- Box *box;
-
- OutColors = CutBoxes(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++) {
- box = get_box_tmp(i);
- palette[i][RED] = (char)(box->mean[RED] * Cfactor + 0.5);
- palette[i][GREEN] = (char)(box->mean[GREEN] * Cfactor + 0.5);
- palette[i][BLUE] = (char)(box->mean[BLUE] * Cfactor + 0.5);
- }
-
- ComputeRGBMap(OutColors, bits, fast);
-
- return OutColors; /* Return # of colormap entries */
- }
-
- int pal_index(UCHAR *pixel)
- {
- return RGBmap[(((pixel[RED]<<INPUT_BITS)|pixel[GREEN])<<INPUT_BITS)|pixel[BLUE]];
- }