ftp.nice.ch/NiCE/X/xv-3.00a.tar.gz#/xv-3.00a/xv24to8.c

This is xv24to8.c in view mode; [Download] [Up]

/*
 * xv24to8.c  -  contains the 24-to-8-bit Conv24to8() procedure
 *               and the 8-to-24-bit Conv8to24() procedure
 *
 * The Conv24to8 procedure takes a pointer to a 24-bit image (loaded
 * previously).  The image will be a w * h * 3 byte array of
 * bytes.  The image will be arranged with 3 bytes per pixel (in order
 * R, G, and B), pixel 0 at the top left corner.  (As normal.)
 * The procedure also takes a maximum number of colors to use (numcols)
 * and pointers to three 256-long arrays of bytes (to hold the returned
 * colormap)
 *
 * Note that Conv24to8() does NOT free the pic24 image under any circumstances
 *
 * The Conv24to8 procedure will set up the following:  it will allocate, make
 * & return 'pic8', a 'w' by 'h' (passed in values) 8-bit picture.
 * it will load up the rmap, gmap and bmap colormap arrays.  it will NOT 
 * calculate numcols, since the cmap sort procedure has to be called anyway
 *
 * Conv24to8 returns 'pic8' if successful, 'NULL' on failure (presumably on a 
 * malloc())
 *
 * The 'slow' code is based on Heckbert's Median Cut algorithm.
 *
 * contains:
 *   Cont24to8()
 *   Init24to8()
 */

/* Copyright Notice
 * ================
 * Copyright 1989, 1990, 1991, 1992, 1993 by John Bradley
 * 
 * Permission to use, copy, and distribute XV in its entirety, for 
 * non-commercial purposes, is hereby granted without fee, provided that
 * this license information and copyright notice appear in all copies.
 * 
 * Note that distributing XV 'bundled' in with ANY product is considered
 * to be a 'commercial purpose'.
 *
 * Also note that any copies of XV that are distributed MUST be built
 * and/or configured to be in their 'unregistered copy' mode, so that it
 * is made obvious to the user that XV is shareware, and that they should
 * consider donating, or at least reading this License Info.
 * 
 * The software may be modified for your own purposes, but modified
 * versions may NOT be distributed without prior consent of the author.
 * 
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the author be held liable for any damages
 * arising from the use of this software.
 * 
 * If you would like to do something with XV that this copyright
 * prohibits (such as distributing it with a commercial product, 
 * using portions of the source in some other program, etc.), please
 * contact the author (preferably via email).  Arrangements can
 * probably be worked out.
 *
 * XV is shareware for PERSONAL USE only.  You may use XV for your own
 * amusement, and if you find it nifty, useful, generally cool, or of
 * some value to you, your non-deductable donation would be greatly
 * appreciated.  $25 is the suggested donation, though, of course,
 * larger donations are quite welcome.  Folks who donate $25 or more
 * can receive a Real Nice bound copy of the XV manual for no extra
 * charge.
 * 
 * Commercial, government, and institutional users MUST register their
 * copies of XV, for the exceedingly REASONABLE price of just $25 per
 * workstation/X terminal.  Site licenses are available for those who
 * wish to run XV on a large number of machines.  Contact the author
 * for more details.
 *
 * The author may be contacted via:
 *    US Mail:  John Bradley
 *              1053 Floyd Terrace
 *              Bryn Mawr, PA  19010
 *
 *    Phone:    (215) 898-8813
 *    EMail:    bradley@cis.upenn.edu
 */

/*
 * Portions Copyright (C) 1989, 1991 by Jef Poskanzer.  See copyright notice
 * at beginning of relevant code.
 */

#include "xv.h"

#define	MAX_CMAP_SIZE	256
#define	COLOR_DEPTH	8
#define	MAX_COLOR	256
#define	B_DEPTH		5		/* # bits/pixel to use */
#define	B_LEN		(1<<B_DEPTH)
#define	C_DEPTH		3
#define	C_LEN		(1<<C_DEPTH)	/* # cells/color to use */

#define R2FACT 20 /* .300 * .300 * 256 = 23 */
#define G2FACT 39 /* .586 * .586 * 256 = 88 */
#define B2FACT 8  /* .114 * .114 * 256 =  3 */


typedef	struct colorbox {
  struct colorbox *next, *prev;
  int              rmin,rmax, gmin,gmax, bmin,bmax;
  int              total;
} CBOX;

typedef struct {
  int num_ents;
  int entries[MAX_CMAP_SIZE][2];
} CCELL;

static byte *pic8;
static byte *pic24;
static byte *rmap, *gmap, *bmap;      /* ptrs to computed colormap */
static int   num_colors, WIDE, HIGH;
static int   histogram[B_LEN][B_LEN][B_LEN];

CBOX   *freeboxes, *usedboxes;
CCELL **ColorCells;

#ifdef __STDC__
  static void   get_histogram(CBOX *);
  static CBOX  *largest_box(void);
  static void   splitbox(CBOX *);
  static void   shrinkbox(CBOX *);
  static void   assign_color(CBOX *, byte *, byte *, byte *);
  static CCELL *create_colorcell(int, int, int);
  static void   map_colortable(void);
  static int    quant_fsdither(void);
  static int    Quick24to8(byte *, int, int);
  static int    QuickCheck(byte *, int, int, int);
  static int    ppmquant(byte *, int, int, int);
#else
  static void   get_histogram();
  static CBOX  *largest_box();
  static void   splitbox();
  static void   shrinkbox();
  static void   assign_color();
  static CCELL *create_colorcell();
  static void   map_colortable();
  static int    quant_fsdither();
  static int    Quick24to8();
  static int    QuickCheck();
  static int    ppmquant();
#endif

static int    tbl1[512],     /* tables used in F-S Dithering */
              tbl3[512],     /* contain i/16, 3i/16, 5i/16, 7i/16, */
              tbl5[512],     /* (i=-256..255) respectively */
              tbl7[512];


/****************************/
void Init24to8()
/****************************/
{
  /* initialize Floyd-Steinberg division tables */
  /* make sure rounding is done correctly for negative values! */

  int i;

  for (i = -256; i < 0; i++) {
    tbl1[i+256] = -((8 -i  )/16);
    tbl3[i+256] = -((8 -3*i)/16);
    tbl5[i+256] = -((8 -5*i)/16);
    tbl7[i+256] = -((8 -7*i)/16);
  }

  for (i = 0; i < 256; i++) {
    tbl1[i+256] = (i  +8)/16;
    tbl3[i+256] = (3*i+8)/16;
    tbl5[i+256] = (5*i+8)/16;
    tbl7[i+256] = (7*i+8)/16;
  }

#ifdef FOO 
  for (i=0; i<512; i++) {
    printf("%3d:  tbl1=%3d, tbl3=%3d, tbl5=%3d, tbl7=%3d\n",
	   i-256, tbl1[i], tbl3[i], tbl5[i], tbl7[i]);
  }
#endif
}



