[comp.sources.x] v09i079: XV -- successor to XGIF, Part01/01

bradley@grip.cis.upenn.edu (John Bradley) (10/11/90)

Submitted-by: bradley@grip.cis.upenn.edu (John Bradley)
Posting-number: Volume 9, Issue 79
Archive-name: xv/part01


-----------------------(cut here)---------------------------
#!/bin/sh
# to extract, remove the header and type "sh filename"
if `test ! -s ./xv24to8.c`
then
echo "writting ./xv24to8.c"
cat > ./xv24to8.c << '\BARFOO\'
/*
 * xv24to8.c  -  contains the 24-to-8-bit Conv24to8 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)
 *
 * The Conv24to8 procedure will set up the following:  it will allocate and
 * create 'pic', a pWIDE*pHIGH 8-bit picture.  it will set up pWIDE and pHIGH.
 * it will load up the r[], g[], and b[] colormap arrays.  it will NOT 
 * calculate numcols, since the cmap sort procedure has to be called anyway
 *
 * Conv24to8 returns '0' if successful, '1' if it failed (presumably on a 
 * malloc())
 *
 * contains:
 *   Cont24to8()
 *   InitFSDTables()
 */

/*
 * Copyright 1989, 1990 by the University of Pennsylvania
 *
 * Permission to use, copy, and distribute for non-commercial purposes,
 * is hereby granted without fee, providing that the above copyright
 * notice appear in all copies and that both the copyright notice and this
 * permission notice appear in supporting documentation.
 *
 * The software may be modified for your own purposes, but modified versions
 * may not be distributed.
 *
 * This software is provided "as is" without any express or implied warranty.
 */

#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		2
#define	C_LEN		(1<<C_DEPTH)	/* # cells/color to use */

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 *pic24;
static int   num_colors, WIDE, HIGH;
static int   histogram[B_LEN][B_LEN][B_LEN];

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

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 byte   tbl1[256],     /* tables used in F-S Dithering */
              tbl3[256],     /* contain i/16, 3i/16, 5i/16, 7i/16, */
              tbl5[256],     /* (i=0-255) respectively */
              tbl7[256];


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

  /* copy arguments to local-global variables */
  pic24 = p;  pWIDE = WIDE = w;  pHIGH = HIGH = h;  num_colors = nc;


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

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


  /* quick check:  if we're going to display a greyscale or 1-bit image,
     DON'T run this annoyingly time-consuming code.  Just convert the 24-bit
     color image to an 8-bit greyscale.  This takes virtually no time, by
     comparision, and it has the same effect. */

  if (mono || nc==0) {
    byte *pp, *p24;

    for (i=0; i<256; i++) r[i]=g[i]=b[i]=i;   /* greyscale colormap */
    pp = pic;  p24 = pic24;
    for (i=WIDE*HIGH; i>0; i--, pp++, p24+=3) 
      *pp = (p24[0]*11 + p24[1]*16 + p24[2]*5) >> 5;  /* pp=.33R+.5G+.17B */

    return(0);
  }

  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 0;   
  }
  else if (!slow24) {
    SetISTR(ISTR_INFO,"Doing quick 24-bit to 8-bit conversion.");
    return(Quick24to8(pic24,w,h));
  }
  else 
    SetISTR(ISTR_INFO,"Doing full 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);
    return(1);
  }

  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, &r[i], &g[i], &b[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 */
  free(ColorCells);

  return i;
}


