/**************************************************************** * fbquant.c: FBM Release 1.2 07-Apr-93 Michael Mauldin * * Copyright (C) 1989-1993 by Michael Mauldin. Permission is granted * to use this file in whole or in part for any purpose, educational, * recreational or commercial, provided that this copyright notice * is retained unchanged. This software is available to all free of * charge by anonymous FTP and in the UUNET archives. * * fbquant: Convert an RGB color image to mapped color format (color * quantization step). Floyd-Steinberg dithering is used * to reduce color banding. The quantization used is a * modification of Heckbert's median cut. * * USAGE * % fbquant [ -c ] [ -r ] [ -m ] * [ -n ] [ - ] * [ -i ] * [ -s ] * [ -C ] * < rgb > mapped" * * EDITLOG * LastEditDate = Thu Oct 11 22:48:22 1990 - Michael Mauldin * LastFileName = /usr2/mlm/src/misc/fbm/fbquant.c * * HISTORY * 07-Apr-93 Michael Mauldin (mlm) at Carnegie-Mellon University * Added -J switch * * 25-Jun-90 Michael Mauldin (mlm@cs.cmu.edu) Carnegie Mellon * Package for Release 1.0 * * 04-Sep-89 Michael Mauldin (mlm) at Carnegie Mellon University * Add option for low-res colors, default assumes each RGB value * can vary from 0 to 255. The default is 8 bits for more than 32 * color images, and 4 bits for images with 32 or fewer colors. * For example, the Amiga has 4bit DACs and can only display * 4^3 == 4096 colors. * * 03-May-89 Michael Mauldin (mlm) at Carnegie Mellon University * Clear FBM pointers before allocate * * 07-Apr-89 Michael Mauldin (mlm) at Carnegie Mellon University * Add -m flag for using colormap from another image. * * 07-Mar-89 Michael Mauldin (mlm) at Carnegie Mellon University * Beta release (version 0.93) mlm@cs.cmu.edu * * 26-Feb-89 Michael Mauldin (mlm) at Carnegie Mellon University * Changes for small color maps. Fixed bug with unsigned * arithmetic that ruined dithering for images with small * colormaps. Added error limiting in the Floyd-Steinberg * code to prevent color "shadowing" that occurs with small * numbers of colors. Also change to use colors 0..n-1 instead * of reserving colors 0 and n-1 for Sun foreground/background * colors, unless output type is SUN. * * 11-Nov-88 Michael Mauldin (mlm) at Carnegie Mellon University * Created. * * References: Uses a variant of Heckbert's adaptive partitioning * algorithm. See Computer Graphics, v16n3 July 1982 * ****************************************************************/ # include # include # include # include "fbm.h" int cmp_red(), cmp_grn(), cmp_blu(), cmp_cmap(), cmp_int(), cmp_hue(); # define RD 0 # define GR 1 # define BL 2 # define REDMASK 0076000 # define REDSHFT 10 # define GRNMASK 0001740 # define GRNSHFT 5 # define BLUMASK 0000037 # define BLUSHFT 0 # define CUBITS 5 # define CUBIGN (8-CUBITS) # define CUBSID 32 # define CUBSIZ 32768 # define MAXSHRT 32767 # define MAXERR 32 # define GETR(X) (((X) & REDMASK) >> REDSHFT) # define GETG(X) (((X) & GRNMASK) >> GRNSHFT) # define GETB(X) ((X) & BLUMASK) # define CLRINDEX(R,G,B) \ (((R) << REDSHFT) & REDMASK | \ ((G) << GRNSHFT) & GRNMASK | \ ((B) & BLUMASK)) # define CLRINDEX8(R,G,B) \ (((R) << (REDSHFT-CUBIGN)) & REDMASK | \ ((G) << (GRNSHFT-CUBIGN)) & GRNMASK | \ ((B) >> (CUBIGN)) & BLUMASK) # define GETR8(X) (((X) & REDMASK) >> (REDSHFT-CUBIGN)) # define GETG8(X) (((X) & GRNMASK) >> (GRNSHFT-CUBIGN)) # define GETB8(X) (((X) & BLUMASK) << CUBIGN) # define abs(X) (((X)<0)?-(X):(X)) typedef struct cstruct { unsigned char rd, gr, bl, indx; short flags; } COLOR; # define FIXED 1 /* This color is set by the user */ # define UNUSABLE 2 /* This color may not be used when dithering */ COLOR *cmap = NULL; COLOR fmap[256]; int freecolors=0, fixedcolors=0, unusable=0, usable=0; typedef struct pix_struct { short cnt; short color; } PIXEL; int debug=0, verbose=0, colors=256, showcolor=0, dither=1, hue=0; int resbits = -1, resmask = 0xff; double flesh = 1.0, atof(); int fr = 250, fg = 150, fb = 80; int outtype = DEF_8BIT; /* Output format desired */ /**************************************************************** * main ****************************************************************/ # define USAGE \ "Usage: fbquant [ -c ] [ -r ] [ -m ]\n\ [ -f ] [ -n ] [ - ] < rgb > mapped" #ifndef lint static char *fbmid = "$FBM fbquant.c <1.2> 07-Apr-93 (C) 1989-1993 by Michael Mauldin, source \ code available free from MLM@CS.CMU.EDU and from UUNET archives$"; #endif main (argc, argv) char *argv[]; { FBM input, output, mapimage; /* Images */ int hist[CUBSIZ]; /* Color cube 32x32x32 for histogram */ char *mapfile = NULL; /* Name of file to copy map from */ register int c; int aindx, ard, agr, abl; /* Get 'i' arguments from sscanf */ int i, bad=0; /* Error checking temp */ /* Clear pointers */ input.bm = input.cm = (unsigned char *) NULL; output.bm = output.cm = (unsigned char *) NULL; mapimage.bm = mapimage.cm = (unsigned char *) NULL; /* Get the options */ while (--argc > 0 && (*++argv)[0] == '-') { while (*++(*argv)) { switch (**argv) { case 'c': colors = atoi (*argv+1); SKIPARG; break; case 'f': flesh = atof (*argv+1); SKIPARG; break; case 'r': resbits = atoi (*argv+1); SKIPARG; break; case 'm': mapfile = *argv+1; SKIPARG; break; case 'n': dither = 0; break; case 'd': debug++; break; case 'D': showcolor++; break; case 'v': verbose++; break; case 'h': if (isdigit (argv[0][1])) { hue = atoi (*argv+1); SKIPARG; } else { hue = 32; } break; case 'C': load_config (*argv+1); SKIPARG; break; case 'A': outtype = FMT_ATK; break; case 'B': outtype = FMT_FACE; break; case 'F': outtype = FMT_FBM; break; case 'G': outtype = FMT_GIF; break; case 'I': outtype = FMT_IFF; break; case 'J': outtype = FMT_JPEG; break; case 'L': outtype = FMT_LEAF; break; case 'M': outtype = FMT_MCP; break; case 'P': outtype = FMT_PBM; break; case 'R': outtype = FMT_RLE; break; case 'S': outtype = FMT_SUN; break; case 'T': outtype = FMT_TIFF; break; case 'X': outtype = FMT_X11; break; case 'Z': outtype = FMT_PCX; break; case 'i': unusable++; /* fall through */ case 's': if (sscanf (*argv+1, "%d,%d,%d,%d", &aindx, &ard, &agr, &abl)) { fmap[fixedcolors].indx = aindx; fmap[fixedcolors].rd = ard; fmap[fixedcolors].gr = agr; fmap[fixedcolors].bl = abl; fmap[fixedcolors].flags = FIXED | (**argv == 'i' ? UNUSABLE : 0); fixedcolors++; SKIPARG; } break; default: fprintf (stderr, "%s\n", USAGE); exit (1); } } } freecolors = colors - fixedcolors; usable = colors - unusable; /* Check fixed colors for being out of range */ for (i=0, bad=0; i= colors) { fprintf (stderr, "Error: fixed color %d is out of range [0..%d]\n", fmap[i].indx, colors-1); bad++; } } if (bad) exit (1); /* Check arguments */ if (colors > 256 || freecolors < 4 || colors < 4) { fprintf (stderr, "fbquant can only handle 4..256 colors\n"); exit (1); } /* Choose default resbits: if using few colors, assume low-res */ if (resbits < 0) { if (colors <= 32) resbits = 4; else resbits = 8; } if (resbits > 8 || resbits < 1) { fprintf (stderr, "color resolution (-r) must be 1..8 bits\n"); exit (1); } resmask = (0xff - (0xff >> resbits)); /* Open file if name given as argument */ if (! read_bitmap (&input, (argc > 0) ? argv[0] : (char *) NULL)) { exit (1); } /* Read colormap from map image (if specified) */ if (mapfile != NULL) { if (!read_bitmap (&mapimage, mapfile)) { perror (mapfile); exit (1); } if (mapimage.hdr.clrlen == 0) { fprintf (stderr, "fbquant: %s: no map\n", mapfile); exit (1); } /* Dont care about the map image, just its colormap */ free (mapimage.bm); mapimage.bm = NULL; colors = mapimage.hdr.clrlen / 3; freecolors = colors - fixedcolors; usable = colors - unusable; cmap = (COLOR *) malloc ((unsigned) colors * sizeof (COLOR)); for (c=0; chdr.cols, height = image->hdr.rows; int rowlen = image->hdr.rowlen, plnlen = image->hdr.plnlen; int used=0; /* Clear the histogram */ for (i=0; ibm[j*rowlen]); gp = rp+plnlen; bp = gp+plnlen; /* If resolution is greater than cube size, just count */ if (resbits >= CUBITS) { for (i=0; i 1.01) { register int diff = 0, new; int maxdiff = 0; int changes = 0; double bonus; maxdiff += (fr > 127) ? fr : 256 - fr; maxdiff += (fg > 127) ? fr : 256 - fr; maxdiff += (fb > 127) ? fr : 256 - fr; for (i=0; i 0) { j = GETR8(i) - fr; j = abs (j); diff = j; j = GETG8(i) - fr; j = abs (j); diff += j; j = GETB8(i) - fr; j = abs (j); diff += j; if (diff < maxdiff) { bonus = 1.0 + flesh * ((double) (maxdiff - diff) / maxdiff); new = ((double) hist[i] * bonus + 0.5); if (new < 0) new = 0; if (new != hist[i]) { hist[i] = new; changes++; } } } } fprintf (stderr, "Flesh bonus %1.4lf, max diff %d, changed %d boxes\n", flesh, maxdiff, changes); } if (debug) { fprintf (stderr, "Done counting, used %d colors for %d pixels\n", used, width*height); } } /**************************************************************** * build_colormap: ****************************************************************/ # define SWAP(A,B) (t=A,A=B,B=t) build_colormap (hist, cmap, colors) int *hist, colors; COLOR *cmap; { register int i, k; PIXEL box[CUBSIZ]; register PIXEL *b; int arrange[256]; int used=0, t, freecolors = colors-fixedcolors; int usable = colors - unusable;; /* Build the first box, encompassing all pixels */ for (b=box, i=0; icolor = i; k = hist[i]; b->cnt = (k > MAXSHRT) ? MAXSHRT : k; b++; } /* Move all non-zero count pixels to the front of the list */ for (i=0, used=0; i 0) box[used++] = box[i]; } /*-------- Special case if we didnt need all colors --------*/ if (used <= freecolors) { /* Copy the colors actually found */ for (i=0; i=0; i--) { cmap[i] = cmap[arrange[i]]; cmap[i].indx = i; } /* Now insert the fixed colors */ for (i=0; i boxlen) { fprintf (stderr, "boxlen %d is less numclr %d, panic!\n than", boxlen, numclr); fflush (stderr); abort (); } /* Base case: only one color, assign the average for this cell */ if (numclr <= 1) { red = box_avg_red (box, boxlen); grn = box_avg_grn (box, boxlen); blu = box_avg_blu (box, boxlen); /* Map x to x+4, because the histogram maps values to multiples of 8 */ cmap[clr].rd = red + 4; cmap[clr].gr = grn + 4; cmap[clr].bl = blu + 4; cmap[clr].indx = clr; cmap[clr].flags = 0; /* Mask off unused bits for color resolution */ cmap[clr].rd &= resmask; cmap[clr].gr &= resmask; cmap[clr].bl &= resmask; /* Should repeat highlevel bits in lower bits to even out values */ if (debug && verbose) { fprintf (stderr, "\t\tassigning color %d <%d,%d,%d> (%d)\n", clr, cmap[clr].rd, cmap[clr].gr, cmap[clr].bl, box_weight (box, boxlen)); } return; } /* Gather statistics about the boxes contents */ minv[RD] = minv[GR] = minv[BL] = CUBSID; maxv[RD] = maxv[GR] = maxv[BL] = 0; numv[RD] = numv[GR] = numv[BL] = 0; for (i=0; i maxv[RD]) maxv[RD] = red; if (grn < minv[GR]) minv[GR] = grn; if (grn > maxv[GR]) maxv[GR] = grn; if (blu < minv[BL]) minv[BL] = blu; if (blu > maxv[BL]) maxv[BL] = blu; if (++pcnt[RD][red] == 1) numv[RD]++; if (++pcnt[GR][grn] == 1) numv[GR]++; if (++pcnt[BL][blu] == 1) numv[BL]++; } /* Special case, boxlen = numclr, just assign each box one color */ if (boxlen == numclr) { for (i=0; i maxdif) { maxdif = dif; split = RD; } if ((dif = (maxv[GR] - minv[GR])) > maxdif) { maxdif = dif; split = GR; } if ((dif = (maxv[BL] - minv[BL])) > maxdif) { maxdif = dif; split = BL; } /* Sort along the chosen dimension */ switch (split) { case RD: qsort (box, boxlen, sizeof (*box), cmp_red); break; case GR: qsort (box, boxlen, sizeof (*box), cmp_grn); break; case BL: qsort (box, boxlen, sizeof (*box), cmp_blu); break; default: fprintf (stderr, "panic in split_box, split %d\n", split); fflush (stderr); fflush (stdout); abort (); } /* * Split at the median, but make sure there are at least numclr/2 * different colors on each side of the split, to avoid wasting * colors. * * Note: need to keep in mind that when the box is large, dont split * too close to one edge. */ half = numclr / 2; top = box; bot = box + (boxlen-1); topw = top->cnt; botw = bot->cnt; /* Set top and bot to point to min/max feasible splits */ while ((top-box)+1 < half) { top++; topw += top->cnt; } while ((boxlen-(bot-box)) < half) { bot--; botw += bot->cnt; } /* Move top and bottom towards each other 1/8 of remaining distance */ c = (bot-top) / 8; for (i=0; icnt; } for (i=0; icnt; } /* Now search for median */ while (top < bot) { if (topw < botw) { top++; topw += top->cnt; } else { bot--; botw += bot->cnt; } } /* Decide which half gets the midpoint */ if (topw > botw) /* Median wants to go with top */ { sbox = (top-box) + 1; snum = numclr - half; } else /* Median wants to go with bottom */ { sbox = (top - box); snum = half; } /* Handle boundary conditions with number of colors vs box size */ if (sbox == 0) sbox++; else if (sbox == boxlen) sbox--; while (snum > sbox) snum--; while (numclr-snum > boxlen-sbox) snum++; # ifndef OPTIMIZE /* Check for boundary condition errors */ if (snum <= 0 || snum >= numclr) { fprintf (stderr, "panic, using zero colors for box\n"); fflush (stderr); abort (); } if (boxlen-sbox < numclr-snum) { fprintf (stderr, "panic, about to used %d boxes for %d colors\n", boxlen-sbox, numclr-snum); fflush (stderr); abort (); } if (sbox < snum) { fprintf (stderr, "panic, about to used %d boxes for %d colors\n", sbox, snum); fflush (stderr); abort (); } # endif if (debug && verbose) { int count = numclr, depth = 8; while (count > 0) { depth--; count /= 2; } for (i=0; i", 0, boxlen-1, numclr, minv[RD], maxv[RD], numv[RD], minv[GR], maxv[GR], numv[GR], minv[BL], maxv[BL], numv[BL]); fprintf (stderr, " %c [%d..%d|%d] [%d..%d|%d]\n", "RGB"[split], 0, sbox-1, snum, sbox, boxlen-1, numclr-snum); } /* Now recurse and split each sub-box */ split_box (box, sbox, clr, snum, cmap); split_box (box+sbox, boxlen - sbox, clr+snum, numclr-snum, cmap); } /**************************************************************** * box_weight: Determine the total count of a box ****************************************************************/ box_weight (box, boxlen) register PIXEL *box; register int boxlen; { register int sum = 0, i; for (i=0; icolor) - GETR(b->color)) { return (r); } if (r = GETG(a->color) - GETG(b->color)) { return (r); } if (r = GETB(a->color) - GETB(b->color)) { return (r); } return (0); } /**************************************************************** * sort by increasing green ( then blue and red ) ****************************************************************/ cmp_grn (a, b) PIXEL *a, *b; { register r; if (r = GETG(a->color) - GETG(b->color)) { return (r); } if (r = GETB(a->color) - GETB(b->color)) { return (r); } if (r = GETR(a->color) - GETR(b->color)) { return (r); } return (0); } /**************************************************************** * sort by increasing blue ( then red and green ) ****************************************************************/ cmp_blu (a, b) PIXEL *a, *b; { register r; if (r = GETB(a->color) - GETB(b->color)) { return (r); } if (r = GETR(a->color) - GETR(b->color)) { return (r); } if (r = GETG(a->color) - GETG(b->color)) { return (r); } return (0); } /**************************************************************** * clr_quantize: Do Floyd Steinberg quantizing on the image ****************************************************************/ clr_quantize (input, output, cmap, colors, fmap, fixedcolors) FBM *input, *output; COLOR *cmap, *fmap; int colors, fixedcolors; { int **cerr, **lerr, **terr; int width = input->hdr.cols, height = input->hdr.rows; int rowlen = input->hdr.rowlen, plnlen = input->hdr.plnlen; register int i, j; register int rd, gr, bl; int rderr, grerr, blerr, clr; unsigned char *rp, *gp, *bp, *obm; /*-------- Copy colormap into output bitmap --------*/ for (i=0; icm[i] = cmap[i].rd; output->cm[i+colors] = cmap[i].gr; output->cm[i+colors+colors] = cmap[i].bl; } /* * Precompute necessary nearest neighbor query info. Note that we wait * until after copying the colormap, since init reorders it */ init_nearest (cmap, colors); /*-------- Do halftoning --------*/ cerr = (int **) malloc (3 * sizeof (int *)); lerr = (int **) malloc (3 * sizeof (int *)); cerr[RD] = (int *) malloc (width * sizeof (int)); cerr[GR] = (int *) malloc (width * sizeof (int)); cerr[BL] = (int *) malloc (width * sizeof (int)); lerr[RD] = (int *) malloc (width * sizeof (int)); lerr[GR] = (int *) malloc (width * sizeof (int)); lerr[BL] = (int *) malloc (width * sizeof (int)); /*-------- Just use the nearest color everywhere --------*/ if (!dither) { for (j=0; jbm[j*rowlen]); gp = rp+plnlen; bp = gp+plnlen; obm = &(output->bm[j*rowlen]); for (i=0; ibm; gp = rp+plnlen; bp = gp+plnlen; obm = output->bm; for (i=0; ibm; gp = rp+plnlen; bp = gp+plnlen; obm = output->bm; for (j=0; jbm[width-1]); gp = rp + plnlen; bp = gp + plnlen; obm = &(output->bm[width-1]); for (j=0; jbm[j*rowlen+1]); gp = rp+plnlen; bp = gp+plnlen; obm = &(output->bm[j*rowlen+1]); for (i=1; i>= 4; /* Divide by 16 */ grerr >>= 4; /* Divide by 16 */ blerr >>= 4; /* Divide by 16 */ /* Choose nearest color to adjusted RGB value */ rd += rderr; gr += grerr; bl += blerr; clr = nearest (rd, gr, bl, cmap, usable); *obm++ = cmap[clr].indx; /* Compute accumulated error for this pixel */ rd -= (int) cmap[clr].rd; gr -= (int) cmap[clr].gr; bl -= (int) cmap[clr].bl; /* Limit error (avoids color shadows) to 75% if over MAXERR */ if (rd < -MAXERR || rd > MAXERR) rd = (rd * 3) >> 2; if (gr < -MAXERR || gr > MAXERR) gr = (gr * 3) >> 2; if (bl < -MAXERR || bl > MAXERR) bl = (bl * 3) >> 2; /* Store errors in error vectors */ cerr[RD][i] = rd; cerr[GR][i] = gr; cerr[BL][i] = bl; } /* Swap error vectors */ terr = lerr; lerr = cerr; cerr = terr; } } /**************************************************************** * nearest: Choose nearest color ****************************************************************/ short cache[CUBSIZ]; init_nearest (cmap, colors) COLOR *cmap; int colors; { register int i; if (debug || showcolor) { fprintf (stderr, "\nFinal colormap (%d colors, %d fixed, %d usable):\n\n", colors, fixedcolors, usable); for (i=0; i [%d]%s%s\n", i, cmap[i].rd, cmap[i].gr, cmap[i].bl, cmap[i].indx, (cmap[i].flags & FIXED) ? " fixed" : "", (cmap[i].flags & UNUSABLE) ? " unusuable" : ""); } } /* Initialize the cache */ for (i=0; ird)) - CR), \ sumtmp += SQR (((int) ((CPTR)->gr)) - CG), \ sumtmp += SQR (((int) ((CPTR)->bl)) - CB), \ sumtmp) # define restrict(X) ((X) < 0 ? 0 : (X) > 255 ? 255 : (X)) nearest (rd, gr, bl, cmap, colors) int rd, gr, bl; COLOR *cmap; int colors; { register int clr, sqrtmp, sumtmp; register COLOR *a, *b, *c, *best, *ctail; int cindx, bestd, dist; rd = restrict (rd); gr = restrict (gr); bl = restrict (bl); /* Find array index in cache */ cindx = CLRINDEX8 (rd, gr, bl); /* Check cache for color value */ if ((clr = cache[cindx]) >= 0) { return (clr); } /* * Search through colormap for nearest neighbor: * 1) Find nearest red value by binary search * 2) Search up and down, keeping best point * 3) Terminate search when red difference is greater than best pt */ /* Binary search for nearest red value */ ctail = &cmap[colors]; for (a=cmap, b= ctail-1; ard, b-cmap, b->rd, c-cmap, c->rd); } if (c->rd == rd) { break; } else if (c->rd < rd) { a = ++c; } else { b = c; } } /* * c now points to closest red value, search up and down for closer * Euclidean distance, and stop search when the red distance alone * exceeds the best found. */ /* Set best and bestd to best red value */ best = c; bestd = CDIST (c, rd, gr, bl); if (debug && verbose) fprintf (stderr, "Found c=%d (%d) dist = %d\n", c-cmap, c->rd, bestd); /* a and b are search pointers up and down */ a = c-1; b = c+1; while (bestd > 0 && (a >= cmap || b < ctail)) { if (debug && verbose) { fprintf (stderr, " search: bestd %d, best %d|%d, a %d, b %d\n", bestd, best-cmap, best->indx, a-cmap, b-cmap); } if (a >= cmap) { if ((dist = CDIST (a, rd, gr, bl)) < bestd) { best = a; bestd = dist; } if (SQR ((int) a->rd - rd) >= bestd) a = cmap-1; else a--; } if (b < ctail) { if ((dist = CDIST (b, rd, gr, bl)) < bestd) { best = b; bestd = dist; } if (SQR ((int) b->rd - rd) >= bestd) b = ctail; else b++; } } if (best < cmap || best >= ctail) { perror ("returning invalid color\n"); abort (); } clr = (best - cmap); if (debug && verbose) { fprintf (stderr, "Nearest(%3d,%3d,%3d) => <%3d,%3d,%3d> [%d]\n", rd, gr, bl, best->rd, best->gr, best->bl, best->indx); } return ((cache[cindx] = clr)); } /**************************************************************** * sort colormap by increasing red ( then green and blue ) ****************************************************************/ cmp_cmap (a, b) register COLOR *a, *b; { register int r; /* Put unusable colors at end of list */ if (b->flags & UNUSABLE) return (-1); /* Else sort by Red, green, blue */ if (r = (a->rd - b->rd)) { return (r); } if (r = (a->gr - b->gr)) { return (r); } if (r = (a->bl - b->bl)) { return (r); } return (0); } /**************************************************************** * sort colormap by increasing intensity (Use NTSC weights) ****************************************************************/ # define RW 299 # define GW 587 # define BW 114 cmp_int (a, b) register COLOR *a, *b; { register int ai, bi; ai = RW * a->rd + GW * a->gr + BW * a->bl; bi = RW * b->rd + GW * b->gr + BW * b->bl; return (ai - bi); } /**************************************************************** * sort colormap by hue (approx), then intensity ****************************************************************/ find_hue (rd, gr, bl) int rd, gr, bl; { double nrd, ngr, nbl; double erd, egr, ebl; double min, max, hv, floor(); int sum, smallest, result, buckets = hue; double sat; double sthresh0 = 0.02; double sthresh1 = 0.17; /* Greys get hue zero */ if (rd == gr && gr == bl) return (0); sum = rd + gr + bl; nrd = (double) rd / sum; ngr = (double) gr / sum; nbl = (double) bl / sum; if (rd < gr && rd <= bl) { smallest = RD; min = nrd; max = (gr > bl) ? ngr : nbl; } else if (gr < bl && gr <= rd) { smallest = GR; min = ngr; max = (rd > bl) ? nrd : nbl; } else { smallest = BL; min = nbl; max = (rd > gr) ? nrd : ngr; } sat = max - min; /* Check for close to gray (at most 5% difference) */ if (sat < sthresh0) return (0); /* Subtract out the smallest value (remove that much white) */ erd = nrd - min; egr = ngr - min; ebl = nbl - min; /* Scalar: 0=red, 0.16=yel, 0.33=grn, 0.5=cyn, 0.66=blu, 0.83=mag */ switch (smallest) { case BL: hv = (egr / (erd + egr)) / 3.0; break; case RD: hv = (ebl / (egr + ebl) + 1.0) / 3.0; break; case GR: hv = (erd / (ebl + erd) + 2.0) / 3.0; break; } /* With more than 10 buckets, use 2 levels of saturation */ if (hue > 20) { buckets = hue / 2; result = (int) floor (hv * buckets + 0.5); result = (result % buckets) + 1; if (sat >= sthresh1) result += buckets; } /* Just use 1 level of saturation (sthresh0 for grey) */ else { result = (int) floor (hv * hue + 0.5); result = (result % hue) + 1; } return (result); } cmp_hue (a, b) register COLOR *a, *b; { register int ai, bi; register int h1, h2; h1 = find_hue (a->rd, a->gr, a->bl); h2 = find_hue (b->rd, b->gr, b->bl); if (h1 != h2) return (h1 - h2); ai = RW * a->rd + GW * a->gr + BW * a->bl; bi = RW * b->rd + GW * b->gr + BW * b->bl; return (ai - bi); }