/****************************/
byte *Conv24to8(p,w,h,nc,rm,gm,bm)
/****************************/
byte *p;
byte *rm, *gm, *bm;
int   w,h,nc;
{
  int   i, j;
  CBOX *box_list, *ptr;

  /* returns pointer to new 8-bit-per-pixel image (w*h) if successful, or
     NULL if unsuccessful */

  if (nc<=0) nc = 255;  /* 'nc == 0' breaks code */

  /* copy arguments to local-global variables */
  pic24 = p;  WIDE = w;  HIGH = h;  num_colors = nc;
  rmap = rm;  gmap = gm;  bmap = bm;

  if (!pic24) return NULL;

  /* allocate pic immediately, so that if we can't allocate it, we don't
     waste time running this algorithm */

  pic8 = (byte *) malloc(WIDE * HIGH);
  if (!pic8) {
    fprintf(stderr,"%s: Conv24to8() - failed to allocate 'pic8'\n",cmd);
    return pic8;
  }

  if (!noqcheck && QuickCheck(pic24,w,h,nc)) { 
    /* see if it's a <256 color RGB pic */
    SetISTR(ISTR_INFO,"No color compression was necessary.\n");
    return pic8;   
  }

  else if (conv24 == CONV24_FAST) {
    SetISTR(ISTR_INFO,"Doing 'quick' 24-bit to 8-bit conversion.");
    i = Quick24to8(pic24,w,h);
    if (i) { free(pic8);  pic8 = NULL; }
    return pic8;
  }

  else if (conv24 == CONV24_BEST) {
    SetISTR(ISTR_INFO,"Doing 'best' 24-bit to 8-bit conversion.");
    i = ppmquant(pic24,w,h,nc);
    if (i) { free(pic8);  pic8 = NULL; }
    return pic8;
  }

  else 
    SetISTR(ISTR_INFO,"Doing 'slow' 24-bit to 8-bit conversion.");



  /**** STEP 1:  create empty boxes ****/

  usedboxes = NULL;
  box_list = freeboxes = (CBOX *) malloc(num_colors * sizeof(CBOX));

  if (box_list == NULL) {
    fprintf(stderr,"%s: Conv24to8() - failed to allocate 'freeboxes'\n",cmd);
    free(pic8);  pic8 = NULL;
    return pic8;
  }

  for (i=0; i<num_colors; i++) {
    freeboxes[i].next = &freeboxes[i+1];
    freeboxes[i].prev = &freeboxes[i-1];
  }
  freeboxes[0].prev = NULL;
  freeboxes[num_colors-1].next = NULL;




  /**** STEP 2: get histogram, initialize first box ****/

  ptr = freeboxes;
  freeboxes = ptr->next;
  if (freeboxes) freeboxes->prev = NULL;

  ptr->next = usedboxes;
  usedboxes = ptr;
  if (ptr->next) ptr->next->prev = ptr;
	
  get_histogram(ptr);




  /**** STEP 3: continually subdivide boxes until no more free boxes remain */

  while (freeboxes) {
    ptr = largest_box();
    if (ptr) splitbox(ptr);
    else break;
  }



  
  /**** STEP 4: assign colors to all boxes ****/

  for (i=0, ptr=usedboxes; i<num_colors && ptr; i++, ptr=ptr->next) {
    assign_color(ptr, &rmap[i], &gmap[i], &bmap[i]);
  }

  /* We're done with the boxes now */
  num_colors = i;
  free(box_list);
  box_list = freeboxes = usedboxes = NULL;
 



  /**** STEP 5: scan histogram and map all values to closest color */

  /* 5a: create cell list as described in Heckbert[2] */

  ColorCells = (CCELL **) calloc(C_LEN*C_LEN*C_LEN, sizeof(CCELL *));


  /* 5b: create mapping from truncated pixel space to color table entries */

  map_colortable();




  /**** STEP 6: scan image, match input values to table entries */

  i = quant_fsdither();

  /* free everything that can be freed */
  for (j=0 ; j < C_LEN*C_LEN*C_LEN ; j++) {
    if (ColorCells[j] != NULL) free (ColorCells[j]);
  }
  free(ColorCells);

  if (i) { free(pic8);  pic8 = NULL; }  /* free 'pic' on failure */
  return pic8;
}


/****************************/
static void get_histogram(box)
     CBOX *box;
/****************************/
{
  int   i,j,r,g,b,*ptr;
  byte *p;

  box->rmin = box->gmin = box->bmin = 999;
  box->rmax = box->gmax = box->bmax = -1;
  box->total = WIDE * HIGH;

  /* zero out histogram */
  ptr = &histogram[0][0][0];
  for (i=B_LEN*B_LEN*B_LEN; i>0; i-- )  *ptr++ = 0;

  /* calculate histogram */
  p = pic24;
  for (i=0; i<HIGH; i++)
    for (j=0; j<WIDE; j++) {
      r = (*p++) >> (COLOR_DEPTH-B_DEPTH);
      g = (*p++) >> (COLOR_DEPTH-B_DEPTH);
      b = (*p++) >> (COLOR_DEPTH-B_DEPTH);

      if (r < box->rmin) box->rmin = r;
      if (r > box->rmax) box->rmax = r;

      if (g < box->gmin) box->gmin = g;
      if (g > box->gmax) box->gmax = g;

      if (b < box->bmin) box->bmin = b;
      if (b > box->bmax) box->bmax = b;
      histogram[r][g][b]++;
    }
}



/******************************/
static CBOX *largest_box()
/******************************/
{
  CBOX *tmp, *ptr;
  int   size = -1;

  tmp = usedboxes;
  ptr = NULL;

  while (tmp) {
    if ( (tmp->rmax > tmp->rmin  ||
	  tmp->gmax > tmp->gmin  ||
	  tmp->bmax > tmp->bmin)  &&  tmp->total > size ) {
      ptr = tmp;
      size = tmp->total;
    }
    tmp = tmp->next;
  }
  return(ptr);
}



/******************************/
static void splitbox(ptr)
     CBOX *ptr;