/****************************/
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;
/*******************************/
{
  *rp = ((ptr->rmin + ptr->rmax) << (COLOR_DEPTH - B_DEPTH)) / 2;
  *gp = ((ptr->gmin + ptr->gmax) << (COLOR_DEPTH - B_DEPTH)) / 2;
  *bp = ((ptr->bmin + ptr->bmax) << (COLOR_DEPTH - B_DEPTH)) / 2;
}



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

  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 = 99999999;

  rp=r;  gp=g;  bp=b;
  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;

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

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

      if (dist < mindist) mindist = dist;
    }


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

  rp=r;  gp=g;  bp=b;
  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;

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

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

      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, tmp, d2, dist;
	  
	  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 = 9999999;
	  for (i=0; i<cell->num_ents && dist>cell->entries[i][1]; i++) {
	    j = cell->entries[i][0];
	    d2 = r[j] - (ir << (COLOR_DEPTH-B_DEPTH));
	    d2 *= d2;
	    tmp = g[j] - (ig << (COLOR_DEPTH-B_DEPTH));
	    d2 += tmp*tmp;
	    tmp = b[j] - (ib << (COLOR_DEPTH-B_DEPTH));
	    d2 += tmp*tmp;
	    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, *tmpbptr;
  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 *) pic;

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

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

    /* read in next line */
    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++;

      if (r2<0) r2=0;  else if (r2>=MAX_COLOR) r2=MAX_COLOR-1;
      if (g2<0) g2=0;  else if (g2>=MAX_COLOR) g2=MAX_COLOR-1;
      if (b2<0) b2=0;  else if (b2>=MAX_COLOR) b2=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, 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 = 9999999;
	for (ci=0; ci<cell->num_ents && dist>cell->entries[ci][1]; ci++) {
	  cj = cell->entries[ci][0];
	  d2 = (r[cj] >> (COLOR_DEPTH-B_DEPTH)) - r2;
	  d2 *= d2;
	  tmp = (g[cj] >> (COLOR_DEPTH-B_DEPTH)) - g2;
	  d2 += tmp*tmp;
	  tmp = (b[cj] >> (COLOR_DEPTH-B_DEPTH)) - b2;
	  d2 += tmp*tmp;
	  if (d2<dist) { dist = d2;  oval = cj; }
	}
	histogram[r2][g2][b2] = oval;
      }

      *outptr++ = oval;

      r1 -= r[oval];  g1 -= g[oval];  b1 -= b[oval];
      /* can't use tables because r1,g1,b1 go negative */

      if (!lastpixel) {
	thisptr[0] += (r1*7)/16;
	thisptr[1] += (g1*7)/16;
	thisptr[2] += (b1*7)/16;
      }

      if (!lastline) {
	if (j) {
	  nextptr[-3] += (r1*3)/16;
	  nextptr[-2] += (g1*3)/16;
	  nextptr[-1] += (b1*3)/16;
	}

	nextptr[0] += (r1*5)/16;
	nextptr[1] += (g1*5)/16;
	nextptr[2] += (b1*5)/16;

	if (!lastpixel) {
	  nextptr[3] += r1/16;
	  nextptr[4] += g1/16;
	  nextptr[5] += b1/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 'pic' 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, rerr, gerr, berr, pwide3;
  int  imax, jmax;

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

  /* load up colormap, 3 bits R, 3 bits G, 2 bits B  (RRRGGGBB) */

#ifdef OLDCOLORS
  for (i=0; i<256; i++) {
    r[i] =  i & 0xe0;  
    g[i] = (i & 0x1c) << 3;  
    b[i] = (i & 0x03) << 6;
  }
#else
  for (i=0; i<256; i++) {
    r[i] =  ((i&0xe0) * 255) / 0xe0;  
    g[i] =  ((i&0x1c) * 255) / 0x1c;
    b[i] =  ((i&0x03) * 255) / 0x03;
  }
#endif

  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!=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);  

      rerr = r1 & 0x1f;  gerr = g1 & 0x1f;  berr = b1 & 0x3f;
      *pp = (r1&0xe0) | ((g1>>3)&0x1c) | (b1>>6); 

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

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

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

  return 0;
}
      


/****************************/
void InitFSDTables()
/****************************/
{
  int i;
  for (i=0; i<256; i++) {     /* initialize Floyd-Steinberg division tables */
    tbl1[i] = i/16;
    tbl3[i] = (3*i)/16;
    tbl5[i] = (5*i)/16;
    tbl7[i] = (7*i)/16;
  }
}




/****************************/
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 pic) */

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

  if (maxcol>256) maxcol = 256;

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

  for (i=w*h,p=pic24; i; i--) {
    col  = (*p++ << 16);  
    col += (*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. */
      /* WARNING: this is an overlapped memory copy.  memcpy doesn't do
	 it correctly, hence 'bcopy', which claims to */
      bcopy(&colors[low], &colors[low+1], (nc - low) * sizeof(unsigned long));
      colors[low] = col;
      nc++;
      if (nc>maxcol) return 0;
    }
  }


  /* 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=pic; i; i--,pix++) {
    col  = (*p++ << 16);  
    col += (*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!\n");
      exit(1);
    }
    *pix = mid;
  }

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

  return 1;
}
\BARFOO\
else
  echo "will not over write ./xv24to8.c"
fi
if `test ! -s ./xvbutt.c`
then
echo "writting ./xvbutt.c"
cat > ./xvbutt.c << '\BARFOO\'
/* 
 * xvbutt.c - regular and 'radio' pushbuttons
 *
 * callable functions:
 *
 *   BTCreate()             -  create a button
 *   BTSetActive()          -  change 'active' status of button
 *   BTRedraw()             -  redraw button
 *   BTTrack()              -  clicked in button.  track until mouse up
 *
 *   RBCreate()             -  create an RBUTT and append to supplied list
 *   RBRedraw()             -  redraw one or all RBUTTs in a list
 *   RBSelect()             -  change selected item in list of RBUTTs
 *   RBWhich()              -  returns index of selected RBUTT in list
 *   RBCount()              -  returns # of RBUTTs in list
 *   RBSetActive()          -  sets active status of an RBUTT
 *   RBClick()              -  finds clicked-on rb in a list
 *   RBTrack()              -  tracks rb after click, until release
 */

/*
 * Copyright 1989, 1990 by the University of Pennsylvania
 *
 * Permission to use, copy, and distribute for non-commercial purposes,
 * is hereby granted without fee, providing that the above copyright
 * notice appear in all copies and that both the copyright notice and this
 * permission notice appear in supporting documentation.
 *
 * The software may be modified for your own purposes, but modified versions
 * may not be distributed.
 *
 * This software is provided "as is" without any express or implied warranty.
 */


#include "xv.h"
#include "bitmaps.h"

static int    rbpixmade = 0;
static Pixmap rbon, rboff, rbon1, rboff1;

#ifdef __STDC__
static void drawRB(RBUTT *);
#else
static void drawRB();
#endif

/**********************************************/
void BTCreate(bp,win,x,y,w,h,str,fg,bg)
BUTT         *bp;
Window        win;
int           x,y,w,h;
char         *str;
unsigned long fg,bg;
{
  bp->win = win;
  bp->x = x;  bp->y = y;  bp->w = w;  bp->h = h;
  bp->str = str;
  bp->fg = fg;  bp->bg = bg;
  bp->lit = 0;
  bp->active = 1;
  bp->toggle = 0;
}