/******************************/
{
  int   hist2[B_LEN], first, last, i, rdel, gdel, bdel;
  CBOX *new;
  int  *iptr, *histp, ir, ig, ib;
  int  rmin,rmax,gmin,gmax,bmin,bmax;
  enum {RED,GREEN,BLUE} which;

  /*
   * see which axis is the largest, do a histogram along that
   * axis.  Split at median point.  Contract both new boxes to
   * fit points and return
   */

  first = last = 0;   /* shut RT hcc compiler up */

  rmin = ptr->rmin;  rmax = ptr->rmax;
  gmin = ptr->gmin;  gmax = ptr->gmax;
  bmin = ptr->bmin;  bmax = ptr->bmax;

  rdel = rmax - rmin;
  gdel = gmax - gmin;
  bdel = bmax - bmin;

  if      (rdel>=gdel && rdel>=bdel) which = RED;
  else if (gdel>=bdel)               which = GREEN;
  else                               which = BLUE;

  /* get histogram along longest axis */
  switch (which) {

  case RED:
    histp = &hist2[rmin];
    for (ir=rmin; ir<=rmax; ir++) {
      *histp = 0;
      for (ig=gmin; ig<=gmax; ig++) {
	iptr = &histogram[ir][ig][bmin];
	for (ib=bmin; ib<=bmax; ib++) {
	  *histp += *iptr;
	  ++iptr;
	}
      }
      ++histp;
    }
    first = rmin;  last = rmax;
    break;

  case GREEN:
    histp = &hist2[gmin];
    for (ig=gmin; ig<=gmax; ig++) {
      *histp = 0;
      for (ir=rmin; ir<=rmax; ir++) {
	iptr = &histogram[ir][ig][bmin];
	for (ib=bmin; ib<=bmax; ib++) {
	  *histp += *iptr;
	  ++iptr;
	}
      }
      ++histp;
    }
    first = gmin;  last = gmax;
    break;

  case BLUE:
    histp = &hist2[bmin];
    for (ib=bmin; ib<=bmax; ib++) {
      *histp = 0;
      for (ir=rmin; ir<=rmax; ir++) {
	iptr = &histogram[ir][gmin][ib];
	for (ig=gmin; ig<=gmax; ig++) {
	  *histp += *iptr;
	  iptr += B_LEN;
	}
      }
      ++histp;
    }
    first = bmin;  last = bmax;
    break;
  }


  /* find median point */
  {
    int sum, sum2;

    histp = &hist2[first];

    sum2 = ptr->total/2;
    histp = &hist2[first];
    sum = 0;
        
    for (i=first; i<=last && (sum += *histp++)<sum2; i++);
    if (i==first) i++;
  }


  /* Create new box, re-allocate points */
  
  new = freeboxes;
  freeboxes = new->next;
  if (freeboxes) freeboxes->prev = NULL;

  if (usedboxes) usedboxes->prev = new;
  new->next = usedboxes;
  usedboxes = new;

  {
    int sum1,sum2,j;
    
    histp = &hist2[first];
    sum1 = 0;
    for (j = first; j < i; ++j) sum1 += *histp++;
    sum2 = 0;
    for (j = i; j <= last; ++j) sum2 += *histp++;
    new->total = sum1;
    ptr->total = sum2;
  }


  new->rmin = rmin;  new->rmax = rmax;
  new->gmin = gmin;  new->gmax = gmax;
  new->bmin = bmin;  new->bmax = bmax;

  switch (which) {
  case RED:    new->rmax = i-1;  ptr->rmin = i;  break;
  case GREEN:  new->gmax = i-1;  ptr->gmin = i;  break;
  case BLUE:   new->bmax = i-1;  ptr->bmin = i;  break;
  }

  shrinkbox(new);
  shrinkbox(ptr);
}


/****************************/
static void shrinkbox(box)
     CBOX *box;
/****************************/
{
  int *histp,ir,ig,ib;
  int  rmin,rmax,gmin,gmax,bmin,bmax;

  rmin = box->rmin;  rmax = box->rmax;
  gmin = box->gmin;  gmax = box->gmax;
  bmin = box->bmin;  bmax = box->bmax;

  if (rmax>rmin) {
    for (ir=rmin; ir<=rmax; ir++)
      for (ig=gmin; ig<=gmax; ig++) {
	histp = &histogram[ir][ig][bmin];
	for (ib=bmin; ib<=bmax; ib++)
	  if (*histp++ != 0) {
	    box->rmin = rmin = ir;
	    goto have_rmin;
	  }
      }

  have_rmin:
    if (rmax>rmin)
      for (ir=rmax; ir>=rmin; --ir)
	for (ig=gmin; ig<=gmax; ig++) {
	  histp = &histogram[ir][ig][bmin];
	  for (ib=bmin; ib<=bmax; ib++)
	    if (*histp++ != 0) {
	      box->rmax = rmax = ir;
	      goto have_rmax;
	    }
	}
  }


 have_rmax:

  if (gmax>gmin) {
    for (ig=gmin; ig<=gmax; ig++)
      for (ir=rmin; ir<=rmax; ir++) {
	histp = &histogram[ir][ig][bmin];
	for (ib=bmin; ib<=bmax; ib++)
	  if (*histp++ != 0) {
	    box->gmin = gmin = ig;
	    goto have_gmin;
	  }
      }
  have_gmin:
    if (gmax>gmin)
      for (ig=gmax; ig>=gmin; --ig)
	for (ir=rmin; ir<=rmax; ir++) {
	  histp = &histogram[ir][ig][bmin];
	  for (ib=bmin; ib<=bmax; ib++)
	    if (*histp++ != 0) {
	      box->gmax = gmax = ig;
	      goto have_gmax;
	    }
	}
  }


 have_gmax:

  if (bmax>bmin) {
    for (ib=bmin; ib<=bmax; ib++)
      for (ir=rmin; ir<=rmax; ir++) {
	histp = &histogram[ir][gmin][ib];
	for (ig=gmin; ig<=gmax; ig++) {
	  if (*histp != 0) {
	    box->bmin = bmin = ib;
	    goto have_bmin;
	  }
	  histp += B_LEN;
	}
      }
  have_bmin:
    if (bmax>bmin)
      for (ib=bmax; ib>=bmin; --ib)
	for (ir=rmin; ir<=rmax; ir++) {
	  histp = &histogram[ir][gmin][ib];
	  for (ig=gmin; ig<=gmax; ig++) {
	    if (*histp != 0) {
	      bmax = ib;
	      goto have_bmax;
	    }
	    histp += B_LEN;
	  }
	}
  }

 have_bmax: return;
}



/*******************************/
static void assign_color(ptr,rp,gp,bp)
     CBOX *ptr;
     byte *rp,*gp,*bp;
/*******************************/
{

  int r,g,b;

  r = ((ptr->rmin + ptr->rmax) << (COLOR_DEPTH - B_DEPTH)) / 2;
  g = ((ptr->gmin + ptr->gmax) << (COLOR_DEPTH - B_DEPTH)) / 2;
  b = ((ptr->bmin + ptr->bmax) << (COLOR_DEPTH - B_DEPTH)) / 2;

  *rp = (byte) r;  *gp = (byte) g;  *bp = (byte) b;
}



/*******************************/
static CCELL *create_colorcell(r1,g1,b1)
     int r1,g1,b1;