/**********************************************/
void BTSetActive(bp,act)
BUTT         *bp;
int           act;
{
  if (bp->active != act) {
    bp->active = act;
    BTRedraw(bp);
  }
}



/**********************************************/
void BTRedraw(bp)
BUTT *bp;
{
  int x,y,w,h,r;
  XPoint poly[9];

  x = bp->x;  y=bp->y;  w=bp->w;  h=bp->h;  r=3;

  /* set up the polygon */
  poly[0].x = x;      poly[0].y = y+r;
  poly[1].x = x;      poly[1].y = y+h-r;
  poly[2].x = x+r;    poly[2].y = y+h;
  poly[3].x = x+w-r;  poly[3].y = y+h;
  poly[4].x = x+w;    poly[4].y = y+h-r;
  poly[5].x = x+w;    poly[5].y = y+r;
  poly[6].x = x+w-r;  poly[6].y = y;
  poly[7].x = x+r;    poly[7].y = y;
  poly[8].x = x;      poly[8].y = y+r;


  if (!bp->active) bp->lit = 0;   /* sanity assertion */

  if (bp->lit) XSetForeground(theDisp, theGC, bp->fg);
          else XSetForeground(theDisp, theGC, bp->bg);
  XFillPolygon(theDisp,bp->win,theGC,poly,9,Convex,CoordModeOrigin);

  XSetForeground(theDisp, theGC, bp->fg);
  XDrawLines(theDisp,bp->win,theGC,poly,9,CoordModeOrigin);

  if (!bp->active) {   /* stipple the text if not active */
    XSetFillStyle(theDisp, theGC, FillStippled);
    XSetStipple(theDisp, theGC, grayStip);
  }

  if (bp->lit) XSetForeground(theDisp, theGC, bp->bg);
          else XSetForeground(theDisp, theGC, bp->fg);
  XDrawString(theDisp, bp->win, theGC, CENTERX(mfinfo,x+w/2,bp->str),
	      CENTERY(mfinfo,y+h/2), bp->str, strlen(bp->str));

  if (!bp->active) XSetFillStyle(theDisp,theGC,FillSolid);  /* back to norm */
}



/**********************************************/
int BTTrack(bp)
BUTT *bp;
{
  /* called when we've gotten a click inside 'bp'.  returns 1 if button
     was still selected lit when mouse was released. */

  Window       rW, cW;
  int          x, y, rx, ry, rval, inval;
  unsigned int mask;

  if (!bp->active) return 0;   /* inactive button */

  bp->lit = !bp->lit;
  inval = bp->lit;

  BTRedraw(bp);  XFlush(theDisp);
  Timer(75);  /* long enough for turn on to be visible */

  while (XQueryPointer(theDisp,bp->win,&rW,&cW,&rx,&ry,&x,&y,&mask)) {
    if (!(mask & Button1Mask)) break;    /* button released */

    if (bp->lit!=inval && PTINRECT(x, y, bp->x, bp->y, bp->w, bp->h)) {
      bp->lit = inval;  BTRedraw(bp);  XFlush(theDisp);
    }
    
    if (bp->lit==inval && !PTINRECT(x, y, bp->x, bp->y, bp->w, bp->h)) {
      bp->lit = !inval;  BTRedraw(bp);  XFlush(theDisp);
    }
  }

  rval = (bp->lit == inval);
  
  if (bp->lit && !bp->toggle) 
    { bp->lit = 0;  BTRedraw(bp);  XFlush(theDisp); }

  return(rval);
}








/***********************************************/
RBUTT *RBCreate(rblist, win, x,y,str, fg, bg)
      RBUTT        *rblist;
      Window        win;
      int           x,y;
      char         *str;
      unsigned long fg,bg;
{
  /* mallocs an RBUTT, fills in the fields, and appends it to rblist
     if rblist is NULL, this is the first rb in the list.  It will
     be made the 'selected' one 

     Note: no need to check return status.  It'll fatal error if it 
     can't malloc */

  RBUTT *rb, *rbptr;

  rb = (RBUTT *) malloc(sizeof(RBUTT));
  if (!rb) FatalError("couldn't malloc RBUTT");

  /* fill in the fields of the structure */
  rb->win      = win;
  rb->x        = x;
  rb->y        = y;
  rb->str      = str;
  rb->selected = 0;
  rb->active   = 1;
  rb->next     = (RBUTT *) NULL;
  rb->fg       = fg;
  rb->bg       = bg;

  if (rblist) {            /* append to end of list */
    rbptr = rblist;
    while (rbptr->next) rbptr = rbptr->next;
    rbptr->next = rb;
  }
  else {                   /* this is the first one in the list.  select it */
    rb->selected = 1;
  }


  /* and, on an unrelated note, if the RB pixmaps haven't been created yet,
     do so.  We'll be needing them, y'see... */

  if (!rbpixmade) {
    rbon  = XCreatePixmapFromBitmapData(theDisp, rootW, rb_on_bits,
	     rb_on_width, rb_on_height, fg, bg, dispDEEP);

    rboff = XCreatePixmapFromBitmapData(theDisp, rootW, rb_off_bits,
	     rb_off_width, rb_off_height, fg, bg, dispDEEP);
    rbon1 = XCreatePixmapFromBitmapData(theDisp, rootW, rb_on1_bits,
	     rb_on1_width, rb_on1_height, fg, bg, dispDEEP);
    rboff1= XCreatePixmapFromBitmapData(theDisp, rootW, rb_off1_bits,
	     rb_off1_width, rb_off1_height, fg, bg, dispDEEP);

    rbpixmade = 1;
  }

  return(rb);
}
  



/***********************************************/
void RBRedraw(rblist, num)
RBUTT *rblist;
int    num;
{
  /* redraws the 'num-th' RB in the list.  if num < 0, redraws entire list */

  RBUTT *rb;
  int    i;

  /* point 'rb' at the appropriate RBUTT, *if* we're not drawing entire list */
  if (num>=0) {
    i=0;  rb=rblist;
    while (i!=num && rb) { rb = rb->next;  i++; }
    if (!rb) return;                     /* num is out of range.  do nothing */
    drawRB(rb);
  }

  else {                                 /* draw entire list */
    rb = rblist;
    while (rb) {
      drawRB(rb);
      rb = rb->next;
    }
  }
}


static void drawRB(rb)
RBUTT *rb;
{
  /* draws the rb being pointed at */

  if (!rb) return;  /* rb = NULL */

  XSetForeground(theDisp, theGC, rb->fg);
  XSetBackground(theDisp, theGC, rb->bg);

  if (rb->selected) 
    XCopyArea(theDisp, rbon, rb->win, theGC, 0, 0, rb_on_width, rb_on_height, 
	      rb->x, rb->y);
  else
    XCopyArea(theDisp, rboff, rb->win, theGC, 0, 0, rb_on_width, rb_on_height, 
	      rb->x, rb->y);

  XDrawString(theDisp, rb->win, theGC, rb->x+rb_on_width+4, 
	      rb->y+rb_on_height/2 - CHIGH/2 + ASCENT,rb->str,strlen(rb->str));

  /* if non-active, dim button and string */
  if (!rb->active) { 
    /* stipple the RB by drawing 'bg' where there's 1's in the stipple */
    XSetFillStyle(theDisp, theGC, FillStippled);
    XSetStipple(theDisp, theGC, grayStip);
    XSetForeground(theDisp, theGC, rb->bg);
    XFillRectangle(theDisp,rb->win,theGC,rb->x,rb->y,rb_on_width,rb_on_height);
    XFillRectangle(theDisp,rb->win,theGC, rb->x + rb_on_width+4,
		   rb->y+rb_on_height/2 - CHIGH/2, StringWidth(rb->str),CHIGH);
    XSetFillStyle(theDisp, theGC, FillSolid);
  }
}


/***********************************************/
void RBSelect(rblist, n)
RBUTT *rblist;
int    n;
{
  RBUTT *rbold, *rb;
  int    i;

  /* makes rb #n the selected rb in the list.  Does all redrawing.  Does
     nothing if rb already selected */

  /* get pointers to the currently selected rb and the desired rb */
  rbold = rblist;
  while (rbold && !rbold->selected) rbold = rbold->next;
  if (!rbold) return;    /* no currently selected item.  shouldn't happen */

  rb = rblist;  i=0;
  while (rb && i!=n) {rb = rb->next;  i++; }
  if (!rb) return;    /* 'n' is out of range */


  if (rb == rbold) return;   /* 'n' is already selected.  do nothing */

  rbold->selected = 0;
  rb->selected    = 1;
  drawRB(rbold);
  drawRB(rb);
}


	      
/***********************************************/
int RBWhich(rblist)
RBUTT *rblist;
{
  int i;

  /* returns index of currently selected rb.  if none, returns -1 */

  i = 0;
  while (rblist && !rblist->selected) { rblist = rblist->next;  i++; }

  if (!rblist) return -1;             /* didn't find one */
  return i;
}


/***********************************************/
int RBCount(rblist)
RBUTT *rblist;
{
  int i;

  /* returns # of rb's in the list */

  i = 0;
  while (rblist) { rblist = rblist->next; i++; }
  return i;
}


/***********************************************/
void RBSetActive(rblist, n, act)
RBUTT *rblist;
int n,act;
{
  RBUTT *rb;
  int    i;

  /* sets 'active' status of rb #n.  does redrawing */

  rb=rblist;  i=0;
  while (rb && i!=n) { rb = rb->next; i++; }
  if (!rb) return;                         /* n out of range.  do nothing */

  if (rb->active != act) {
    rb->active = act;
    drawRB(rb);
  }
}


/***********************************************/
int RBClick(rblist, mx, my)
RBUTT *rblist;
int    mx,my;
{
  int i;

  /* searches through rblist to see if mouse click at mx,my is in the
     clickable region of any of the rb's.  If it finds one, it returns 
     it's index in the list.  If not, returns -1 */

  i = 0;
  while (rblist) {
    if (PTINRECT(mx, my, rblist->x, rblist->y, rb_on_width, rb_on_height)) 
      break;
    rblist = rblist->next;
    i++;
  }

  if (!rblist) return -1;
  return(i);
}