/*******************************/
{
  register int    i;
  register CCELL *ptr;
  register byte  *rp,*gp,*bp;
  int             ir,ig,ib;
  long            dist, mindist, tmp;

  ir = r1 >> (COLOR_DEPTH-C_DEPTH);
  ig = g1 >> (COLOR_DEPTH-C_DEPTH);
  ib = b1 >> (COLOR_DEPTH-C_DEPTH);

  r1 &= ~1 << (COLOR_DEPTH-C_DEPTH);
  g1 &= ~1 << (COLOR_DEPTH-C_DEPTH);
  b1 &= ~1 << (COLOR_DEPTH-C_DEPTH);

  ptr = (CCELL *) malloc(sizeof(CCELL));
  *(ColorCells + ir*C_LEN*C_LEN + ig*C_LEN + ib) = ptr;
  ptr->num_ents = 0;

  /* step 1: find all colors inside this cell, while we're at
     it, find distance of centermost point to furthest
     corner */

  mindist = 2000000000;

  rp=rmap;  gp=gmap;  bp=bmap;
  for (i=0; i<num_colors; i++,rp++,gp++,bp++)
    if( *rp>>(COLOR_DEPTH-C_DEPTH) == ir  &&
        *gp>>(COLOR_DEPTH-C_DEPTH) == ig  &&
        *bp>>(COLOR_DEPTH-C_DEPTH) == ib) {

      ptr->entries[ptr->num_ents][0] = i;
      ptr->entries[ptr->num_ents][1] = 0;
      ++ptr->num_ents;

      tmp = *rp - r1;
      if (tmp < (MAX_COLOR/C_LEN/2)) tmp = MAX_COLOR/C_LEN-1 - tmp;
      dist = (tmp*tmp * R2FACT);

      tmp = *gp - g1;
      if (tmp < (MAX_COLOR/C_LEN/2)) tmp = MAX_COLOR/C_LEN-1 - tmp;
      dist += (tmp*tmp * G2FACT);

      tmp = *bp - b1;
      if (tmp < (MAX_COLOR/C_LEN/2)) tmp = MAX_COLOR/C_LEN-1 - tmp;
      dist += (tmp*tmp * B2FACT);

      if (dist < mindist) mindist = dist;
    }


  /* step 3: find all points within that distance to box */

  rp=rmap;  gp=gmap;  bp=bmap;
  for (i=0; i<num_colors; i++,rp++,gp++,bp++)
    if (*rp >> (COLOR_DEPTH-C_DEPTH) != ir  ||
	*gp >> (COLOR_DEPTH-C_DEPTH) != ig  ||
	*bp >> (COLOR_DEPTH-C_DEPTH) != ib) {

      dist = 0;

      if ((tmp = r1 - *rp)>0 || (tmp = *rp - (r1 + MAX_COLOR/C_LEN-1)) > 0 )
	dist += (tmp*tmp * R2FACT);

      if( (tmp = g1 - *gp)>0 || (tmp = *gp - (g1 + MAX_COLOR/C_LEN-1)) > 0 )
	dist += (tmp*tmp * G2FACT);

      if( (tmp = b1 - *bp)>0 || (tmp = *bp - (b1 + MAX_COLOR/C_LEN-1)) > 0 )
	dist += (tmp*tmp * B2FACT);

      if( dist < mindist ) {
	ptr->entries[ptr->num_ents][0] = i;
	ptr->entries[ptr->num_ents][1] = dist;
	++ptr->num_ents;
      }
    }


  /* sort color cells by distance, use cheap exchange sort */
  {
    int n, next_n;

    n = ptr->num_ents - 1;
    while (n>0) {
      next_n = 0;
      for (i=0; i<n; ++i) {
	if (ptr->entries[i][1] > ptr->entries[i+1][1]) {
	  tmp = ptr->entries[i][0];
	  ptr->entries[i][0] = ptr->entries[i+1][0];
	  ptr->entries[i+1][0] = tmp;
	  tmp = ptr->entries[i][1];
	  ptr->entries[i][1] = ptr->entries[i+1][1];
	  ptr->entries[i+1][1] = tmp;
	  next_n = i;
	}
      }
      n = next_n;
    }
  }
  return (ptr);
}




/***************************/
static void map_colortable()
/***************************/
{
  int    ir,ig,ib, *histp;
  CCELL *cell;

  histp  = &histogram[0][0][0];
  for (ir=0; ir<B_LEN; ir++)
    for (ig=0; ig<B_LEN; ig++)
      for (ib=0; ib<B_LEN; ib++) {
	if (*histp==0) *histp = -1;
	else {
	  int  i, j;
	  long dist, d2, tmp;
	  
	  cell = *(ColorCells +
		   ( ((ir>>(B_DEPTH-C_DEPTH)) << C_DEPTH*2)
		   + ((ig>>(B_DEPTH-C_DEPTH)) << C_DEPTH)
		   +  (ib>>(B_DEPTH-C_DEPTH)) ) );
		
	  if (cell==NULL)
	    cell = create_colorcell(ir<<(COLOR_DEPTH-B_DEPTH),
				    ig<<(COLOR_DEPTH-B_DEPTH),
				    ib<<(COLOR_DEPTH-B_DEPTH));

	  dist = 2000000000;
	  for (i=0; i<cell->num_ents && dist>cell->entries[i][1]; i++) {
	    j = cell->entries[i][0];
	    d2 = rmap[j] - (ir << (COLOR_DEPTH-B_DEPTH));
	    d2 = (d2 * d2 * R2FACT);
	    tmp = gmap[j] - (ig << (COLOR_DEPTH-B_DEPTH));
	    d2 += (tmp*tmp * G2FACT);
	    tmp = bmap[j] - (ib << (COLOR_DEPTH-B_DEPTH));
	    d2 += (tmp*tmp * B2FACT);
	    if( d2 < dist ) { dist = d2;  *histp = j; }
	  }
	}
	histp++;
      }
}



/*****************************/
static int quant_fsdither()
/*****************************/
{
  register int  *thisptr, *nextptr;
  int           *thisline, *nextline, *tmpptr;
  int            r1, g1, b1, r2, g2, b2;
  int            i, j, imax, jmax, oval;
  byte          *inptr, *outptr;
  int            lastline, lastpixel;

  imax = HIGH - 1;
  jmax = WIDE - 1;
  
  thisline = (int *) malloc(WIDE * 3 * sizeof(int));
  nextline = (int *) malloc(WIDE * 3 * sizeof(int));

  if (thisline == NULL || nextline == NULL) {
    fprintf(stderr,"%s: unable to allocate stuff for the 'dither' routine\n",
	    cmd);
    return 1;
  }


  inptr  = (byte *) pic24;
  outptr = (byte *) pic8;

  /* get first line of picture */
  for (j=WIDE * 3, tmpptr=nextline; j; j--)
    *tmpptr++ = (int) *inptr++;

  for (i=0; i<HIGH; i++) {
    /* swap thisline and nextline */
    tmpptr = thisline;  thisline = nextline;  nextline = tmpptr;
    lastline = (i==imax);

    if ((i&0x1f) == 0) WaitCursor();

    /* read in next line */
    if (!lastline)
      for (j=WIDE * 3, tmpptr=nextline; j; j--)
	*tmpptr++ = (int) *inptr++;

    /* dither this line and put it into the output picture */
    thisptr = thisline;  nextptr = nextline;

    for (j=0; j<WIDE; j++) {
      lastpixel = (j==jmax);

      r2 = *thisptr++;  g2 = *thisptr++;  b2 = *thisptr++;

      RANGE(r2, 0, MAX_COLOR-1);
      RANGE(g2, 0, MAX_COLOR-1);
      RANGE(b2, 0, MAX_COLOR-1);

      r1 = r2;  g1 = g2;  b1 = b2;

      r2 >>= (COLOR_DEPTH-B_DEPTH);
      g2 >>= (COLOR_DEPTH-B_DEPTH);
      b2 >>= (COLOR_DEPTH-B_DEPTH);

      if ( (oval=histogram[r2][g2][b2]) == -1) {
	int ci, cj;
	long dist, d2, tmp;
	CCELL *cell;

	cell = *( ColorCells + 
		( ((r2>>(B_DEPTH-C_DEPTH)) << C_DEPTH*2)
	        + ((g2>>(B_DEPTH-C_DEPTH)) << C_DEPTH )
		+  (b2>>(B_DEPTH-C_DEPTH)) ) );
	      
	if (cell==NULL) cell = create_colorcell(r1,g1,b1);

	dist = 2000000000;
	for (ci=0; ci<cell->num_ents && dist>cell->entries[ci][1]; ci++) {
	  cj = cell->entries[ci][0];
	  d2 = (rmap[cj] >> (COLOR_DEPTH-B_DEPTH)) - r2;
	  d2 = (d2*d2 * R2FACT);
	  tmp = (gmap[cj] >> (COLOR_DEPTH-B_DEPTH)) - g2;
	  d2 += (tmp*tmp * G2FACT);
	  tmp = (bmap[cj] >> (COLOR_DEPTH-B_DEPTH)) - b2;
	  d2 += (tmp*tmp * B2FACT);
	  if (d2<dist) { dist = d2;  oval = cj; }
	}
	histogram[r2][g2][b2] = oval;
      }

      *outptr++ = oval;

      r1 -= rmap[oval];  g1 -= gmap[oval];  b1 -= bmap[oval];
	
      /* don't use tables, because r1,g1,b1 could go negative */
      if (!lastpixel) {
	thisptr[0] += (r1<0) ? (r1*7-8)/16 : (r1*7+8)/16;
	thisptr[1] += (g1<0) ? (g1*7-8)/16 : (g1*7+8)/16;
	thisptr[2] += (b1<0) ? (b1*7-8)/16 : (b1*7+8)/16;
      }
	
      if (!lastline) {
	if (j) {
	  nextptr[-3] += (r1<0) ? (r1*3-8)/16 : (r1*3+8)/16;
	  nextptr[-2] += (g1<0) ? (g1*3-8)/16 : (g1*3+8)/16;
	  nextptr[-1] += (b1<0) ? (b1*3-8)/16 : (b1*3+8)/16;
	}

	nextptr[0] += (r1<0) ? (r1*5-8)/16 : (r1*5+8)/16;
	nextptr[1] += (g1<0) ? (g1*5-8)/16 : (g1*5+8)/16;
	nextptr[2] += (b1<0) ? (b1*5-8)/16 : (b1*5+8)/16;
	
	if (!lastpixel) {
	  nextptr[3] += (r1<0) ? (r1-8)/16 : (r1+8)/16;
	  nextptr[4] += (g1<0) ? (g1-8)/16 : (g1+8)/16;
	  nextptr[5] += (b1<0) ? (b1-8)/16 : (b1+8)/16;
	}
	nextptr += 3;
      }
    }
  }

  free(thisline);  free(nextline);
  return 0;
}