/***********************************************/
void RBTrack(rblist, n)
RBUTT *rblist;
int    n;
{
  RBUTT       *rb;
  Window       rW, cW;
  int          i, x, y, rx, ry, lit;
  unsigned int mask;
  Pixmap litpix, darkpix;

  rb=rblist;  i=0;
  while (rb && i!=n) { rb = rb->next; i++; }
  if (!rb) return;                    /* n out of range */

  /* called once we've figured out that the mouse clicked in 'rb' */

  if (!rb->active) return;

  if (rb->selected) { litpix = rbon1;   darkpix = rbon; }
               else { litpix = rboff1;  darkpix = rboff; }

  lit = 1;
  XCopyArea(theDisp, litpix, rb->win, theGC, 0, 0, rb_on_width, rb_on_height, 
	      rb->x, rb->y);
  XFlush(theDisp);
  Timer(75);          /* give chance for 'turn on' to become visible */

  while (XQueryPointer(theDisp,rb->win,&rW,&cW,&rx,&ry,&x,&y,&mask)) {
    if (!(mask & Button1Mask)) break;    /* button released */

    if (!lit && PTINRECT(x, y, rb->x, rb->y, rb_on_width, rb_on_height)) {
      lit=1;
      XCopyArea(theDisp, litpix, rb->win, theGC, 0,0,rb_on_width,rb_on_height, 
	      rb->x, rb->y);
      XFlush(theDisp);
    }
    
    if (lit && !PTINRECT(x, y, rb->x, rb->y, rb_on_width, rb_on_height)) {
      lit=0;
      XCopyArea(theDisp, darkpix,rb->win,theGC, 0,0,rb_on_width,rb_on_height, 
	      rb->x, rb->y);
      XFlush(theDisp);
    }
  }

  if (lit) {
    XCopyArea(theDisp, darkpix, rb->win, theGC, 0, 0, 
	      rb_on_width, rb_on_height, rb->x, rb->y);
    RBSelect(rblist, n);
  }

  XFlush(theDisp);
}
\BARFOO\
else
  echo "will not over write ./xvbutt.c"
fi
if `test ! -s ./xvctrl.c`
then
echo "writting ./xvctrl.c"
cat > ./xvctrl.c << '\BARFOO\'
/* 
 * xvctrl.c - Control box handling functions
 *
 * callable functions:
 *
 *   CreateCtrl(geom)       -  creates the ctrlW window.  Doesn't map it.
 *   CtrlBox(vis)           -  random processing based on value of 'vis'
 *                             maps/unmaps window, etc.
 *   RedrawCtrl(x,y,w,h)    -  called by 'expose' events
 *   ClickCtrl(x,y)
 *   DrawCtrlStr()          -  called to redraw 'ISTR_INFO' string in ctrlW
 *   ScrollToCurrent()      -  called when 'curname' is changed 
 *
 *   LSCreate()             -  creates a listbox
 *   LSRedraw()             -  redraws 'namelist' box
 *   LSClick()              -  operates list box
 *   LSNewData()            -  called when strings or number of them change
 *
 */

/*
 * Copyright 1989, 1990 by the University of Pennsylvania
 *
 * Permission to use, copy, and distribute for non-commercial purposes,
 * is hereby granted without fee, providing that the above copyright
 * notice appear in all copies and that both the copyright notice and this
 * permission notice appear in supporting documentation.
 *
 * The software may be modified for your own purposes, but modified versions
 * may not be distributed.
 *
 * This software is provided "as is" without any express or implied warranty.
 */


#include "xv.h"
#include "bitmaps.h"

#define DBLCLKTIME 500             /* double-click speed in milliseconds */

#define INACTIVE(lptr, item) ((lptr)->filetypes && (lptr)->dirsonly && \
			      (item) >= 0 && (item) < (lptr)->nstr && \
			      (lptr)->str[(item)][0] != C_DIR && \
			      (lptr)->str[(item)][0] != C_LNK)

#define NLINES 9                   /* # of lines in list control (keep odd) */
#define LISTW   330

#define BUTTW   60
#define BUTTH   19

static int    listh;               /* height of list/scrl controls */
static int    ptop;                /* y-coord of top of button area in ctrlW */

static Pixmap fifoPix, chrPix, dirPix, blkPix, lnkPix, sockPix, regPix;

#ifdef __STDC__
static void drawSel(LIST *, int);
static void RedrawNList(void);
#else
static void drawSel(), RedrawNList();
#endif


/***************************************************/
void CreateCtrl(geom)
char *geom;
{
  int i;

  ctrlW = CreateWindow("xv controls", geom, CTRLWIDE,CTRLHIGH,infofg,infobg);
  if (!ctrlW) FatalError("can't create controls window!");

  grayTile = XCreatePixmapFromBitmapData(theDisp, ctrlW, gray25_bits,
	     gray25_width, gray25_height, infofg, infobg, dispDEEP);

  grayStip = XCreatePixmapFromBitmapData(theDisp, ctrlW, gray50_bits,
	     gray50_width, gray50_height, 1, 0, 1);
  
  fifoPix  = XCreatePixmapFromBitmapData(theDisp, ctrlW, i_fifo_bits,
	     i_fifo_width, i_fifo_height, 1, 0, 1);
  
  chrPix  = XCreatePixmapFromBitmapData(theDisp, ctrlW, i_chr_bits,
	     i_chr_width, i_chr_height, 1,0,1);
  
  dirPix  = XCreatePixmapFromBitmapData(theDisp, ctrlW, i_dir_bits,
	     i_dir_width, i_dir_height, 1,0,1);
  
  blkPix  = XCreatePixmapFromBitmapData(theDisp, ctrlW, i_blk_bits,
	     i_blk_width, i_blk_height, 1,0,1);
  
  lnkPix  = XCreatePixmapFromBitmapData(theDisp, ctrlW, i_lnk_bits,
	     i_lnk_width, i_lnk_height, 1,0,1);
  
  sockPix  = XCreatePixmapFromBitmapData(theDisp, ctrlW, i_sock_bits,
	     i_sock_width, i_sock_height, 1,0,1);
  
  regPix  = XCreatePixmapFromBitmapData(theDisp, ctrlW, i_reg_bits,
	     i_reg_width, i_reg_height, 1,0,1);
  

  XSetWindowBackgroundPixmap(theDisp, ctrlW, grayTile);

  /* create doo-wahs */
  listh = LINEHIGH * NLINES;

  LSCreate(&nList, ctrlW, 10, 10+CHIGH+3, LISTW, listh, NLINES, dispnames, 
	   numnames, infofg, infobg, RedrawNList, 0, 0);

  ptop = CTRLHIGH - (2*BUTTH + 3*8);

  i = listh-BUTTH;

  BTCreate(&but[BNEXT], ctrlW, 368, nList.y+(i*0)/4, 60, 
	   BUTTH, "Next",     infofg, infobg);
  BTCreate(&but[BPREV], ctrlW, 368, nList.y+(i*1)/4, 60, 
	   BUTTH, "Previous", infofg, infobg);
  BTCreate(&but[BINFO], ctrlW, 368, nList.y+(i*2)/4, 60, 
	   BUTTH, "Info", infofg, infobg);
  BTCreate(&but[BSAVE], ctrlW, 368, nList.y+(i*3)/4, 60, 
	   BUTTH, "Save", infofg, infobg);
  BTCreate(&but[BQUIT], ctrlW, 368, nList.y+(i*4)/4, 60, 
	   BUTTH, "Quit", infofg, infobg);

  BTCreate(&but[BCROP], ctrlW, 10, ptop+8, 
	   BUTTW, BUTTH, "Crop", infofg, infobg);
  BTCreate(&but[BUNCROP], ctrlW, 10, ptop + BUTTH + 2*8, 
	   BUTTW, BUTTH, "UnCrop", infofg, infobg);

  BTCreate(&but[BMAX], ctrlW, 10+(CTRLWIDE-20-BUTTW)/5, ptop+8, 
	   BUTTW, BUTTH, "Max Size", infofg, infobg);
  BTCreate(&but[BNORM], ctrlW, 10+(CTRLWIDE-20-BUTTW)/5, ptop + BUTTH + 2*8, 
	   BUTTW, BUTTH, "Normal", infofg, infobg);

  BTCreate(&but[BUP2], ctrlW, 10+(2*(CTRLWIDE-20-BUTTW))/5, ptop+8, 
	   BUTTW, BUTTH, "Dbl Size", infofg, infobg);
  BTCreate(&but[BDN2], ctrlW, 10+(2*(CTRLWIDE-20-BUTTW))/5, ptop+BUTTH+2*8, 
	   BUTTW, BUTTH, "Half Size", infofg, infobg);

  BTCreate(&but[BUP10], ctrlW, 10+(3*(CTRLWIDE-20-BUTTW))/5, ptop+8, 
	   BUTTW, BUTTH, "+10%", infofg, infobg);
  BTCreate(&but[BDN10], ctrlW, 10+(3*(CTRLWIDE-20-BUTTW))/5, ptop+BUTTH+2*8, 
	   BUTTW, BUTTH, "-10%", infofg, infobg);

  BTCreate(&but[B4BY3], ctrlW, 10+(4*(CTRLWIDE-20-BUTTW))/5, ptop+8, 
	   BUTTW, BUTTH, "4x3", infofg, infobg);
  BTCreate(&but[BASPECT], ctrlW, 10+(4*(CTRLWIDE-20-BUTTW))/5, ptop+BUTTH+2*8, 
	   BUTTW, BUTTH, "Aspect", infofg, infobg);

  BTCreate(&but[BROTATE],  ctrlW, 10+(5*(CTRLWIDE-20-BUTTW))/5, ptop+8,
	   BUTTW, BUTTH, "Rotate", infofg, infobg);
  BTCreate(&but[BGAMMA], ctrlW, 10+(5*(CTRLWIDE-20-BUTTW))/5, ptop+BUTTH+2*8,
	   BUTTW, BUTTH, "Gamma", infofg, infobg);

  XMapSubwindows(theDisp, ctrlW);
}
  

/***************************************************/
void CtrlBox(vis)
int vis;
{
  if (vis) XMapRaised(theDisp, ctrlW);
  else     XUnmapWindow(theDisp, ctrlW);

  ctrlUp = vis;
}