/************************************/
static int Quick24to8(p24,w,h)
byte *p24;
int   w,h;
{

  /* floyd-steinberg dithering.
   *
   * ----   x    7/16
   * 3/16  5/16  1/16
   *
   */

  /* called after 'pic8' has been alloced, pWIDE,pHIGH set up, mono/1-bit
     checked already */

  byte *pp;
  int  r1, g1, b1;
  int  *thisline, *nextline, *thisptr, *nextptr, *tmpptr;
  int  i, j, val, pwide3;
  int  imax, jmax;

  pp = pic8;  pwide3 = w * 3;  imax = h-1;  jmax = w-1;

  /* up to 256 colors:     3 bits R, 3 bits G, 2 bits B  (RRRGGGBB) */
#define RMASK 0xe0
#define R_SHIFT        0
#define GMASK 0xe0
#define G_SHIFT        3
#define BMASK 0xc0
#define B_SHIFT        6

  /* load up colormap */
  /* note that 0 and 255 of each color are always in the map; */
  /* intermediate values are evenly spaced. */
  for (i=0; i<256; i++) {
    rmap[i] = (((i<<R_SHIFT) & RMASK) * 255 + RMASK/2) / RMASK;
    gmap[i] = (((i<<G_SHIFT) & GMASK) * 255 + GMASK/2) / GMASK;
    bmap[i] = (((i<<B_SHIFT) & BMASK) * 255 + BMASK/2) / BMASK;
  }


  thisline = (int *) malloc(pwide3 * sizeof(int));
  nextline = (int *) malloc(pwide3 * sizeof(int));
  if (!thisline || !nextline) {
    fprintf(stderr,"%s: unable to allocate memory in Quick24to8()\n", cmd);
    return(1);
    }

  /* get first line of picture */
  for (j=pwide3, tmpptr=nextline; j; j--) *tmpptr++ = (int) *p24++;

  for (i=0; i<h; i++) {
    tmpptr = thisline;  thisline = nextline;  nextline = tmpptr;   /* swap */

    if ((i&0x1f) == 0) WaitCursor();

    if (i!=imax)   /* get next line */
      for (j=pwide3, tmpptr=nextline; j; j--)
	*tmpptr++ = (int) *p24++;

    for (j=0, thisptr=thisline, nextptr=nextline; j<w; j++,pp++) {
      r1 = *thisptr++;  g1 = *thisptr++;  b1 = *thisptr++;
      RANGE(r1,0,255);  RANGE(g1,0,255);  RANGE(b1,0,255);  

      /* choose actual pixel value */
      val = (((r1&RMASK)>>R_SHIFT) | ((g1&GMASK)>>G_SHIFT) | 
	     ((b1&BMASK)>>B_SHIFT));
      *pp = val;

      /* compute color errors */
      r1 -= rmap[val];
      g1 -= gmap[val];
      b1 -= bmap[val];

      /* Add fractions of errors to adjacent pixels */
      r1 += 256;              /* make positive for table indexing */
      g1 += 256;
      b1 += 256;

      if (j!=jmax) {  /* adjust RIGHT pixel */
	thisptr[0] += tbl7[r1];
	thisptr[1] += tbl7[g1];
	thisptr[2] += tbl7[b1];
      }
      
      if (i!=imax) {	/* do BOTTOM pixel */
	nextptr[0] += tbl5[r1];
	nextptr[1] += tbl5[g1];
	nextptr[2] += tbl5[b1];

	if (j>0) {  /* do BOTTOM LEFT pixel */
	  nextptr[-3] += tbl3[r1];
	  nextptr[-2] += tbl3[g1];
	  nextptr[-1] += tbl3[b1];
	}

	if (j!=jmax) {  /* do BOTTOM RIGHT pixel */
	  nextptr[3] += tbl1[r1];
	  nextptr[4] += tbl1[g1];
	  nextptr[5] += tbl1[b1];
	}
	nextptr += 3;
      }
    }
  }

  return 0;
}
      


/****************************/
static int QuickCheck(pic24,w,h,maxcol)
byte *pic24;
int   w,h,maxcol;
{
  /* scans picture until it finds more than 'maxcol' different colors.  If it
     finds more than 'maxcol' colors, it returns '0'.  If it DOESN'T, it does
     the 24-to-8 conversion by simply sticking the colors it found into
     a colormap, and changing instances of a color in pic24 into colormap
     indicies (in pic8) */

  unsigned long colors[256],col;
  int           i, nc, low, high, mid;
  byte         *p, *pix;

  if (maxcol>256) maxcol = 256;

  /* put the first color in the table by hand */
  nc = 0;  mid = 0;  

  for (i=w*h,p=pic24; i; i--) {
    col  = (((u_long) *p++) << 16);  
    col += (((u_long) *p++) << 8);
    col +=  *p++;

    /* binary search the 'colors' array to see if it's in there */
    low = 0;  high = nc-1;
    while (low <= high) {
      mid = (low+high)/2;
      if      (col < colors[mid]) high = mid - 1;
      else if (col > colors[mid]) low  = mid + 1;
      else break;
    }

    if (high < low) { /* didn't find color in list, add it. */
      if (nc>=maxcol) return 0;
      xvbcopy((char *) &colors[low], (char *) &colors[low+1],
	      (nc - low) * sizeof(u_long));
      colors[low] = col;
      nc++;
    }
  }


  /* run through the data a second time, this time mapping pixel values in
     pic24 into colormap offsets into 'colors' */

  for (i=w*h,p=pic24, pix=pic8; i; i--,pix++) {
    col  = (((u_long) *p++) << 16);  
    col += (((u_long) *p++) << 8);
    col +=  *p++;

    /* binary search the 'colors' array.  It *IS* in there */
    low = 0;  high = nc-1;
    while (low <= high) {
      mid = (low+high)/2;
      if      (col < colors[mid]) high = mid - 1;
      else if (col > colors[mid]) low  = mid + 1;
      else break;
    }

    if (high < low) {
      fprintf(stderr,"QuickCheck:  impossible situation!\n");
      exit(1);
    }
    *pix = mid;
  }

  /* and load up the 'desired colormap' */
  for (i=0; i<nc; i++) {
    rmap[i] =  colors[i]>>16;  
    gmap[i] = (colors[i]>>8) & 0xff;
    bmap[i] =  colors[i]     & 0xff;
  }

  return 1;
}