/***************************************************/
void RedrawCtrl(x,y,w,h)
int x,y,w,h;
{
  char foo[40];
  int i;
  XRectangle xr;

  xr.x = x;  xr.y = y;  xr.width = w;  xr.height = h;
  XSetClipRectangles(theDisp, theGC, 0,0, &xr, 1, Unsorted);

  XSetForeground(theDisp, theGC, infofg);
  XSetBackground(theDisp, theGC, infobg);

  XDrawLine(theDisp, ctrlW, theGC, 0, ptop, CTRLWIDE, ptop);

  if (numnames>1) sprintf(foo,"%d files",numnames);
  else strcpy(foo,"1 file");
    
  XSetForeground(theDisp, theGC, infobg);
  XFillRectangle(theDisp,ctrlW, theGC, 10+1,5+1,StringWidth(foo)+4,CHIGH+2);
  XSetForeground(theDisp,theGC,infofg);
  XDrawRectangle(theDisp,ctrlW, theGC, 10,5,StringWidth(foo)+5,CHIGH+3);
  XDrawString(theDisp, ctrlW, theGC, 10+3, 5+ASCENT+2,
	      foo, strlen(foo));
 
  for (i=0; i<NBUTTS; i++)
    BTRedraw(&but[i]);

  DrawCtrlStr();

  XSetClipMask(theDisp, theGC, None);
}


/***************************************************/
void DrawCtrlStr()
{
  int   y;
  char *st;

  y = ptop - (CHIGH + 2);
  st = GetISTR(ISTR_INFO);

  XSetForeground(theDisp, theGC, infobg);
  XFillRectangle(theDisp, ctrlW, theGC, 0, y-1, CTRLWIDE, CHIGH+3);

  XSetForeground(theDisp, theGC, infofg);
  XDrawLine(theDisp, ctrlW, theGC, 0, y-2, CTRLWIDE, y-2);

  XDrawString(theDisp, ctrlW, theGC, 10, y+ASCENT, st, strlen(st));
}


/***************************************************/
int ClickCtrl(x,y)
int x,y;
{
  BUTT *bp;
  int   i;

  for (i=0; i<NBUTTS; i++) {
    bp = &but[i];
    if (PTINRECT(x, y, bp->x, bp->y, bp->w, bp->h)) break;
  }

  if (i<NBUTTS) {                   /* found one */
    if (BTTrack(bp)) return (i);    /* and it was clicked */
  }

  return -1;
}



/***************************************************/
void ScrollToCurrent()
{
  /* called when 'curname' is changed by anything (next/prev buttons,
     wait timeout, whatever.  IF curname is already visible, just redraws
     list to reflect changed selection.  If not, trys to adjust 'liststart' 
     so that curname will appear in the center of the list window */

  int halfway;

  nList.selected = curname;
/*  if (nList.selected >= nList.scrl.val && 
      nList.selected <  nList.scrl.val + nList.nlines) LSRedraw(&nList); */

  if (nList.selected > nList.scrl.val && 
      nList.selected <  nList.scrl.val + nList.nlines-1) LSRedraw(&nList);
  else {
    halfway = (nList.nlines)/2;   /* offset to the halfway pt. of the list */
    SCSetVal(&nList.scrl, nList.selected - halfway);
  }
}


/***************************************************/
static void RedrawNList()
{
  LSRedraw(&nList);
}




/***************** LIST STUFF *********************/

/***************************************************/
void LSCreate(lp, win, x, y, w, h, nlines, strlist, nstr, fg, bg, fptr, 
	      typ, donly)
LIST         *lp;
Window        win;
int           x,y,w,h,nlines,nstr,typ,donly;
unsigned long fg, bg;
char        **strlist;    /* a pointer to a list of strings */
void        (*fptr)();
{
  lp->win = XCreateSimpleWindow(theDisp,win,x,y,w,h,1,infofg,infobg);
  if (!lp->win) FatalError("can't create list window!");

  lp->x = x;    lp->y = y;   
  lp->w = w;    lp->h = h;
  lp->fg = fg;  lp->bg = bg;
  lp->str      = strlist;
  lp->nstr     = nstr;
  lp->selected = 0;
  lp->nlines   = nlines;
  lp->filetypes= typ;
  lp->dirsonly = donly;

  XSelectInput(theDisp, lp->win, ExposureMask | ButtonPressMask);

  SCCreate(&lp->scrl, win, x+w, y, 1, h, 0, nstr-nlines, curname, nlines-1,
	   fg, bg, fptr);
}



/***************************************************/
void LSNewData(lp, strlist, nstr)
LIST         *lp;
char        **strlist;
int           nstr;
{
  lp->str = strlist;
  lp->nstr = nstr;
  lp->selected = 0;
  SCSetRange(&lp->scrl, 0, nstr - lp->nlines, 0, lp->nlines-1);
}