/***************************************************************/
byte *Conv8to24(pic8, w, h, rmap,gmap,bmap)
     byte *pic8, *rmap, *gmap, *bmap;
     int   w, h;
{
  /* converts an w*h 8-bit image (with colormap rmap,gmap,bmap) into a
   * 24-bit image.  Note, 'pic8' could be NULL
   *
   * returns pointer to new 24-bits-per-pixel image (w*h) if successful,
   * or NULL if unsuccessful
   */

  int   i;
  byte *pic24, *sp, *dp;

  pic24 = (byte *) malloc(w * h * 3);
  if (!pic24) return pic24;

  for (i=w*h, sp=pic8, dp=pic24; i; i--, sp++) {
    if ((i&0x1ffff)==0) WaitCursor();
    *dp++ = rmap[*sp];
    *dp++ = gmap[*sp];
    *dp++ = bmap[*sp];
  }

  return pic24;
}
  







/***************************************************************/
/* The following code based on code from the 'pbmplus' package */
/* written by Jef Poskanzer                                    */
/***************************************************************/


/* ppmquant.c - quantize the colors in a pixmap down to a specified number
**
** Copyright (C) 1989, 1991 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/


#undef max
#define max(a,b) ((a) > (b) ? (a) : (b))
#undef min
#define min(a,b) ((a) < (b) ? (a) : (b))
#undef abs
#define abs(a) ((a) >= 0 ? (a) : -(a))
#undef odd
#define odd(n) ((n) & 1)

typedef unsigned char pixval;

#define PPM_MAXMAXVAL 255
typedef struct { pixval r, g, b; } pixel;

#define PPM_GETR(p) ((p).r)
#define PPM_GETG(p) ((p).g)
#define PPM_GETB(p) ((p).b)

#define PPM_ASSIGN(p,red,grn,blu) \
  { (p).r = (red); (p).g = (grn); (p).b = (blu); }

#define PPM_EQUAL(p,q) ( (p).r == (q).r && (p).g == (q).g && (p).b == (q).b )


/* Color scaling macro -- to make writing ppmtowhatever easier. */

#define PPM_DEPTH(newp,p,oldmaxval,newmaxval) \
    PPM_ASSIGN( (newp), \
	        (int) PPM_GETR(p) * (newmaxval) / (oldmaxval), \
	        (int) PPM_GETG(p) * (newmaxval) / (oldmaxval), \
	        (int) PPM_GETB(p) * (newmaxval) / (oldmaxval) )


/* Luminance macro. */

/* 
 * #define PPM_LUMIN(p) \
 *   ( 0.299 * PPM_GETR(p) + 0.587 * PPM_GETG(p) + 0.114 * PPM_GETB(p) )
 */

/* Luminance macro, using only integer ops.  Returns an int (*256)  JHB */
#define PPM_LUMIN(p) \
  ( 77 * PPM_GETR(p) + 150 * PPM_GETG(p) + 29 * PPM_GETB(p) )

/* Color histogram stuff. */

typedef struct colorhist_item* colorhist_vector;
struct colorhist_item { pixel color;
			int value;
		      };

typedef struct colorhist_list_item* colorhist_list;
struct colorhist_list_item { struct colorhist_item ch;
			     colorhist_list next;
			   };

typedef colorhist_list* colorhash_table;

typedef struct box* box_vector;
struct box {
  int index;
  int colors;
  int sum;
};


#define MAXCOLORS 32767
#define CLUSTER_MAXVAL 63

#define LARGE_LUM
#define REP_AVERAGE_PIXELS

#define FS_SCALE 1024

#define HASH_SIZE 6553

#define ppm_hashpixel(p) ((((int) PPM_GETR(p) * 33023 +    \
			    (int) PPM_GETG(p) * 30013 +    \
			    (int) PPM_GETB(p) * 27011) & 0x7fffffff)   \
			  % HASH_SIZE)



/*** function defs ***/

#ifdef __STDC__
  static colorhist_vector mediancut(colorhist_vector, int, int, int, int);
  static int              redcompare  (colorhist_vector, colorhist_vector);
  static int              greencompare(colorhist_vector, colorhist_vector);
  static int              bluecompare (colorhist_vector, colorhist_vector);
  static int              sumcompare  (box_vector, box_vector);
  static colorhist_vector ppm_computecolorhist(pixel **, int,int,int,int *);
  static colorhash_table  ppm_computecolorhash(pixel **, int,int,int,int *);
  static colorhist_vector ppm_colorhashtocolorhist(colorhash_table, int);
  static colorhash_table  ppm_alloccolorhash(void);
  static void             ppm_freecolorhist(colorhist_vector);
  static void             ppm_freecolorhash(colorhash_table);
#else
  static colorhist_vector mediancut();
  static int              redcompare(), greencompare(), bluecompare();
  static int              sumcompare();
  static colorhist_vector ppm_computecolorhist(), ppm_colorhashtocolorhist();
  static colorhash_table  ppm_computecolorhash(), ppm_alloccolorhash();
  static void             ppm_freecolorhist(), ppm_freecolorhash();
#endif


/****************************************************************************/
static int ppmquant(pic24, cols, rows, newcolors)
     byte *pic24;
     int  cols, rows, newcolors;
{
  pixel**          pixels;
  register pixel*  pP;
  int              row;
  register int     col, limitcol;
  pixval           maxval, newmaxval;
  int              colors;
  register int     index;
  colorhist_vector chv, colormap;
  colorhash_table  cht;
  int              i;
  unsigned char    *picptr;
  static char      *fn = "ppmquant()";

  index = 0;
  maxval = 255;

  /*
   *  reformat 24-bit pic24 image (3 bytes per pixel) into 2-dimensional
   *  array of pixel structures
   */

  if (DEBUG) fprintf(stderr,"%s: remapping to ppm-style internal fmt\n", fn);
  WaitCursor();
  
  pixels = (pixel **) malloc(rows * sizeof(pixel *));
  if (!pixels) FatalError("couldn't allocate 'pixels' array");
  for (row=0; row<rows; row++) {
    pixels[row] = (pixel *) malloc(cols * sizeof(pixel));
    if (!pixels[row]) FatalError("couldn't allocate a row of pixels array");

    for (col=0, pP=pixels[row]; col<cols; col++, pP++) {
      pP->r = *pic24++;
      pP->g = *pic24++;
      pP->b = *pic24++;
    }
  }
  if (DEBUG) fprintf(stderr,"%s: done format remapping\n", fn);


    

  /*
   *  attempt to make a histogram of the colors, unclustered.
   *  If at first we don't succeed, lower maxval to increase color
   *  coherence and try again.  This will eventually terminate, with
   *  maxval at worst 15, since 32^3 is approximately MAXCOLORS.
   */

  WaitCursor();
  for ( ; ; ) {
    if (DEBUG) fprintf(stderr, "%s:  making histogram\n", fn);

    chv = ppm_computecolorhist(pixels, cols, rows, MAXCOLORS, &colors);
    if (chv != (colorhist_vector) 0) break;
    
    if (DEBUG) fprintf(stderr, "%s: too many colors!\n", fn);
    newmaxval = maxval / 2;
    if (DEBUG) fprintf(stderr, "%s: rescaling colors (maxval=%d) %s\n",
		       fn, newmaxval, "to improve clustering");

    for (row=0; row<rows; ++row)
      for (col=0, pP=pixels[row]; col<cols; ++col, ++pP)
	PPM_DEPTH( *pP, *pP, maxval, newmaxval );
    maxval = newmaxval;
  }

  if (DEBUG) fprintf(stderr,"%s: %d colors found\n", fn, colors);



  /*
   * Step 3: apply median-cut to histogram, making the new colormap.
   */

  WaitCursor();
  if (DEBUG) fprintf(stderr, "%s: choosing %d colors\n", fn, newcolors);
  colormap = mediancut(chv, colors, rows * cols, maxval, newcolors);
  ppm_freecolorhist(chv);



  /*
   *  Step 4: map the colors in the image to their closest match in the
   *  new colormap, and write 'em out.
   */

  if (DEBUG) fprintf(stderr,"%s: mapping image to new colors\n", fn);
  cht = ppm_alloccolorhash();

  picptr = pic8;
  for (row = 0;  row < rows;  ++row) {
    col = 0;  limitcol = cols;  pP = pixels[row];

    if ((row & 0x1f) == 0) WaitCursor();
    do {
      int hash;
      colorhist_list chl;

      /* Check hash table to see if we have already matched this color. */

      hash = ppm_hashpixel(*pP);
      for (chl = cht[hash];  chl;  chl = chl->next)
	if (PPM_EQUAL(chl->ch.color, *pP)) {index = chl->ch.value; break;}

      if (!chl /*index = -1*/) {/* No; search colormap for closest match. */
	register int i, r1, g1, b1, r2, g2, b2;
	register long dist, newdist;

	r1 = PPM_GETR( *pP );
	g1 = PPM_GETG( *pP );
	b1 = PPM_GETB( *pP );
	dist = 2000000000;

	for (i=0; i<newcolors; i++) {
	  r2 = PPM_GETR( colormap[i].color );
	  g2 = PPM_GETG( colormap[i].color );
	  b2 = PPM_GETB( colormap[i].color );

	  newdist = ( r1 - r2 ) * ( r1 - r2 ) +
	            ( g1 - g2 ) * ( g1 - g2 ) +
	            ( b1 - b2 ) * ( b1 - b2 );

	  if (newdist<dist) { index = i;  dist = newdist; }
	}

	hash = ppm_hashpixel(*pP);
	chl = (colorhist_list) malloc(sizeof(struct colorhist_list_item));
	if (!chl) FatalError("ran out of memory adding to hash table");

	chl->ch.color = *pP;
	chl->ch.value = index;
	chl->next = cht[hash];
	cht[hash] = chl;
      }

      *picptr++ = index;

      ++col;
      ++pP;
    }
    while (col != limitcol);
  }

  /* rescale the colormap and load the XV colormap */
  for (i=0; i<newcolors; i++) {
    PPM_DEPTH(colormap[i].color, colormap[i].color, maxval, 255);
    rmap[i] = PPM_GETR( colormap[i].color );
    gmap[i] = PPM_GETG( colormap[i].color );
    bmap[i] = PPM_GETB( colormap[i].color );
  }

  /* free the pixels array */
  for (i=0; i<rows; i++) free(pixels[i]);
  free(pixels);

  /* free cht and colormap */
  ppm_freecolorhist(colormap);
  ppm_freecolorhash(cht);

  return 0;
}



/*
** Here is the fun part, the median-cut colormap generator.  This is based
** on Paul Heckbert's paper "Color Image Quantization for Frame Buffer
** Display", SIGGRAPH '82 Proceedings, page 297.
*/