/***************************************************/
static void drawSel(lp,j)
LIST *lp;
int j;
{
  int i, inactive;
  unsigned long fg, bg;

  inactive = INACTIVE(lp,j);

  i = j - lp->scrl.val;
  if (i<0 || i>=lp->nlines) return;  /* off screen */

  if (j == lp->selected && !inactive && j<lp->nstr) 
       { fg = lp->bg;  bg = lp->fg; }  /* invert */
  else { fg = lp->fg;  bg = lp->bg; }

  XSetForeground(theDisp, theGC, bg);
  XFillRectangle(theDisp, lp->win, theGC, 0,i*LINEHIGH, lp->w, LINEHIGH);

  if (j>=0 && j<lp->nstr) {   /* only draw string if valid */
    /* make non-dirs inactive, if dirsonly and filetypes */
    XSetForeground(theDisp, theGC, fg);
    XSetBackground(theDisp, theGC, bg);

    if (!lp->filetypes) 
      XDrawString(theDisp, lp->win, theGC, 3, i*LINEHIGH + ASCENT + 1, 
		  lp->str[j], strlen(lp->str[j]));
    else {
      int ypos = i*LINEHIGH + (LINEHIGH - i_fifo_height)/2;

      if (lp->str[j][0] == C_FIFO) 
	XCopyPlane(theDisp, fifoPix, lp->win, theGC, 0, 0,
		   i_fifo_width, i_fifo_height, 3, ypos, 1L);

      else if (lp->str[j][0] == C_CHR) 
	XCopyPlane(theDisp, chrPix, lp->win, theGC, 0, 0,
		   i_chr_width, i_chr_height, 3, ypos, 1L);

      else if (lp->str[j][0] == C_DIR) 
	XCopyPlane(theDisp, dirPix, lp->win, theGC, 0, 0,
		   i_dir_width, i_dir_height, 3, ypos, 1L);

      else if (lp->str[j][0] == C_BLK) 
	XCopyPlane(theDisp, blkPix, lp->win, theGC, 0, 0,
		   i_blk_width, i_blk_height, 3, ypos, 1L);

      else if (lp->str[j][0] == C_LNK) 
	XCopyPlane(theDisp, lnkPix, lp->win, theGC, 0, 0,
		   i_lnk_width, i_lnk_height, 3, ypos, 1L);

      else if (lp->str[j][0] == C_SOCK) 
	XCopyPlane(theDisp, sockPix, lp->win, theGC, 0, 0,
		   i_sock_width, i_sock_height, 3, ypos, 1L);

      else  /* lp->str[j][0] == C_REG */
	XCopyPlane(theDisp, regPix, lp->win, theGC, 0, 0,
		   i_reg_width, i_reg_height, 3, ypos, 1L);


      XDrawString(theDisp, lp->win, theGC, 3 + i_fifo_width + 3, 
		  i*LINEHIGH + ASCENT + 1, 
		  lp->str[j]+1, strlen(lp->str[j]+1));

#ifdef STIPPLE
      if (inactive) { 
	/* stipple the icon by drawing 'bg' where there's 1's in the stipple */
	XSetFillStyle(theDisp, theGC, FillStippled);
	XSetStipple(theDisp, theGC, grayStip);
	XSetForeground(theDisp, theGC, bg);
	XSetBackground(theDisp, theGC, fg);
	XFillRectangle(theDisp,lp->win,theGC,3,i*LINEHIGH,lp->w,LINEHIGH);
	XSetForeground(theDisp, theGC, fg);
	XSetFillStyle(theDisp, theGC, FillSolid);
      }
#endif STIPPLE

    }
  }
}


/***************************************************/
void LSRedraw(lp)
LIST *lp;
{
  int  i;

  for (i = lp->scrl.val; i < lp->scrl.val + lp->nlines; i++) 
    drawSel(lp,i);
}


/***************************************************/
int LSClick(lp,ev)
LIST *lp;
XButtonEvent *ev;
{
  /* returns '-1' normally.  returns 0 -> numnames-1 for a goto */

  Window       rW, cW;
  int          rx, ry, x, y, sel, oldsel;
  unsigned int mask;
  static Time  lasttime=0;
  static int   lastsel = -1;

  x = ev->x;  y = ev->y;
  sel = lp->scrl.val + y/LINEHIGH;
  if (sel >= lp->nstr) sel = lp->selected;

  /* see if it's a double click */
  if (ev->time - lasttime < DBLCLKTIME && sel==lastsel 
      && (lp->scrl.val + y/LINEHIGH) < lp->nstr
      && !INACTIVE(lp,sel)) {
    return (sel);
  }

  lasttime = ev->time;  lastsel = sel;

  /* if not clicked on selected, turn off selected and select new one */
  if (sel != lp->selected) {
    oldsel = lp->selected;
    lp->selected = sel;
    drawSel(lp,sel);  drawSel(lp,oldsel);
    XFlush(theDisp);
  }

  while (XQueryPointer(theDisp,lp->win,&rW,&cW,&rx,&ry,&x,&y,&mask)) {
    if (!(mask & Button1Mask)) break;    /* button released */

    if (y<0) { /* scroll up in list */ 
      if (lp->scrl.val > lp->scrl.min) {
	lp->selected = lp->scrl.val - 1;
	SCSetVal(&lp->scrl, lp->scrl.val - 1);
	Timer(100);
      }
    }

    else if (y > lp->h) { /* scroll down in list */
      if (lp->scrl.val < lp->scrl.max) {
	lp->selected = lp->scrl.val + lp->nlines;
	if (lp->selected >= lp->nstr) lp->selected = lp->nstr - 1;
	SCSetVal(&lp->scrl, lp->scrl.val + 1);
	Timer(100);
      }
    }

    else {
      sel = lp->scrl.val + y/LINEHIGH;
      if (sel >= lp->nstr) sel = lp->nstr - 1;

      if (sel != lp->selected && sel >= lp->scrl.val &&
	  sel < lp->scrl.val + lp->nlines) {  
	/* dragged to another on current page */
	oldsel = lp->selected;
	lp->selected = sel;
	drawSel(lp, sel);  drawSel(lp, oldsel);
	XFlush(theDisp);
      }
    }
  }

  return(-1);
}



\BARFOO\
else
  echo "will not over write ./xvctrl.c"
fi
echo "Finished archive 4 of 8"
exit



dan
----------------------------------------------------
O'Reilly && Associates   argv@sun.com / argv@ora.com
Opinions expressed reflect those of the author only.