/****************************************************************************/
static colorhist_vector mediancut( chv, colors, sum, maxval, newcolors )
     colorhist_vector chv;
     int colors, sum, newcolors;
     int maxval;
{
  colorhist_vector colormap;
  box_vector bv;
  register int bi, i;
  int boxes;

  bv = (box_vector) malloc(sizeof(struct box) * newcolors);
  colormap = (colorhist_vector) 
             malloc(sizeof(struct colorhist_item) * newcolors );

  if (!bv || !colormap) FatalError("unable to malloc in mediancut()");

  for (i=0; i<newcolors; i++)
    PPM_ASSIGN(colormap[i].color, 0, 0, 0);

  /*
   *  Set up the initial box.
   */
  bv[0].index = 0;
  bv[0].colors = colors;
  bv[0].sum = sum;
  boxes = 1;


  /*
   ** Main loop: split boxes until we have enough.
   */

  while ( boxes < newcolors ) {
    register int indx, clrs;
    int sm;
    register int minr, maxr, ming, maxg, minb, maxb, v;
    int halfsum, lowersum;

    /*
     ** Find the first splittable box.
     */
    for (bi=0; bv[bi].colors<2 && bi<boxes; bi++) ;
    if (bi == boxes) break;	/* ran out of colors! */

    indx = bv[bi].index;
    clrs = bv[bi].colors;
    sm = bv[bi].sum;

    /*
     ** Go through the box finding the minimum and maximum of each
     ** component - the boundaries of the box.
     */
    minr = maxr = PPM_GETR( chv[indx].color );
    ming = maxg = PPM_GETG( chv[indx].color );
    minb = maxb = PPM_GETB( chv[indx].color );

    for (i=1; i<clrs; i++) {
      v = PPM_GETR( chv[indx + i].color );
      if (v < minr) minr = v;
      if (v > maxr) maxr = v;

      v = PPM_GETG( chv[indx + i].color );
      if (v < ming) ming = v;
      if (v > maxg) maxg = v;

      v = PPM_GETB( chv[indx + i].color );
      if (v < minb) minb = v;
      if (v > maxb) maxb = v;
    }

    /*
     ** Find the largest dimension, and sort by that component.  I have
     ** included two methods for determining the "largest" dimension;
     ** first by simply comparing the range in RGB space, and second
     ** by transforming into luminosities before the comparison.  You
     ** can switch which method is used by switching the commenting on
     ** the LARGE_ defines at the beginning of this source file.
     */
    {
      /* LARGE_LUM version */

      pixel p;
      int rl, gl, bl;

      PPM_ASSIGN(p, maxr - minr, 0, 0);
      rl = PPM_LUMIN(p);

      PPM_ASSIGN(p, 0, maxg - ming, 0);
      gl = PPM_LUMIN(p);

      PPM_ASSIGN(p, 0, 0, maxb - minb);
      bl = PPM_LUMIN(p);

      if (rl >= gl && rl >= bl)
	qsort( (char*) &(chv[indx]), clrs, sizeof(struct colorhist_item),
	      redcompare );
      else if (gl >= bl)
	qsort( (char*) &(chv[indx]), clrs, sizeof(struct colorhist_item),
	      greencompare );
      else qsort( (char*) &(chv[indx]), clrs, sizeof(struct colorhist_item),
		 bluecompare );
    }

    /*
     ** Now find the median based on the counts, so that about half the
     ** pixels (not colors, pixels) are in each subdivision.
     */
    lowersum = chv[indx].value;
    halfsum = sm / 2;
    for (i=1; i<clrs-1; i++) {
      if (lowersum >= halfsum) break;
      lowersum += chv[indx + i].value;
    }

    /*
     ** Split the box, and sort to bring the biggest boxes to the top.
     */
    bv[bi].colors = i;
    bv[bi].sum = lowersum;
    bv[boxes].index = indx + i;
    bv[boxes].colors = clrs - i;
    bv[boxes].sum = sm - lowersum;
    ++boxes;
    qsort( (char*) bv, boxes, sizeof(struct box), sumcompare );
  }  /* while (boxes ... */

  /*
   ** Ok, we've got enough boxes.  Now choose a representative color for
   ** each box.  There are a number of possible ways to make this choice.
   ** One would be to choose the center of the box; this ignores any structure
   ** within the boxes.  Another method would be to average all the colors in
   ** the box - this is the method specified in Heckbert's paper.  A third
   ** method is to average all the pixels in the box.  You can switch which
   ** method is used by switching the commenting on the REP_ defines at
   ** the beginning of this source file.
   */
  
  for (bi=0; bi<boxes; bi++) {
    /* REP_AVERAGE_PIXELS version */
    register int indx = bv[bi].index;
    register int clrs = bv[bi].colors;
    register long r = 0, g = 0, b = 0, sum = 0;

    for (i=0; i<clrs; i++) {
      r += PPM_GETR( chv[indx + i].color ) * chv[indx + i].value;
      g += PPM_GETG( chv[indx + i].color ) * chv[indx + i].value;
      b += PPM_GETB( chv[indx + i].color ) * chv[indx + i].value;
      sum += chv[indx + i].value;
    }

    r = r / sum;  if (r>maxval) r = maxval;	/* avoid math errors */
    g = g / sum;  if (g>maxval) g = maxval;
    b = b / sum;  if (b>maxval) b = maxval;

    PPM_ASSIGN( colormap[bi].color, r, g, b );
  }

  free(bv);
  return colormap;
}


/**********************************/
static int redcompare(ch1, ch2)
     colorhist_vector ch1, ch2;
{
  return (int) PPM_GETR( ch1->color ) - (int) PPM_GETR( ch2->color );
}

/**********************************/
static int greencompare(ch1, ch2)
     colorhist_vector ch1, ch2;
{
  return (int) PPM_GETG( ch1->color ) - (int) PPM_GETG( ch2->color );
}

/**********************************/
static int bluecompare( ch1, ch2 )
     colorhist_vector ch1, ch2;
{
  return (int) PPM_GETB( ch1->color ) - (int) PPM_GETB( ch2->color );
}

/**********************************/
static int sumcompare( b1, b2 )
     box_vector b1, b2;
{
  return b2->sum - b1->sum;
}



/****************************************************************************/
static colorhist_vector 
  ppm_computecolorhist(pixels, cols, rows, maxcolors, colorsP)
     pixel** pixels;
     int cols, rows, maxcolors;
     int* colorsP;
{
  colorhash_table cht;
  colorhist_vector chv;

  cht = ppm_computecolorhash(pixels, cols, rows, maxcolors, colorsP);
  if (!cht) return (colorhist_vector) 0;

  chv = ppm_colorhashtocolorhist(cht, maxcolors);
  ppm_freecolorhash(cht);
  return chv;
}


/****************************************************************************/
static colorhash_table ppm_computecolorhash(pixels, cols, rows, 
					    maxcolors, colorsP )
     pixel** pixels;
     int cols, rows, maxcolors;
     int* colorsP;
{
  colorhash_table cht;
  register pixel* pP;
  colorhist_list chl;
  int col, row, hash;

  cht = ppm_alloccolorhash( );
  *colorsP = 0;

  /* Go through the entire image, building a hash table of colors. */
  for (row=0; row<rows; row++)
    for (col=0, pP=pixels[row];  col<cols;  col++, pP++) {
      hash = ppm_hashpixel(*pP);

      for (chl = cht[hash]; chl != (colorhist_list) 0; chl = chl->next)
	if (PPM_EQUAL(chl->ch.color, *pP)) break;
      
      if (chl != (colorhist_list) 0) ++(chl->ch.value);
      else {
	if ((*colorsP)++ > maxcolors) {
	  ppm_freecolorhash(cht);
	  return (colorhash_table) 0;
	}
	
	chl = (colorhist_list) malloc(sizeof(struct colorhist_list_item));
	if (!chl) FatalError("ran out of memory computing hash table");

	chl->ch.color = *pP;
	chl->ch.value = 1;
	chl->next = cht[hash];
	cht[hash] = chl;
      }
    }
  
  return cht;
}


/****************************************************************************/
static colorhash_table ppm_alloccolorhash()
{
  colorhash_table cht;
  int i;

  cht = (colorhash_table) malloc( HASH_SIZE * sizeof(colorhist_list) );
  if (!cht) FatalError("ran out of memory allocating hash table");

  for (i=0; i<HASH_SIZE; i++ )
    cht[i] = (colorhist_list) 0;

  return cht;
}


/****************************************************************************/
static colorhist_vector ppm_colorhashtocolorhist( cht, maxcolors )
     colorhash_table cht;
     int maxcolors;
{
  colorhist_vector chv;
  colorhist_list chl;
  int i, j;

  /* Now collate the hash table into a simple colorhist array. */
  chv = (colorhist_vector) malloc( maxcolors * sizeof(struct colorhist_item) );

  /* (Leave room for expansion by caller.) */
  if (!chv) FatalError("ran out of memory generating histogram");

  /* Loop through the hash table. */
  j = 0;
  for (i=0; i<HASH_SIZE; i++)
    for (chl = cht[i];  chl != (colorhist_list) 0;  chl = chl->next) {
      /* Add the new entry. */
      chv[j] = chl->ch;
      ++j;
    }

  return chv;
}


/****************************************************************************/
static void ppm_freecolorhist( chv )
     colorhist_vector chv;
{
  free( (char*) chv );
}


/****************************************************************************/
static void ppm_freecolorhash( cht )
     colorhash_table cht;
{
  int i;
  colorhist_list chl, chlnext;

  for (i=0; i<HASH_SIZE; i++)
    for (chl = cht[i];  chl != (colorhist_list) 0; chl = chlnext) {
      chlnext = chl->next;
      free( (char*) chl );
    }

  free( (char*) cht );
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.