/* 
 * xvgam.c
 *
 * callable functions:
 *   <many>
 */

/*
 * Copyright 1989, 1990, 1991, 1992 by John Bradley and
 *                       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 expressed or implied warranty.
 *
 * The author may be contacted via:
 *    US Mail:   John Bradley
 *               GRASP Lab, Room 301C
 *               3401 Walnut St.  
 *               Philadelphia, PA  19104
 *
 *    Phone:     (215) 898-8813
 *    EMail:     bradley@cis.upenn.edu       
 */


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

#define GAMW 664
#define GAMH 518

#define GAMbutF 386

#define CMAPX  10
#define CMAPY  17
#define CMAPCW 12
#define CMAPCH 10
#define CMAPW  (CMAPCW * 16)
#define CMAPH  (CMAPCH * 16)

#define MAXUNDO 32

#define BUTTH   23
#define N_HMAP   6     /* # of Hue modification remappings */

#define N_HDBUTT 5
#define HDB_ROTL  0
#define HDB_ROTR  1
#define HDB_EXPND 2
#define HDB_SHRNK 3
#define HDB_FLIP  4

#define N_HDBUTT2 4
#define HDB_DESAT 2
#define HDB_SAT   3


#define HD_CLEAR  0x01   /* clears inside of hue dial */
#define HD_FRAME  0x02
#define HD_HANDS  0x04
#define HD_DIR    0x08
#define HD_VALS   0x10
#define HD_TITLE  0x20
#define HD_CLHNDS 0x40
#define HD_BUTTS  0x80
#define HD_ALL   (HD_FRAME | HD_HANDS | HD_DIR | HD_VALS | HD_TITLE | HD_BUTTS)

#define HD_RADIUS 30   /* radius of bounding circle of HDIALs */

#define DEG2RAD (3.14159 / 180.0)
#define RAD2DEG (180.0 / 3.14159)



/* stuff for colormap Undo button */
struct cmapstate { byte  r[256], g[256], b[256];
		   int cellgroup[256];
		   int curgroup;
		   int maxgroup;
		   int editColor;
		 } prevcmap, tmpcmap;

struct hmap {    int src_st;
		 int src_en;
		 int src_ccw;
		 int dst_st;
		 int dst_en;
		 int dst_ccw;
	       };

struct gamstate { struct hmap hmap[N_HMAP];
		  int hueRBnum;                          /* 1 - 6 */
		  int wht_stval, wht_satval, wht_enab;
		  int satval;
		  GRAF_STATE istate, rstate, gstate, bstate;
		} undo[MAXUNDO], preset[4], defstate; 
static int uptr, uhead, utail;

typedef struct huedial {
		 Window win;      /* window that dial exists in */
		 int    x,y;      /* coordinates of center of dial */
                 int    range;    /* 0 = single value, 1 = range */
		 int    stval;    /* start of range (ONLY val ifnot range) */
		 int    enval;    /* end of range */
		 int    ccwise;   /* 1 if range goes ccwise, 0 if cwise */
		 char  *str;      /* title string */
		 u_long fg,bg;    /* colors */
		 int    satval;   /* saturation value on non-range dial */
		 BUTT   hdbutt[N_HDBUTT];
		 CBUTT  enabCB;
	       } HDIAL;



static BUTT   hueclrB;
static CBUTT  enabCB, autoCB, resetCB;
static HDIAL  srcHD, dstHD, whtHD;
static DIAL   satDial, rhDial, gsDial, bvDial;
static GRAF   intGraf, rGraf, gGraf, bGraf;
static RBUTT *hueRB;
static Window cmapF, hsvF, rgbF, modF, butF;
static struct hmap hmap[N_HMAP];
static int    hremap[360];



#ifdef __STDC__
static void changedGam();
static void drawGam(int,int,int,int);
static void drawBut(int,int,int,int);
static void drawArrow(int, int);
static void drawCmap(void);
static void clickCmap(int,int,int);
static void clickGam(int,int);
static void selectCell(int,int);
static int  deladdCell(int, int);
static void doCmd(int);
static void SetHSVmode(void);
static void applyGamma();
static void calcHistEQ(int *);
static void saveGamState(void);
static void gamUndo(void);
static void gamRedo(void);
static void save_gstate(struct gamstate *);
static void restore_gstate(struct gamstate *);
static void rndCols(void);
static void saveCMap(struct cmapstate *);
static void restoreCMap(struct cmapstate *);
static void parseResources(void);
static void makeResources(void);

static void HDCreate(HDIAL *, Window, int, int, int, int, int, int, char *,
 		     u_long, u_long);
static void HDRedraw(HDIAL *, int);
static int  HDClick(HDIAL *, int, int);
static int  HDTrack(HDIAL *, int, int);
static int  hdg2xdg(int);
static void pol2xy(int, int, double, int, int *, int *);
static int  computeHDval(HDIAL *, int, int);
static void initHmap(void);
static void init1hmap(int);
static void dials2hmap(void);
static void hmap2dials(void);
static void build_hremap(void);

#else
static void changedGam(), drawGam(), drawBut(), drawArrow(), drawCmap();
static void clickCmap(), clickGam(), selectCell();
static void doCmd(), applyGamma(), calcHistEQ();
static int  deladdCell();
static void SetHSVmode(), saveGamState(), gamUndo(), gamRedo();
static void save_gstate(), restore_gstate();
static void rndCols(), saveCMap(), restoreCMap(), parseResources();
static void makeResources();

static void HDCreate(), HDRedraw(), pol2xy();
static int  HDClick(), HDTrack(), hdg2xdg(), computeHDval();
static void initHmap(), init1hmap(), dials2hmap(), hmap2dials();
static void build_hremap();
#endif





/***************************************************/
void CreateGam(geom)
char *geom;
{
  XClassHint classh;

  gamW = CreateWindow("xv color editor", geom, GAMW,GAMH, infofg,infobg);
  if (!gamW) FatalError("can't create cedit window!");

  classh.res_name = "xv";
  classh.res_class = "XVcedit";
  XSetClassHint(theDisp, gamW, &classh);
  StoreDeleteWindowProp(gamW);

  cmapF = XCreateSimpleWindow(theDisp,gamW, 10,   8,212,339, 1,infofg,infobg);
  butF  = XCreateSimpleWindow(theDisp,gamW, 10, 353,212, 96, 1,infofg,infobg);
  modF  = XCreateSimpleWindow(theDisp,gamW, 10, 455,212, 53, 1,infofg,infobg);
  hsvF  = XCreateSimpleWindow(theDisp,gamW, 242,  8,205,500, 1,infofg,infobg);
  rgbF  = XCreateSimpleWindow(theDisp,gamW, 467,  8,185,500, 1,infofg,infobg);

  if (!cmapF || !butF || !modF || !hsvF || !rgbF) 
    FatalError("couldn't create frame windows");

  XSelectInput(theDisp, gamW,  ExposureMask);
  XSelectInput(theDisp, cmapF, ExposureMask | ButtonPressMask);
  XSelectInput(theDisp, butF,  ExposureMask | ButtonPressMask);
  XSelectInput(theDisp, modF,  ExposureMask | ButtonPressMask);
  XSelectInput(theDisp, hsvF,  ExposureMask | ButtonPressMask);
  XSelectInput(theDisp, rgbF,  ExposureMask);

  if (ctrlColor) XSetWindowBackground(theDisp, gamW, locol);
            else XSetWindowBackgroundPixmap(theDisp, gamW, grayTile);

  /********** COLORMAP editing doo-wahs ***********/


  BTCreate(&gbut[G_BCOLUNDO], cmapF, 5, 182, 66, BUTTH, 
	   "ColUndo", infofg, infobg);
  BTCreate(&gbut[G_BCOLREV], cmapF,  5 + 66 + 1, 182, 67, BUTTH, 
	   "Revert", infofg, infobg);
  BTCreate(&gbut[G_BHSVRGB], cmapF,  5+66+67+2,  182, 66, BUTTH, 
	   "RGB/HSV", infofg, infobg);

  BTCreate(&gbut[G_BMONO], cmapF,    5, 206, 66, BUTTH, 
	   "Grey", infofg, infobg);
  BTCreate(&gbut[G_BRV],   cmapF,    5 + 66 + 1, 206, 67, BUTTH, 
	   "RevVid", infofg, infobg);
  BTCreate(&gbut[G_BRNDCOL], cmapF,  5 + 66 + 67 + 2, 206, 66, BUTTH, 
	   "Random", infofg, infobg);

  DCreate(&rhDial, cmapF, 5, 232, 66, 100,   0,360,180, 5, 
	  infofg, infobg, "Hue", NULL);
  DCreate(&gsDial, cmapF, 72, 232, 66, 100,  0,360,180, 5, 
	  infofg, infobg, "Sat.", NULL);
  DCreate(&bvDial, cmapF, 139, 232, 66, 100,   0,360,180, 5, 
	  infofg, infobg, "Value", NULL);


  /*********** CONTROL BUTTONS ***********/

/* positioning constants for buttons.  (arranged as 4x4 grid...) */
#define BXSPACE 53
#define BYSPACE (BUTTH+1)

#define BX0 0
#define BX1 (BX0 + BXSPACE)
#define BX2 (BX0 + BXSPACE*2)
#define BX3 (BX0 + BXSPACE*3)

#define BY0 0
#define BY1 (BY0 + BYSPACE)
#define BY2 (BY0 + BYSPACE*2)
#define BY3 (BY0 + BYSPACE*3)

  BTCreate(&gbut[G_BAPPLY],  butF, BX0,BY0, 52,BUTTH,"Apply", infofg,infobg);
  BTCreate(&gbut[G_BNOGAM],  butF, BX0,BY1, 52,BUTTH,"NoMod", infofg,infobg);
  BTCreate(&gbut[G_BMAXCONT],butF, BX0,BY2, 52,BUTTH,"Norm",  infofg,infobg);
  BTCreate(&gbut[G_BHISTEQ], butF, BX0,BY3, 52,BUTTH,"HistEq",infofg,infobg);

  BTCreate(&gbut[G_BUP_BR],butF, BX1,BY0, 52,BUTTH,"Brite",infofg,infobg);
  BTCreate(&gbut[G_BDN_BR],butF, BX1,BY1, 52,BUTTH,"Dim",  infofg,infobg);
  BTCreate(&gbut[G_BUP_CN],butF, BX1,BY2, 52,BUTTH,"Sharp",infofg,infobg);
  BTCreate(&gbut[G_BDN_CN],butF, BX1,BY3, 52,BUTTH,"Dull", infofg,infobg);

  BTCreate(&gbut[G_BRESET],butF, BX2,   BY0, 52,BUTTH,"Reset", infofg,infobg);
  BTCreate(&gbut[G_B1],    butF, BX2,   BY1, 25,BUTTH,"1",    infofg,infobg);
  BTCreate(&gbut[G_B2],    butF, BX2+26,BY1, 26,BUTTH,"2",    infofg,infobg);
  BTCreate(&gbut[G_B3],    butF, BX2,   BY2, 25,BUTTH,"3",    infofg,infobg);
  BTCreate(&gbut[G_B4],    butF, BX2+26,BY2, 26,BUTTH,"4",    infofg,infobg);
  BTCreate(&gbut[G_BSET],  butF, BX2,   BY3, 52,BUTTH,"Set",  infofg,infobg);

  BTCreate(&gbut[G_BUNDO], butF, BX3, BY0, 52,BUTTH,"Undo",   infofg,infobg);
  BTCreate(&gbut[G_BREDO], butF, BX3, BY1, 52,BUTTH,"Redo",   infofg,infobg);
  BTCreate(&gbut[G_BGETRES],butF,BX3, BY2, 52,BUTTH,"CutRes", infofg,infobg);
  BTCreate(&gbut[G_BCLOSE],butF, BX3, BY3, 52,BUTTH,"Close",  infofg,infobg);


  gbut[G_BSET].toggle = 1;
  gbut[G_BUNDO].active = 0;
  gbut[G_BREDO].active = 0;

  CBCreate(&enabCB, modF,2,2,     "Display with HSV/RGB mods.",infofg,infobg);
  CBCreate(&autoCB, modF,2,2+17,  "Auto-apply HSV/RGB mods.",  infofg,infobg);
  CBCreate(&resetCB,modF,2,2+17*2,"Auto-reset on new image.",  infofg,infobg);
  enabCB.val = autoCB.val = resetCB.val = 1;


  /************ HSV editing doo-wahs **************/


  HDCreate(&srcHD, hsvF,  51, 65, 1, 0, 30, 0, "From", infofg, infobg);
  HDCreate(&dstHD, hsvF, 155, 65, 1, 0, 30, 0, "To",   infofg, infobg);

  HDCreate(&whtHD, hsvF,  50,243, 0, 0,  0, 0, "White",infofg, infobg);

  DCreate(&satDial, hsvF, 100, 199, 100, 121, -100, 100, 0, 5, 
	   infofg, infobg, "Saturation", "%");

  hueRB = RBCreate(NULL, hsvF,  7, 153, "1", infofg, infobg);
  RBCreate        (hueRB,hsvF, 47, 153, "2", infofg, infobg);
  RBCreate        (hueRB,hsvF, 87, 153, "3", infofg, infobg);
  RBCreate        (hueRB,hsvF,  7, 170, "4", infofg, infobg);
  RBCreate        (hueRB,hsvF, 47, 170, "5", infofg, infobg);
  RBCreate        (hueRB,hsvF, 87, 170, "6", infofg, infobg);

  BTCreate(&hueclrB, hsvF, 127, 158, 70, BUTTH, "Reset", infofg, infobg);

  initHmap();
  hmap2dials();
  build_hremap();

  InitGraf(&intGraf);
  CreateGraf(&intGraf, hsvF, 20, 339, infofg, infobg, "Intensity");


  /********* RGB color correction doo-wahs ***********/


  InitGraf(&rGraf);
  CreateGraf(&rGraf, rgbF, 10, 20, infofg, infobg, "Red");

  InitGraf(&gGraf);
  CreateGraf(&gGraf, rgbF, 10, 179, infofg, infobg, "Green");
  
  InitGraf(&bGraf);
  CreateGraf(&bGraf, rgbF, 10, 338, infofg, infobg, "Blue");
  
  SetHSVmode();

  save_gstate(&defstate);
  save_gstate(&preset[0]);
  save_gstate(&preset[1]);
  save_gstate(&preset[2]);
  save_gstate(&preset[3]);
  
  parseResources();
  restore_gstate(&defstate);   /* defstate may have changed due to resources */

  uptr = utail = uhead = 0;
  save_gstate(&undo[0]);

  XMapSubwindows(theDisp, cmapF);
  XMapSubwindows(theDisp, hsvF);
  XMapSubwindows(theDisp, rgbF);
  XMapSubwindows(theDisp, gamW);
}
  

/***************************************************/
int GamCheckEvent(xev)
XEvent *xev;
{
  /* check event to see if it's for one of our subwindows.  If it is,
     deal accordingly, and return '1'.  Otherwise, return '0' */

  int rv;

  rv = 1;
  
  if (xev->type == Expose) {
    int x,y,w,h;
    XExposeEvent *e = (XExposeEvent *) xev;
    x = e->x;  y = e->y;  w = e->width;  h = e->height;

    /* throw away excess redraws for 'dumb' windows */
    if (e->count > 0 && 
	(e->window == satDial.win || e->window == rhDial.win ||
	 e->window == gsDial.win  || e->window == bvDial.win ||
	 e->window == cmapF       || e->window == modF       ||
	 e->window == intGraf.win || e->window == rGraf.win  ||
	 e->window == gGraf.win   || e->window == bGraf.win  ||
	 e->window == rgbF)) {}

    else if (e->window == gamW)        drawGam(x, y, w, h);
    else if (e->window == butF)        drawBut(x, y, w, h);
    else if (e->window == satDial.win) DRedraw(&satDial);
    else if (e->window == rhDial.win)  DRedraw(&rhDial);
    else if (e->window == gsDial.win)  DRedraw(&gsDial);
    else if (e->window == bvDial.win)  DRedraw(&bvDial);

    else if (e->window == intGraf.win) RedrawGraf(&intGraf,x,y,w,h);
    else if (e->window == rGraf.win)   RedrawGraf(&rGraf,  x,y,w,h);
    else if (e->window == gGraf.win)   RedrawGraf(&gGraf,  x,y,w,h);
    else if (e->window == bGraf.win)   RedrawGraf(&bGraf,  x,y,w,h);

    else if (e->window == cmapF) drawCmap();

    else if (e->window == modF) {
      CBRedraw(&enabCB);
      CBRedraw(&autoCB);
      CBRedraw(&resetCB);
    }

    else if (e->window == hsvF) {
      XRectangle xr;
      xr.x = x;  xr.y = y;  xr.width = e->width;  xr.height = e->height;
      XSetClipRectangles(theDisp, theGC, 0,0, &xr, 1, Unsorted);

      XSetForeground(theDisp, theGC, infofg);
      ULineString(hsvF, "HSV Modification", 2, 1+ASCENT);

      HDRedraw(&srcHD, HD_ALL);
      HDRedraw(&dstHD, HD_ALL);
      HDRedraw(&whtHD, HD_ALL);
      RBRedraw(hueRB, -1);
      BTRedraw(&hueclrB);

      XSetClipMask(theDisp, theGC, None);
    }

    else if (e->window == rgbF) {
      XSetForeground(theDisp, theGC, infofg);
      ULineString(rgbF, "RGB Modification", 2, 1+ASCENT);
    }

    else rv = 0;
  }

  else if (xev->type == ButtonPress) {
    XButtonEvent *e = (XButtonEvent *) xev;
    int i,x,y;
    x = e->x;  y = e->y;

    if (e->button == Button1) {
      if      (e->window == butF)  clickGam(x,y);
      else if (e->window == cmapF) clickCmap(x,y,1);

      else if (e->window == modF) {
	if (CBClick(&enabCB,x,y)) {
	  if (CBTrack(&enabCB)) applyGamma();  /* enabCB changed, regen */
	}

	else if (CBClick(&autoCB,x,y)) {
	  CBTrack(&autoCB);
	  if (autoCB.val) applyGamma();  /* auto-apply turned on, apply now */
	}

	else if (CBClick(&resetCB,x,y)) CBTrack(&resetCB);
      }


      else if (e->window == hsvF) {
	if (HDClick(&srcHD, x,y) || HDClick(&dstHD, x,y)) { 
	  dials2hmap();
	  build_hremap();
	  changedGam();
	}
	else if (HDClick(&whtHD, x,y)) changedGam();

	else if (PTINRECT(x,y,hueclrB.x, hueclrB.y, hueclrB.w, hueclrB.h)) {
	  if (BTTrack(&hueclrB)) {   /* RESET */
	    dstHD.stval  = srcHD.stval;
	    dstHD.enval  = srcHD.enval;
	    dstHD.ccwise = srcHD.ccwise;
	    HDRedraw(&dstHD, HD_ALL | HD_CLEAR);
	    dials2hmap();
	    build_hremap();
	    changedGam();
	  }
	}

	else if ((i=RBClick(hueRB,x,y)) >= 0) {
	  dials2hmap();
	  if (RBTrack(hueRB, i)) hmap2dials();
	}
      }

      else if (e->window == intGraf.win) {
	GRAF_STATE gs;
	if (ClickGraf(&intGraf, e->subwindow, x,y)) {
	  GetGrafState(&intGraf, &gs);
	  changedGam();
	}
      }

      else if (e->window == rGraf.win) {
	if (ClickGraf(&rGraf, e->subwindow, x,y)) changedGam();
      }

      else if (e->window == gGraf.win) {
	if (ClickGraf(&gGraf, e->subwindow, x,y)) changedGam();
      }

      else if (e->window == bGraf.win) {
	if (ClickGraf(&bGraf, e->subwindow, x,y)) changedGam();
      }


      else if (e->window == satDial.win) {
	if (DTrack(&satDial, x, y)) changedGam();
      }

      else if (e->window == rhDial.win ||
	       e->window == gsDial.win ||
	       e->window == bvDial.win) {

	if ((e->window == rhDial.win && DTrack(&rhDial, x,y)) || 
	    (e->window == gsDial.win && DTrack(&gsDial, x,y)) ||
	    (e->window == bvDial.win && DTrack(&bvDial, x,y))) {
	  saveCMap(&prevcmap);
	  BTSetActive(&gbut[G_BCOLUNDO],1);
	  ApplyEditColor(0);
	}
      }

      else rv = 0;
    }

    else if (e->button == Button2) {
      if (e->window == cmapF) clickCmap(x, y, 2);
      else rv = 0;
    }

    else if (e->button == Button3) {
      if (e->window == cmapF) clickCmap(x, y, 3);
      else rv = 0;
    }
    else rv = 0;
  }


  else if (xev->type == KeyPress) {
    XKeyEvent *e = (XKeyEvent *) xev;
    char buf[128];  KeySym ks;
    int stlen;
	
    stlen = XLookupString(e,buf,128,&ks,(XComposeStatus *) NULL);
    buf[stlen] = '\0';

    if (!stlen) return 0;

    else if (e->window == intGraf.win) rv = GrafKey(&intGraf,buf);
    else if (e->window == rGraf.win  ) rv = GrafKey(&rGraf,buf);
    else if (e->window == gGraf.win  ) rv = GrafKey(&gGraf,buf);
    else if (e->window == bGraf.win  ) rv = GrafKey(&bGraf,buf);
    else rv = 0;

    if (rv>1) changedGam();   /* hit 'enter' in one of Graf's */
  }

  else rv = 0;

  return rv;
}



/***************************************************/
static void changedGam()
{
  /* called whenever an HSV/RGB gamma ctrl has changed
     applies change to image if autoCB.val is set */

  saveGamState();
  if (autoCB.val) applyGamma();
}


/***************************************************/
void GamBox(vis)
int vis;
{
  if (vis) XMapRaised(theDisp, gamW);
  else     XUnmapWindow(theDisp, gamW);

  gamUp = vis;
}


/***************************************************/
static void drawGam(x,y,w,h)
int x,y,w,h;
{
  XRectangle xr;

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

  drawArrow(232,178);
  drawArrow(457,178);

  XSetClipMask(theDisp, theGC, None);
}


/***************************************************/
static void drawBut(x,y,w,h)
int x,y,w,h;
{
  int i;
  XRectangle xr;

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

  for (i=0; i<G_NBUTTS; i++) {
    if (gbut[i].win == butF) BTRedraw(&gbut[i]);
  }

  XSetClipMask(theDisp, theGC, None);
}


/***************************************************/
static void drawArrow(x,y)
int x,y;
{
  XPoint pts[8];
  
  pts[0].x = x+10;     pts[0].y = y;
  pts[1].x = x-4;      pts[1].y = y-100;
  pts[2].x = x-4;      pts[2].y = y-40;
  pts[3].x = x-10;     pts[3].y = y-40;
  pts[4].x = x-10;     pts[4].y = y+40;
  pts[5].x = x-4;      pts[5].y = y+40;
  pts[6].x = x-4;      pts[6].y = y+100;
  pts[7].x = pts[0].x; pts[7].y = pts[0].y;

  XSetForeground(theDisp, theGC, infobg);
  XFillPolygon(theDisp, gamW, theGC, pts, 8, Convex, CoordModeOrigin);
  XSetForeground(theDisp, theGC, infofg);
  XDrawLines(theDisp, gamW, theGC, pts, 8, CoordModeOrigin);
}


/***************************************************/
static void drawCmap()
{
  int i;

  XSetForeground(theDisp, theGC, infofg);
  ULineString(cmapF, "Colormap Editing", 2, 1+ASCENT);

  for (i=0; i<G_NBUTTS; i++) {
    if (gbut[i].win == cmapF) BTRedraw(&gbut[i]);
  }

  RedrawCMap();
}



/***************************************************/
void NewCMap()
{
  /* called when we've loaded a new picture */

  int i;

  XClearArea(theDisp, cmapF, CMAPX, CMAPY, CMAPW+1, CMAPH+1, False);
  for (i=0; i<256; i++) cellgroup[i] = 0;
  curgroup = maxgroup = 0;

  BTSetActive(&gbut[G_BCOLUNDO],0);

  if (resetCB.val) {            /* auto-reset gamma controls */
    i = autoCB.val;
    if (i) autoCB.val = 0;      /* must NOT apply changes! */
    restore_gstate(&defstate);
    autoCB.val = i;
  }
}


/***************************************************/
void RedrawCMap()
{
  int i;

  XSetLineAttributes(theDisp, theGC, 0, LineSolid, CapButt, JoinMiter);
  XSetForeground(theDisp, theGC, infofg);

  for (i=0; i<numcols; i++) {
    int x,y;
    x = CMAPX + (i%16)*CMAPCW;
    y = CMAPY + (i/16)*CMAPCH;
    XDrawRectangle(theDisp, cmapF, theGC, x, y, CMAPCW,CMAPCH);
  }

  for (i=0; i<numcols; i++) {
    int x,y;
    x = CMAPX + (i%16)*CMAPCW;
    y = CMAPY + (i/16)*CMAPCH;

    XSetForeground(theDisp, theGC, cols[i]);
    XFillRectangle(theDisp, cmapF, theGC, x+1, y+1, CMAPCW-1,CMAPCH-1);

    if (i == editColor || (curgroup && (cellgroup[i]==curgroup))) {
      XSetForeground(theDisp, theGC, infobg);
      XDrawRectangle(theDisp, cmapF, theGC, x+1, y+1, CMAPCW-2,CMAPCH-2);
      XSetForeground(theDisp, theGC, infofg);
      XDrawRectangle(theDisp, cmapF, theGC, x+2, y+2, CMAPCW-4,CMAPCH-4);
    }
  }
}


/***************************************************/
static void selectCell(cellno, sel)
int cellno, sel;
{
  int x,y;

  if (cellno >= numcols) return;

  x = CMAPX + (cellno%16)*CMAPCW;
  y = CMAPY + (cellno/16)*CMAPCH;

  if (!sel) {   /* unhighlight a cell */
    XSetForeground(theDisp, theGC, cols[cellno]);
    XFillRectangle(theDisp, cmapF, theGC, x+1, y+1, CMAPCW-1,CMAPCH-1);
  }
  else {  /* highlight a cell */
    XSetForeground(theDisp, theGC, infobg);
    XDrawRectangle(theDisp, cmapF, theGC, x+1, y+1, CMAPCW-2,CMAPCH-2);
    XSetForeground(theDisp, theGC, infofg);
    XDrawRectangle(theDisp, cmapF, theGC, x+2, y+2, CMAPCW-4,CMAPCH-4);
  }
}


/***************************************************/
static void clickGam(x,y)
int x,y;
{
  int i;
  BUTT *bp;

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

  /* if 'Set' is lit, and we didn't click 'set' or 'Reset' or '1'..'4', 
     turn it off */
  if (i!=G_BSET && i!=G_B1 && i!=G_B2 && i!=G_B3 && i!=G_B4 && i!=G_BRESET
      && gbut[G_BSET].lit) {
    gbut[G_BSET].lit = 0;  
    BTRedraw(&gbut[G_BSET]);
  }
  

  if (i<G_NBUTTS) {  /* found one */
    if (BTTrack(bp)) doCmd(i);
  }
}


/***************************************************/
static void clickCmap(x,y,but)
int x,y,but;
{
  int i, recolor;
  BUTT *bp;

  if (but==1) {   /* if left click, check the cmap controls */
    for (i=0; i<G_NBUTTS; i++) {
      bp = &gbut[i];
      if (bp->win == cmapF && PTINRECT(x,y,bp->x, bp->y, bp->w, bp->h)) break;
    }

    if (i<G_NBUTTS) {  /* found one */
      if (BTTrack(bp)) doCmd(i);
      return;
    }
  }


  /* see if we're anywhere in the colormap area */

  if (PTINRECT(x,y,CMAPX,CMAPY,CMAPW,CMAPH)) {
    if (but==1) {           /* select different cell/group for editing */
      /* compute colorcell # */
      i = ((x-CMAPX)/CMAPCW) + ((y-CMAPY)/CMAPCH)*16;
      if (i<numcols) {    /* clicked in colormap.  track til mouseup */
	if (i!=editColor) ChangeEC(i);

	while (1) {
	  Window       rW,cW;
	  int          rx,ry,x,y;
	  unsigned int mask;

	  if (XQueryPointer(theDisp,cmapF,&rW,&cW,&rx,&ry,&x,&y,&mask)) {
	    if (!(mask & Button1Mask)) break;    /* button released */

	    RANGE(x, CMAPX, CMAPX+CMAPW-1);
	    RANGE(y, CMAPY, CMAPY+CMAPH-1);

	    i = ((x-CMAPX)/CMAPCW) + ((y-CMAPY)/CMAPCH)*16;
	    if (i<numcols && i != editColor) ChangeEC(i);
	  }
	} /* while */
      } /* if i<numcols */
    } /* if but==1 */

    
    else if (but==2) {   /* color smooth */
      int cellnum, delc, col1, j, delr, delg, delb;

      /* compute colorcell # */
      cellnum = ((x-CMAPX)/CMAPCW) + ((y-CMAPY)/CMAPCH)*16;
      if (cellnum<numcols) {
	delc = abs(cellnum - editColor);
	if (delc) {  /* didn't click on same cell */
	  saveCMap(&tmpcmap);  /* save the current cmap state */

	  if (cellnum < editColor) col1 = cellnum;  else col1 = editColor;

	  delr = rcmap[col1 + delc] - rcmap[col1];
	  delg = gcmap[col1 + delc] - gcmap[col1];
	  delb = bcmap[col1 + delc] - bcmap[col1];

	  for (i=0; i<delc; i++) {
	    rcmap[col1 + i] = rcmap[col1] + (delr * i) / delc;
	    gcmap[col1 + i] = gcmap[col1] + (delg * i) / delc;
	    bcmap[col1 + i] = bcmap[col1] + (delb * i) / delc;

	    if (cellgroup[col1 + i]) { 
	      /* propogate new color to all members of this group */
	      for (j=0; j<numcols; j++) 
		if (cellgroup[j] == cellgroup[col1 + i]) {
		  rcmap[j] = rcmap[col1 + i];
		  gcmap[j] = gcmap[col1 + i];
		  bcmap[j] = bcmap[col1 + i];
		}
	    }
	  }

	  for (i=0; i<numcols; i++) {
	    if (rcmap[i] != tmpcmap.r[i] ||
		gcmap[i] != tmpcmap.g[i] ||
		bcmap[i] != tmpcmap.b[i]) break;
	  }

	  if (i<numcols) {  /* something changed */
	    bcopy(&tmpcmap, &prevcmap, sizeof(struct cmapstate));
	    BTSetActive(&gbut[G_BCOLUNDO],1);
	    applyGamma();
	  }
	}
      }
    }


    else if (but==3) { /* add/delete cell(s) from current group */
      int lastcell,j,resetdel,curcell;

      /* better save the current cmap state, as it might change */
      saveCMap(&tmpcmap);

      recolor = resetdel = 0;
      /* compute colorcell # clicked in */
      curcell = ((x-CMAPX)/CMAPCW) + ((y-CMAPY)/CMAPCH)*16;
      if (curcell<numcols) {    /* clicked in colormap.  track til mouseup */
	if (deladdCell(curcell, 1)) recolor=1;

	lastcell = curcell;

	j = XGrabPointer(theDisp, cmapF, False, 0, GrabModeAsync, 
		     GrabModeAsync, None, None, CurrentTime);
	while (1) {
	  Window       rW,cW;
	  int          rx,ry,x,y;
	  unsigned int mask;

	  if (XQueryPointer(theDisp,cmapF,&rW,&cW,&rx,&ry,&x,&y,&mask)) {
	    /* if button3 and shift released */
	    if (!(mask & (Button3Mask | ShiftMask))) break;  

	    /* if user lets go of B3, reset addonly/delonly flag & lastcell */
	    if (!(mask & Button3Mask) && (mask & ShiftMask)) {
	      resetdel = 1;
	      lastcell = -1;
	    }

	    if (mask & Button3Mask) {  /* select while b3 down */
	      RANGE(x, CMAPX, CMAPX+CMAPW-1);
	      RANGE(y, CMAPY, CMAPY+CMAPH-1);

	      curcell = ((x-CMAPX)/CMAPCW) + ((y-CMAPY)/CMAPCH)*16;
	      if (curcell<numcols && curcell != lastcell) {
		lastcell = curcell;
		if (deladdCell(curcell, resetdel)) recolor=1;
		resetdel = 0;
	      }
	    }
	  }
	} /* while */

	XUngrabPointer(theDisp, CurrentTime);

	/* restore all cells that aren't in curgroup, unless curgroup=0,
	   in which case restore everything...  nasty */
	for (i=0; i<numcols; i++) {
	  if (!curgroup || cellgroup[i]!=curgroup) {
	    rcmap[i] = tmpcmap.r[i];
	    gcmap[i] = tmpcmap.g[i];
	    bcmap[i] = tmpcmap.b[i];
	  }
	}

	if (recolor) {
	  /* colors changed.  save to color undo area */
	  bcopy(&tmpcmap, &prevcmap, sizeof(struct cmapstate));
	  BTSetActive(&gbut[G_BCOLUNDO],1);
	  applyGamma();   /* have to regen entire image when groupings chg */
	}

      } /* if i<numcols */
    } /* if but==3 */

  } /* if PTINRECT */
}




/***************************************************/
static int deladdCell(cnum, first)
int cnum, first;
{
  int i,j,rv;
  static int mode;

#define ADDORDEL 0
#define DELONLY  1
#define ADDONLY  2

  /* if 'first', we can add/delete cells as appropriate.  otherwise,
     the (static) value of 'mode' determines whether we can
     delete or add cells to groups.  The practical upshot is that it should
     behave like the 'fat-bits pencil' in MacPaint */

  /* cases:  curgroup>0, clicked on something in same group
                         remove target from group
	     curgroup>0, clicked on something in different group
	                 merge groups.  (target group gets 
			 set equal to current values)
             curgroup>0, clicked on something in no group
	                 add target to curgroup
             curgroup=0, clicked on something in a group
	                 add editColor to target group, 
			 set curgroup = target group
			 target group gets current values
	     curgroup=0, clicked on something in no group
	                 create a new group, add both cells to it
   */


  rv = 0;
  if (first) mode = ADDORDEL;

  if (curgroup) {
    if ((mode!=ADDONLY) && cellgroup[cnum] == curgroup) {
      /* remove target from curgroup.  If it's the last one, delete group */

      for (i=0,j=0; i<numcols; i++) {     /* count #cells in this group */
	if (cellgroup[i] == curgroup) j++;
      }

      if (j>1) {  /* remove target cell from group */
	cellgroup[cnum] = 0;
	selectCell(cnum,0);
	mode = DELONLY;
	if (cnum==editColor) { /* set editColor to first cell in group */
	  for (i=0; i<numcols && cellgroup[i]!=curgroup; i++);
	  if (i<numcols) editColor = i;
	}
      }
      else {  /* last cell in group.  set to group=0, but don't unhighlight */
	cellgroup[cnum] = 0;
	curgroup = 0;
      }
    }

    else if ((mode!=DELONLY) && cellgroup[cnum] != curgroup && 
	     cellgroup[cnum]>0) {
      /* merge clicked-on group into curgroup */
      mode = ADDONLY;
      rv = 1;  j = cellgroup[cnum];
      for (i=0; i<numcols; i++) {
	if (cellgroup[i] == j) {
	  cellgroup[i] = curgroup;
	  selectCell(i,1);
	  rcmap[i] = rcmap[editColor];
	  gcmap[i] = gcmap[editColor];
	  bcmap[i] = bcmap[editColor]; 
	}
      }
    }
	    
    else if ((mode!=DELONLY) && cellgroup[cnum] == 0) {
      /* merge clicked-on cell into curgroup */
      mode = ADDONLY;
      rv = 1;
      cellgroup[cnum] = curgroup;
      selectCell(cnum,1);
      rcmap[cnum] = rcmap[editColor];
      gcmap[cnum] = gcmap[editColor];
      bcmap[cnum] = bcmap[editColor]; 
    }
  }

  else {  /* !curgroup */
    if ((mode!=DELONLY) && cellgroup[cnum] != 0) {
      /* merge editColor into clicked-on group */
      /* clicked on group, however, takes values of editCell */
      mode = ADDONLY;
      rv = 1;  j = cellgroup[cnum];
      for (i=0; i<numcols; i++) {
	if (cellgroup[i] == j) {
	  selectCell(i,1);
	  rcmap[i] = rcmap[editColor];
	  gcmap[i] = gcmap[editColor];
	  bcmap[i] = bcmap[editColor]; 
	}
      }
      curgroup = cellgroup[cnum];
      cellgroup[editColor] = curgroup;
    }
	    
    else if ((mode!=DELONLY) && (cellgroup[cnum] == 0) 
	     && (cnum != editColor)) {
      /* create new group for these two cells (cnum and editColor) */
      mode = ADDONLY;
      rv = 1;
      maxgroup++;
      cellgroup[cnum] = cellgroup[editColor] = maxgroup;
      selectCell(cnum,1);
      curgroup = maxgroup;
      rcmap[cnum] = rcmap[editColor];
      gcmap[cnum] = gcmap[editColor];
      bcmap[cnum] = bcmap[editColor];
    }
  }

  return rv;
}	    
	    

/*********************/
void ChangeEC(num)
int num;
{
  /* given a color # that is to become the new editColor, do all 
     highlighting/unhighlighting, copy editColor's rgb values to
     the rgb/hsv dials */

  int i,oldgroup;

  oldgroup = curgroup;

  if (curgroup && cellgroup[num] != curgroup) {
    /* a group is currently selected, and we're picking a new cell that
       isn't in the group, have to unhighlight entire group */

    for (i=0; i<numcols; i++) {
      if (cellgroup[i] == curgroup) selectCell(i,0);
    }
  }
  else if (!curgroup) selectCell(editColor,0);

  editColor = num;
  curgroup = cellgroup[editColor];

  if (curgroup && curgroup != oldgroup) {
    /* if new cell is in a group, highlight that group */
    for (i=0; i<numcols; i++) {
      if (cellgroup[i] == curgroup) selectCell(i,1);
    }
  }
  else if (!curgroup) selectCell(editColor,1);


  if (hsvmode) {
    double h, s, v;
    rgb2hsv(rcmap[editColor], gcmap[editColor], bcmap[editColor], &h, &s, &v);
    if (h<0) h = 0;

    DSetVal(&rhDial, (int) h);
    DSetVal(&gsDial, (int) (s*100));
    DSetVal(&bvDial, (int) (v*100));
  }
  else {
    DSetVal(&rhDial, rcmap[editColor]);
    DSetVal(&gsDial, gcmap[editColor]);
    DSetVal(&bvDial, bcmap[editColor]);
  }
}
  
    
/*********************/
void ApplyECctrls()
{
  /* sets values of {r,g,b}cmap[editColor] based on dial settings */

  if (hsvmode) {
    int rv, gv, bv;
    hsv2rgb((double) rhDial.val, ((double) gsDial.val) / 100.0, 
	    ((double) bvDial.val) / 100.0, &rv, &gv, &bv);
    rcmap[editColor] = rv;
    gcmap[editColor] = gv;
    bcmap[editColor] = bv;
  }
  else {
    rcmap[editColor] = rhDial.val;
    gcmap[editColor] = gsDial.val;
    bcmap[editColor] = bvDial.val;
  }
}



/*********************/
void GenerateFSGamma()
{
  /* this function generates the Floyd-Steinberg gamma curve (fsgamcr)

     This function generates a 4 point spline curve to be used as a 
     non-linear grey 'colormap'.  Two of the points are nailed down at 0,0
     and 255,255, and can't be changed.  You specify the other two.  If
     you specify points on the line (0,0 - 255,255), you'll get the normal
     linear reponse curve.  If you specify points of 50,0 and 200,255, you'll
     get grey values of 0-50 to map to black (0), and grey values of 200-255
     to map to white (255) (roughly).  Values between 50 and 200 will cover
     the output range 0-255.  The reponse curve will be slightly 's' shaped. */

  int i,j;
  static int x[4] = {0,32,224,255};
  static int y[4] = {0, 0,255,255};
  double yf[4];

  InitSpline(x, y, 4, yf);
  
  for (i=0; i<256; i++) {
    j = (int) EvalSpline(x, y, yf, 4, (double) i);
    if (j<0) j=0;
    else if (j>255) j=255;
    fsgamcr[i] = j;
  }
}


/*********************/
static void doCmd(cmd)
int cmd;
{
  int i;
  GRAF_STATE gs;

  switch (cmd) {

  case G_BAPPLY: 
    if (enabCB.val != 1) { enabCB.val = 1;  CBRedraw(&enabCB); }
    applyGamma();           
    break;

  case G_BNOGAM:
    if (enabCB.val != 0) { enabCB.val = 0;  CBRedraw(&enabCB); }
    applyGamma();           
    break;

  case G_BUNDO:  gamUndo();  break;
  case G_BREDO:  gamRedo();  break;
  case G_BCLOSE: GamBox(0);  break;


  case G_BHISTEQ: {
    int histeq[256];

    calcHistEQ(histeq);
    
    for (i=0; i<256; i++) 
      intGraf.func[i] = histeq[i];
    
    for (i=0; i< intGraf.nhands; i++)
      intGraf.hands[i].y = intGraf.func[intGraf.hands[i].x];
    
    intGraf.entergamma = 0;
    XClearWindow(theDisp, intGraf.gwin);
    RedrawGraf(&intGraf, 0, 0, 200, 200);
    changedGam();
  }
    break;



  case G_BDN_BR: 
  case G_BUP_BR: GetGrafState(&intGraf, &gs);
                 for (i=0; i < gs.nhands; i++) {
		   if (cmd==G_BUP_BR) gs.hands[i].y += 10;
		                 else gs.hands[i].y -= 10;
		   RANGE(gs.hands[i].y, 0, 255);
		 }
                 SetGrafState(&intGraf, &gs);
                 changedGam();
                 break;


  case G_BUP_CN: GetGrafState(&intGraf, &gs);
                 for (i=0; i < gs.nhands; i++) {
		   if (gs.hands[i].x < 128) gs.hands[i].y -= 10;
		                       else gs.hands[i].y += 10;
		   RANGE(gs.hands[i].y, 0, 255);
		 }
                 SetGrafState(&intGraf, &gs);
                 changedGam();
                 break;

  case G_BDN_CN: GetGrafState(&intGraf, &gs);
                 for (i=0; i < gs.nhands; i++) {
		   if (gs.hands[i].y < 128) {
		     gs.hands[i].y += 10;
		     if (gs.hands[i].y > 128) gs.hands[i].y = 128;
		   }
		   else {
		     gs.hands[i].y -= 10;
		     if (gs.hands[i].y < 128) gs.hands[i].y = 128;
		   }
		 }
                 SetGrafState(&intGraf, &gs);
                 changedGam();
                 break;


  case G_BMAXCONT: { int minv, maxv, v;
		     minv = 255;  maxv = 0;
		     for (i=0; i<numcols; i++) {
		       v = MONO(rcmap[i],gcmap[i],bcmap[i]);
		       if (v<minv) minv = v;
		       if (v>maxv) maxv = v;
		     }

		     gs.spline = 0;
		     gs.entergamma = 0;
		     gs.gammamode = 0;
		     gs.nhands = 4;
		     gs.hands[0].x = 0;       gs.hands[0].y = 0;
		     gs.hands[1].x = minv;    gs.hands[1].y = 0;
		     gs.hands[2].x = maxv;    gs.hands[2].y = 255;
		     gs.hands[3].x = 255;     gs.hands[3].y = 255;

		     if (minv<1)   { gs.hands[1].x = gs.hands[1].y = 1; }
		     if (maxv>254) { gs.hands[2].x = gs.hands[2].y = 254; }

		     SetGrafState(&intGraf, &gs);
		     changedGam();
		   }
                   break;

  case G_BRESET:
  case G_B1:
  case G_B2:
  case G_B3:
  case G_B4: { struct gamstate *ptr;

	       if      (cmd==G_B1)     ptr = &preset[0];
	       else if (cmd==G_B2)     ptr = &preset[1];
	       else if (cmd==G_B3)     ptr = &preset[2];
	       else if (cmd==G_B4)     ptr = &preset[3];
	       else if (cmd==G_BRESET) ptr = &defstate;
                
	       if (gbut[G_BSET].lit) {
		 save_gstate(ptr);
		 gbut[G_BSET].lit = 0;
		 BTRedraw(&gbut[G_BSET]);
	       }
	       else restore_gstate(ptr);
	     }
             break;

  case G_BSET:  break;


  case G_BHSVRGB:
    hsvmode = !hsvmode;
    SetHSVmode();
    saveGamState();
    break;


  case G_BCOLREV: 
    {
      struct cmapstate tmp1cmap;
      int gchg;

      for (i=0; i<numcols && prevcmap.cellgroup[i]==0; i++);
      gchg = (i!=numcols);

      saveCMap(&tmpcmap);         /* buffer current cmapstate */
    
      for (i=0; i<numcols; i++) { /* do reversion */
	rcmap[i] = rorg[i];  
	gcmap[i] = gorg[i];
	bcmap[i] = borg[i];
	cellgroup[i] = 0;
      }
      curgroup = maxgroup = 0;

      saveCMap(&tmp1cmap);        /* buffer current cmapstate */
    
      /* prevent multiple 'Undo All's from filling Undo buffer */
      if (bcmp(&tmpcmap, &tmp1cmap, sizeof(struct cmapstate))) {
	/* the reversion changed the cmapstate */
	bcopy(&tmpcmap, &prevcmap, sizeof(struct cmapstate));
	BTSetActive(&gbut[G_BCOLUNDO],1);

	RedrawCMap();
	ChangeEC(editColor);
	applyGamma();
	if (gchg) ApplyEditColor(1);
      }
    }
    break;


  case G_BRNDCOL:
    saveCMap(&prevcmap);
    BTSetActive(&gbut[G_BCOLUNDO],1);
    rndCols();
    break;
  
  case G_BRV:
    saveCMap(&prevcmap);
    BTSetActive(&gbut[G_BCOLUNDO],1);

    if (hsvmode) {              /* reverse video in HSV space (flip V only) */
      double h,s,v;   int rr, gr, br;
      for (i=0; i<numcols; i++) {
	rgb2hsv(rcmap[i], gcmap[i], bcmap[i], &h, &s, &v);

	v = 1.0 - v;
	if (v <= .05) v = .05;

	hsv2rgb(h, s, v, &rr, &gr, &br);
	rcmap[i] = rr;  gcmap[i] = gr;  bcmap[i] = br;
      }
    }
    else {
      for (i=0; i<numcols; i++) {
	rcmap[i] = 255-rcmap[i];
	gcmap[i] = 255-gcmap[i];
	bcmap[i] = 255-bcmap[i];
      }
    }
    ChangeEC(editColor);
    applyGamma();
    break;
  

  case G_BMONO:
    saveCMap(&prevcmap);
    BTSetActive(&gbut[G_BCOLUNDO],1);
    for (i=0; i<numcols; i++) {
      rcmap[i] = gcmap[i] = bcmap[i] = MONO(rcmap[i],gcmap[i],bcmap[i]);
    }
    ChangeEC(editColor);
    applyGamma();
    break;
  

  case G_BCOLUNDO:
    for (i=0; i<numcols && cellgroup[i]==prevcmap.cellgroup[i]; i++);

    saveCMap(&tmpcmap);
    restoreCMap(&prevcmap);
    bcopy(&tmpcmap, &prevcmap, sizeof(struct cmapstate));
    RedrawCMap();
    ChangeEC(editColor);
    applyGamma();
    if (i!=numcols) ApplyEditColor(1);
    break;

  case G_BGETRES:   makeResources();  break;
  }
}


/*********************/
static void SetHSVmode()
{
  if (!hsvmode) {
    rhDial.title = "Red";
    gsDial.title = "Green";
    bvDial.title = "Blue";
		   
    DSetRange(&rhDial, 0, 255, rcmap[editColor], 16);
    DSetRange(&gsDial, 0, 255, gcmap[editColor], 16);
    DSetRange(&bvDial, 0, 255, bcmap[editColor], 16);

    XClearWindow(theDisp, rhDial.win);    DRedraw(&rhDial);
    XClearWindow(theDisp, gsDial.win);    DRedraw(&gsDial);
    XClearWindow(theDisp, bvDial.win);    DRedraw(&bvDial);
  }

  else {
    double h,s,v;

    rhDial.title = "Hue";
    gsDial.title = "Sat.";
    bvDial.title = "Value";

    rgb2hsv(rcmap[editColor], gcmap[editColor], bcmap[editColor],
	    &h, &s, &v);

    if (h<0.0) h = 0.0;
    DSetRange(&rhDial, 0, 360, (int) h, 5);
    DSetRange(&gsDial, 0, 100, (int) (s*100), 5);
    DSetRange(&bvDial, 0, 100, (int) (v*100), 5);

    XClearWindow(theDisp, rhDial.win);    DRedraw(&rhDial);
    XClearWindow(theDisp, gsDial.win);    DRedraw(&gsDial);
    XClearWindow(theDisp, bvDial.win);    DRedraw(&bvDial);
  }
}


/*********************/
static void applyGamma()
{
  /* called to regenerate the image based on r/g/bcmap or output of rgb/hsv
     filters.  Doesn't check autoCB.  */

  int i,j;
  byte oldr[256], oldg[256], oldb[256];

  /* save current 'desired' colormap */
  bcopy(r, oldr, numcols);
  bcopy(g, oldg, numcols);
  bcopy(b, oldb, numcols);

  GammifyColors();

  /* if current 'desired' colormap hasn't changed, don't DO anything */
  if (!bcmp(r, oldr, numcols) && 
      !bcmp(g, oldg, numcols) && 
      !bcmp(b, oldb, numcols)) return;



  /* special case: if using R/W color, just modify the colors and leave */
  if (rwcolor && rwthistime) {
    XColor ctab[256];

    for (i=0; i<nfcols; i++) {
      j = fc2pcol[i];
      if (mono) {
	int intens = MONO(r[j], g[j], b[j]);
	ctab[i].red = ctab[i].green = ctab[i].blue = intens<<8;
      }
      else {
	ctab[i].red   = r[j]<<8;
	ctab[i].green = g[j]<<8;
	ctab[i].blue  = b[j]<<8;
      }

      ctab[i].pixel = freecols[i];
      ctab[i].flags = DoRed | DoGreen | DoBlue;
      XStoreColor(theDisp, LocalCmap ? LocalCmap : theCmap, &ctab[i]);
    }
    XStoreColor(theDisp, LocalCmap ? LocalCmap : theCmap, &ctab[0]);

    for (i=0; i<numcols; i++) {
      rdisp[colAllocOrder[i]] = r[rwpc2pc[i]];
      gdisp[colAllocOrder[i]] = g[rwpc2pc[i]];
      bdisp[colAllocOrder[i]] = b[rwpc2pc[i]];
    }

    return;
  }
    

  FreeAllColors();
#ifdef FOO
  if (theVisual->class & 1 && ncols>0) XClearWindow(theDisp,mainW);
#endif

  SortColormap();
  AllocColors();
  CreateXImage();

  if (useroot) MakeRootPic();
          else DrawWindow(0,0,eWIDE,eHIGH);
  if (but[BCROP].active) InvCropRect();
  SetCursors(-1);
}


/*********************/
static void calcHistEQ(histeq)
     int *histeq;
{
  int i, maxv, topbin, hist[256], rgb[256];
  byte *ep;
  unsigned long total;

  for (i=0; i<256; i++) {
    hist[i] = 0;
    rgb[i] = MONO(rcmap[i],gcmap[i],bcmap[i]);
  }

  /* compute intensity histogram */
  for (i=eWIDE*eHIGH,ep=epic; i>0; i--, ep++) hist[rgb[*ep]]++;

  if (DEBUG) {
    fprintf(stderr,"intensity histogram:  ");
    for (i=0; i<256; i++) fprintf(stderr,"%d ", hist[i]);
    fprintf(stderr,"\n\n");
  }

  /* compute histeq curve */
  total = topbin = 0;
  for (i=0; i<256; i++) {
    histeq[i] = (total * 255) / ((unsigned long) eWIDE * eHIGH);
    if (hist[i]) topbin = i;
    total += hist[i];
  }

  /* stretch range, as histeq[255] is probably *not* equal to 255 */
  maxv = (histeq[topbin]) ? histeq[topbin] : 255;   /* avoid div by 0 */
  for (i=0; i<256; i++)
    histeq[i] = (histeq[i] * 255) / maxv;

  if (DEBUG) {
    fprintf(stderr,"intensity eq curve:  ");
    for (i=0; i<256; i++) fprintf(stderr,"%d ", histeq[i]);
    fprintf(stderr,"\n\n");
  }

  /* play it safe:  do a range check on histeq */
  for (i=0; i<256; i++) RANGE(histeq[i],0,255);
}


/*********************/
void GammifyColors()
{
  int i;

  if (enabCB.val) {
    for (i=0; i<numcols; i++) Gammify1(i);
  }
  else {
    for (i=0; i<numcols; i++) { 
      r[i] = rcmap[i];
      g[i] = gcmap[i];
      b[i] = bcmap[i];
    }
  }
}


#define NOHUE -1

/*********************/
void Gammify1(col)
int col;
{
  int rv, gv, bv, vi, hi;
  double h,s,v;

  rv = rcmap[col];  gv = gcmap[col];  bv = bcmap[col];
  if (DEBUG>1) fprintf(stderr,"Gammify:  %d,%d,%d",rv,gv,bv);

  rgb2hsv(rv, gv, bv, &h, &s, &v);
  if (DEBUG>1) fprintf(stderr," -> %f,%f,%f",h,s,v);

  /* map near-black to black to avoid weird effects */
  if (v <= .0625) s = 0.0;

  /* apply intGraf.func[] function to 'v' (the intensity) */
  vi = (int) floor((v * 255.0) + 0.5);
  if (DEBUG>1) fprintf(stderr," (vi=%d)",vi);

  v = intGraf.func[vi] / 255.0;
  if (DEBUG>1) fprintf(stderr," (v=%f)",v);

  if (h>=0) {
    hi = (int) h;  
    if (hi<0)    hi += 360;
    if (hi>=360) hi -= 360;
    h = (double) hremap[hi];
  }
  else {
    if (whtHD.enabCB.val) {
      h = (double) whtHD.stval;
      s = (double) whtHD.satval / 100.0;

      /* special case:  if stval = satval = 0, set hue = -1 */
      if (whtHD.stval == 0 && whtHD.satval == 0) h = -1.0;
    }
  }

  /* apply satDial value to s */
  s = s + ((double) satDial.val) / 100.0;
  if (s<0.0) s = 0.0;
  if (s>1.0) s = 1.0;

  hsv2rgb(h,s,v,&rv, &gv, &bv);
  if (DEBUG>1) fprintf(stderr," -> %d,%d,%d",rv,gv,bv);

  r[col] = rGraf.func[rv];  
  g[col] = gGraf.func[gv];
  b[col] = bGraf.func[bv];
  if (DEBUG>1) fprintf(stderr," -> %d,%d,%d\n",r[col],g[col],b[col]);
}


/*********************/
void rgb2hsv(r,g,b, hr, sr, vr)
int r, g, b;
double *hr, *sr, *vr;
{
  double rd, gd, bd, h, s, v, max, min, del, rc, gc, bc;

  /* convert RGB to HSV */
  rd = r / 255.0;            /* rd,gd,bd range 0-1 instead of 0-255 */
  gd = g / 255.0;
  bd = b / 255.0;

  /* compute maximum of rd,gd,bd */
  if (rd>=gd) { if (rd>=bd) max = rd;  else max = bd; }
         else { if (gd>=bd) max = gd;  else max = bd; }

  /* compute minimum of rd,gd,bd */
  if (rd<=gd) { if (rd<=bd) min = rd;  else min = bd; }
         else { if (gd<=bd) min = gd;  else min = bd; }

  del = max - min;
  v = max;
  if (max != 0.0) s = (del) / max;
             else s = 0.0;

  h = NOHUE;
  if (s != 0.0) {
    rc = (max - rd) / del;
    gc = (max - gd) / del;
    bc = (max - bd) / del;

    if      (rd==max) h = bc - gc;
    else if (gd==max) h = 2 + rc - bc;
    else if (bd==max) h = 4 + gc - rc;

    h = h * 60;
    if (h<0) h += 360;
  }

  *hr = h;  *sr = s;  *vr = v;
}



/*********************/
void hsv2rgb(h, s, v, rr, gr, br)
double h, s, v;
int *rr, *gr, *br;
{
  int    j;
  double rd, gd, bd;
  double f, p, q, t;

  /* convert HSV back to RGB */
  if (h==NOHUE || s==0.0) { rd = v;  gd = v;  bd = v; }
  else {
    if (h==360.0) h = 0.0;
    h = h / 60.0;
    j = (int) floor(h);
    f = h - j;
    p = v * (1-s);
    q = v * (1 - (s*f));
    t = v * (1 - (s*(1 - f)));

    switch (j) {
    case 0:  rd = v;  gd = t;  bd = p;  break;
    case 1:  rd = q;  gd = v;  bd = p;  break;
    case 2:  rd = p;  gd = v;  bd = t;  break;
    case 3:  rd = p;  gd = q;  bd = v;  break;
    case 4:  rd = t;  gd = p;  bd = v;  break;
    case 5:  rd = v;  gd = p;  bd = q;  break;
    default: rd = v;  gd = t;  bd = p;  break;  /* never happen */
    }
  }

  *rr = (int) floor((rd * 255.0) + 0.5);
  *gr = (int) floor((gd * 255.0) + 0.5);
  *br = (int) floor((bd * 255.0) + 0.5);
}



/*********************/
static void save_gstate(gs)
struct gamstate *gs;
{
  bcopy(hmap, gs->hmap, sizeof(hmap));

  gs->wht_stval = whtHD.stval;  
  gs->wht_satval = whtHD.satval;  
  gs->wht_enab = whtHD.enabCB.val;

  gs->hueRBnum = RBWhich(hueRB);

  gs->satval = satDial.val;
  GetGrafState(&intGraf,&gs->istate);
  GetGrafState(&rGraf,  &gs->rstate);
  GetGrafState(&gGraf,  &gs->gstate);
  GetGrafState(&bGraf,  &gs->bstate);
}


/*********************/
static void restore_gstate(gs)
struct gamstate *gs;
{
  int changed = 0;
  struct hmap *hm;

  if (gs->hueRBnum != RBWhich(hueRB)) {
    RBSelect(hueRB, gs->hueRBnum);
    changed++;
  }

  if (bcmp(hmap, gs->hmap, sizeof(hmap))) {   /* hmap has changed */
    bcopy(gs->hmap, hmap, sizeof(hmap));
    build_hremap();
    changed++;

    hm = &(gs->hmap[gs->hueRBnum]);

    if (srcHD.stval  != hm->src_st ||
	srcHD.enval  != hm->src_en ||
	srcHD.ccwise != hm->src_ccw) {
      srcHD.stval  = hm->src_st;
      srcHD.enval  = hm->src_en;
      srcHD.ccwise = hm->src_ccw;
      HDRedraw(&srcHD, HD_ALL | HD_CLEAR);
    }
    
    if (dstHD.stval  != hm->dst_st ||
	dstHD.enval  != hm->dst_en ||
	dstHD.ccwise != hm->dst_ccw) {
      dstHD.stval  = hm->dst_st;
      dstHD.enval  = hm->dst_en;
      dstHD.ccwise = hm->dst_ccw;
      HDRedraw(&dstHD, HD_ALL | HD_CLEAR);
    }
  }    


  if (whtHD.stval != gs->wht_stval || whtHD.satval != gs->wht_satval ||
      whtHD.enabCB.val != gs->wht_enab) {
    whtHD.stval  = gs->wht_stval;
    whtHD.satval  = gs->wht_satval;
    whtHD.enabCB.val = gs->wht_enab;
    CBRedraw(&whtHD.enabCB);
    HDRedraw(&whtHD, HD_ALL | HD_CLEAR);
    changed++;
  }
    
  if (gs->satval != satDial.val) {
    DSetVal(&satDial,gs->satval);
    changed++;
  }

  if (SetGrafState(&intGraf, &gs->istate)) changed++;
  if (SetGrafState(&rGraf,   &gs->rstate)) changed++;
  if (SetGrafState(&gGraf,   &gs->gstate)) changed++;
  if (SetGrafState(&bGraf,   &gs->bstate)) changed++;

  if (changed) changedGam();
}


static int nosave_kludge = 0;

/*********************/
static void saveGamState()
{
  /* increment uptr, sticks current state into uptr
     set utail = uptr
     If utail==uhead, increment uhead (buffer full, throw away earliest)
   */

  if (nosave_kludge) return;

  uptr = (uptr+1) % MAXUNDO;
  save_gstate(&undo[uptr]);
  utail = uptr;
  if (utail == uhead) uhead = (uhead + 1) % MAXUNDO;

  BTSetActive(&gbut[G_BUNDO],1);
  BTSetActive(&gbut[G_BREDO],0);
}



/*********************/
static void gamUndo()
{
  /* if uptr!=uhead  decements uptr, restores state pointed to by uptr
                     if uptr now == uhead, turn off 'Undo' button
   */

  if (uptr != uhead) {   /* this should always be true when gamUndo called */
    uptr = (uptr + MAXUNDO - 1) % MAXUNDO;
    nosave_kludge = 1;
    restore_gstate(&undo[uptr]);
    nosave_kludge = 0;
    if (uptr == uhead) BTSetActive(&gbut[G_BUNDO],0);
    if (uptr != utail) BTSetActive(&gbut[G_BREDO],1);
  }
}



/*********************/
static void gamRedo()
{
  /* if uptr != utail   increments uptr, restores state pointed to by uptr
                        if uptr now == utail, turn off 'Redo' button */

  if (uptr != utail) {   /* this should always be true when gamRedo called */
    uptr = (uptr + 1) % MAXUNDO;
    nosave_kludge = 1;
    restore_gstate(&undo[uptr]);
    nosave_kludge = 0;
    if (uptr != uhead) BTSetActive(&gbut[G_BUNDO],1);
    if (uptr == utail) BTSetActive(&gbut[G_BREDO],0);
  }
}




/*********************/
static void rndCols()
{
  int i,j;

  for (i=0; i<numcols; i++) {
    if (cellgroup[i]) {
      /* determine if this group's already been given a random color */
      for (j=0; j<i && cellgroup[i] != cellgroup[j]; j++);
      if (j<i) {  /* it has */
	rcmap[i] = rcmap[j];  gcmap[i] = gcmap[j];  bcmap[i] = bcmap[j];
	continue;
      }
    }

    rcmap[i] = random()&0xff;
    gcmap[i] = random()&0xff;
    bcmap[i] = random()&0xff;
  }

  ChangeEC(editColor);
  applyGamma();
}



/*********************/
static void saveCMap(cst)
struct cmapstate *cst;
{
  int i;

  for (i=0; i<256; i++) {
    cst->r[i] = rcmap[i];
    cst->g[i] = gcmap[i];
    cst->b[i] = bcmap[i];
    cst->cellgroup[i] = cellgroup[i];
  }

  cst->curgroup  = curgroup;
  cst->maxgroup  = maxgroup;
  cst->editColor = editColor;
}


/*********************/
static void restoreCMap(cst)
struct cmapstate *cst;
{
  int i;

  for (i=0; i<256; i++) {
    rcmap[i] = cst->r[i];
    gcmap[i] = cst->g[i];
    bcmap[i] = cst->b[i];
    cellgroup[i] = cst->cellgroup[i];
  }

  curgroup = cst->curgroup;
  maxgroup = cst->maxgroup;
  editColor = cst->editColor;
}


    

/*********************/
static void parseResources()
{
  char gname[80], tmp[80], tmp1[256];
  int  i,j;
  struct gamstate *gsp, gs;


  /* look for the simple coledit-related resources */
  if (rd_flag("autoApply"))   autoCB.val = def_int;
  if (rd_flag("displayMods")) enabCB.val = def_int;
  if (rd_flag("autoReset"))   resetCB.val = def_int;


  /* look for preset/default resources */
  for (i=0; i<5; i++) {
    if (i) { sprintf(gname,"preset%d",i);  gsp = &preset[i-1]; }
      else { sprintf(gname,"default");     gsp = &defstate; }

    bcopy(gsp, &gs, sizeof(struct gamstate));   /* load 'gs' with defaults */

    for (j=0; j<6; j++) {                       /* xv.*.huemap resources */
      sprintf(tmp, "%s.huemap%d", gname, j+1);
      if (rd_str_cl(tmp, "Setting.Huemap")) {   /* got one */
	int fst, fen, tst, ten;
	char fcw[32], tcw[32];

	if (DEBUG) fprintf(stderr,"parseResource 'xv.%s: %s'\n",tmp, def_str);
	lower_str(def_str);
	if (sscanf(def_str,"%d %d %s %d %d %s",
		   &fst, &fen, fcw, &tst, &ten, tcw) != 6) {
	  fprintf(stderr,"%s: unable to parse resource 'xv.%s: %s'\n", 
		  cmd, tmp, def_str);
	}
	else {
	  RANGE(fst, 0, 359);   RANGE(fen, 0, 359);
	  RANGE(tst, 0, 359);   RANGE(ten, 0, 359);
	  gs.hmap[j].src_st  = fst;
	  gs.hmap[j].src_en  = fen;
	  gs.hmap[j].src_ccw = (strcmp(fcw,"ccw") == 0);
	  gs.hmap[j].dst_st  = tst;
	  gs.hmap[j].dst_en  = ten;
	  gs.hmap[j].dst_ccw = (strcmp(tcw,"ccw") == 0);
	}
      }
    }

    sprintf(tmp, "%s.whtmap", gname);           /* xv.*.whtmap resource */
    if (rd_str_cl(tmp, "Setting.Whtmap")) {        /* got one */
      int wst, wsat, enab;
      if (DEBUG) fprintf(stderr,"parseResource 'xv.%s: %s'\n",tmp, def_str);
      if (sscanf(def_str,"%d %d %d", &wst, &wsat, &enab) != 3) {
	fprintf(stderr,"%s: unable to parse resource 'xv.%s: %s'\n", 
		cmd, tmp, def_str);
      }
      else {                                    /* successful parse */
	RANGE(wst, 0, 359);  RANGE(wsat, 0, 100);
	gs.wht_stval  = wst;
	gs.wht_satval = wsat;
	gs.wht_enab   = enab;
      }
    }

    sprintf(tmp, "%s.satval", gname);           /* xv.*.satval resource */
    if (rd_str_cl(tmp, "Setting.Satval")) {         /* got one */
      int sat;
      if (DEBUG) fprintf(stderr,"parseResource 'xv.%s: %s'\n",tmp, def_str);
      if (sscanf(def_str,"%d", &sat) != 1) {
	fprintf(stderr,"%s: unable to parse resource 'xv.%s: %s'\n", 
		cmd, tmp, def_str);
      }
      else {                                    /* successful parse */
	RANGE(sat, -100, 100);
	gs.satval = sat;
      }
    }

    for (j=0; j<4; j++) {                       /* xv.*.*graf resources */
      GRAF_STATE gstat, *gsgst;
      switch (j) {
      case 0: sprintf(tmp, "%s.igraf", gname);  gsgst = &gs.istate; break;
      case 1: sprintf(tmp, "%s.rgraf", gname);  gsgst = &gs.rstate; break;
      case 2: sprintf(tmp, "%s.ggraf", gname);  gsgst = &gs.gstate; break;
      case 3: sprintf(tmp, "%s.bgraf", gname);  gsgst = &gs.bstate; break;
      default: sprintf(tmp, "%s.bgraf", gname);  gsgst = &gs.bstate; break;
      }

      if (rd_str_cl(tmp, "Setting.Graf")) {       /* got one */
	strcpy(tmp1, def_str);
	bcopy(gsgst, &gstat, sizeof(GRAF_STATE));
	if (DEBUG) fprintf(stderr,"parseResource 'xv.%s: %s'\n",tmp, tmp1);
	if (!Str2Graf(&gstat, tmp1)) {            /* successful parse */
	  bcopy(&gstat, gsgst, sizeof(GRAF_STATE));
	}
      }
    }
    
    /* copy (potentially) modified gs back to default/preset */
    bcopy(&gs, gsp, sizeof(struct gamstate));
  }
}


/*********************/
static void makeResources()
{
  char rsrc[2000];     /* wild over-estimation */
  char gname[40], rname[64], tmp[256], tmp1[256];
  struct gamstate gstate;
  int i;

  rsrc[0] = '\0';

  /* write out current state */
  save_gstate(&gstate);
  strcpy(gname, "xv.default");
  
  /* write out huemap resources */
  for (i=0; i<6; i++) {
    if (1 || gstate.hmap[i].src_st  != gstate.hmap[i].dst_st ||
	gstate.hmap[i].src_en  != gstate.hmap[i].dst_en ||
	gstate.hmap[i].src_ccw != gstate.hmap[i].dst_ccw) {
      sprintf(tmp, "%s.huemap%d: %3d %3d %3s %3d %3d %3s\n", gname, i+1, 
	      gstate.hmap[i].src_st, gstate.hmap[i].src_en, 
	      gstate.hmap[i].src_ccw ? "CCW" : "CW",
	      gstate.hmap[i].dst_st, gstate.hmap[i].dst_en, 
	      gstate.hmap[i].dst_ccw ? "CCW" : "CW");
      strcat(rsrc, tmp);
    }
  }

  /* write out whtmap resource */
  if (1 || gstate.wht_stval || gstate.wht_satval || gstate.wht_enab != 1) {
    sprintf(tmp, "%s.whtmap:  %d %d %d\n", gname, gstate.wht_stval, 
	    gstate.wht_satval, gstate.wht_enab);
    strcat(rsrc, tmp);
  }

  /* write out satval resource */
  if (1 || gstate.satval) {
    sprintf(tmp, "%s.satval:  %d\n", gname, gstate.satval);
    strcat(rsrc, tmp);
  }


  /* write out graf resources */
  for (i=0; i<4; i++) {
    GRAF_STATE *gfstat;
    switch (i) {
    case 0: sprintf(rname, "%s.igraf", gname);  gfstat = &gstate.istate; break;
    case 1: sprintf(rname, "%s.rgraf", gname);  gfstat = &gstate.rstate; break;
    case 2: sprintf(rname, "%s.ggraf", gname);  gfstat = &gstate.gstate; break;
    case 3: sprintf(rname, "%s.bgraf", gname);  gfstat = &gstate.bstate; break;
    default:
      sprintf(rname, "%s.bgraf", gname);  gfstat = &gstate.bstate; break;
    }

    Graf2Str(gfstat, tmp1);
    sprintf(tmp, "%s: %s\n", rname, tmp1);
    strcat(rsrc, tmp);
  }
  XStoreBytes(theDisp, rsrc, strlen(rsrc));
}
    
    
    
        

/**********************************************/
/*************  HUE wheel functions ***********/
/**********************************************/


static int hdb_pixmaps_built = 0;
static Pixmap hdbpix1[N_HDBUTT];
static Pixmap hdbpix2[N_HDBUTT2];
#define PW 15
#define PH 15

/**************************************************/
static void HDCreate(hd, win, x, y, r, st, en, ccwise, str, fg, bg)
HDIAL *hd;
Window win;
int x,y,r,st,en,ccwise;
char *str;
u_long fg,bg;
{
  int i;

  hd->win    = win;
  hd->x      = x;
  hd->y      = y;
  hd->range  = r;
  hd->stval  = st;
  hd->enval  = en;
  hd->satval = 0;
  hd->ccwise = ccwise;
  hd->str    = str;
  hd->fg     = fg;
  hd->bg     = bg;

  if (!hdb_pixmaps_built) {
    hdbpix1[HDB_ROTL]   = XCreatePixmapFromBitmapData(theDisp, win,
                                    h_rotl_bits, PW,PH, 1, 0, 1);
    hdbpix1[HDB_ROTR]   = XCreatePixmapFromBitmapData(theDisp, win,
                                    h_rotr_bits, PW,PH, 1, 0, 1);
    hdbpix1[HDB_FLIP]   = XCreatePixmapFromBitmapData(theDisp, win,
                                    h_flip_bits, PW,PH, 1, 0, 1);
    hdbpix1[HDB_EXPND]  = XCreatePixmapFromBitmapData(theDisp, win,
                                    h_sinc_bits, PW,PH, 1, 0, 1);
    hdbpix1[HDB_SHRNK]  = XCreatePixmapFromBitmapData(theDisp, win,
                                    h_sdec_bits, PW,PH, 1, 0, 1);

    hdbpix2[HDB_SAT]    = XCreatePixmapFromBitmapData(theDisp, win,
                                    h_sat_bits, PW,PH, 1, 0, 1);
    hdbpix2[HDB_DESAT]  = XCreatePixmapFromBitmapData(theDisp, win,
                                    h_desat_bits, PW,PH, 1, 0, 1);

    hdbpix2[HDB_ROTL]  = hdbpix1[HDB_ROTL];
    hdbpix2[HDB_ROTR]  = hdbpix1[HDB_ROTR];
  }

    
  if (hd->range) {
    BTCreate(&hd->hdbutt[HDB_ROTL], win, x-50,y+60,18,18,NULL,fg,bg);
    BTCreate(&hd->hdbutt[HDB_ROTR], win, x-30,y+60,18,18,NULL,fg,bg);
    BTCreate(&hd->hdbutt[HDB_FLIP], win, x-10,y+60,18,18,NULL,fg,bg);
    BTCreate(&hd->hdbutt[HDB_EXPND],win, x+10,y+60,18,18,NULL,fg,bg);
    BTCreate(&hd->hdbutt[HDB_SHRNK],win, x+30,y+60,18,18,NULL,fg,bg);

    for (i=0; i<N_HDBUTT; i++) {
      hd->hdbutt[i].pix = hdbpix1[i];
      hd->hdbutt[i].pw = PW;
      hd->hdbutt[i].ph = PH;
    }
  }

  else {
    BTCreate(&hd->hdbutt[HDB_ROTL], win, x-39,y+60,18,18,NULL,fg,bg);
    BTCreate(&hd->hdbutt[HDB_ROTR], win, x-19,y+60,18,18,NULL,fg,bg);
    BTCreate(&hd->hdbutt[HDB_DESAT],win, x+1, y+60,18,18,NULL,fg,bg);
    BTCreate(&hd->hdbutt[HDB_SAT],  win, x+21,y+60,18,18,NULL,fg,bg);
    CBCreate(&hd->enabCB, win, x+23, y-44, "", fg, bg);
    hd->enabCB.val = 1;

    for (i=0; i<N_HDBUTT2; i++) {
      hd->hdbutt[i].pix = hdbpix2[i];
      hd->hdbutt[i].pw = PW;
      hd->hdbutt[i].ph = PH;
    }
  }
}


/**************************************************/
static void HDRedraw(hd, flags)
HDIAL *hd;
int flags;
{
  int    i, x, y, x1, y1;
  double a;

  if (flags & HD_CLEAR) {
    XSetForeground(theDisp, theGC, hd->bg);
    XFillArc(theDisp, hd->win, theGC, hd->x-(HD_RADIUS-1),hd->y-(HD_RADIUS-1),
	     (HD_RADIUS-1)*2, (HD_RADIUS-1)*2, 0, 360*64);
  }

  if (flags & HD_FRAME) {
    static char *colstr = "RYGCBM";
    char tstr[2];

    XSetForeground(theDisp, theGC, hd->fg);
    XDrawArc(theDisp, hd->win, theGC, hd->x - HD_RADIUS, hd->y - HD_RADIUS,
	     HD_RADIUS*2, HD_RADIUS*2, 0, 360*64);
    
    for (i=0; i<6; i++) {
      int kldg;

      if (i==2 || i==5) kldg = -1;  else kldg = 0;
      a = hdg2xdg(i*60) * DEG2RAD;
      pol2xy(hd->x, hd->y, a, HD_RADIUS+1,      &x,  &y);
      pol2xy(hd->x, hd->y, a, HD_RADIUS+4+kldg, &x1, &y1);
      XDrawLine(theDisp, hd->win, theGC, x,y,x1,y1);

      tstr[0] = colstr[i];  tstr[1] = '\0';
      pol2xy(hd->x, hd->y, a, HD_RADIUS+10, &x, &y);
      CenterString(hd->win, tstr, x,y);
    }
  }

  if (flags & HD_HANDS || flags & HD_CLHNDS) {
    if (flags & HD_CLHNDS) XSetForeground(theDisp, theGC, hd->bg);
                      else XSetForeground(theDisp, theGC, hd->fg);

    if (hd->range ) {
      if (flags & HD_HANDS)   /* draw center dot */
	XFillRectangle(theDisp, hd->win, theGC, hd->x-1, hd->y-1, 3,3);

      a = hdg2xdg(hd->stval) * DEG2RAD;
      pol2xy(hd->x, hd->y, a, HD_RADIUS - 4, &x, &y);
      XDrawLine(theDisp, hd->win, theGC, hd->x, hd->y, x,y);
      
      if (flags & HD_CLHNDS) 
	XFillRectangle(theDisp, hd->win, theGC, x-2,y-2, 5,5);
      else {
	XSetForeground(theDisp, theGC, hd->bg);
	XFillRectangle(theDisp, hd->win, theGC, x-1, y-1, 3,3);
	XSetForeground(theDisp, theGC, hd->fg);
	XDrawPoint(theDisp, hd->win, theGC, x, y);
	XDrawRectangle(theDisp, hd->win, theGC, x-2, y-2, 4,4);
      }

      a = hdg2xdg(hd->enval) * DEG2RAD;
      pol2xy(hd->x, hd->y, a, HD_RADIUS - 4, &x, &y);
      XDrawLine(theDisp, hd->win, theGC, hd->x, hd->y, x,y);
    
      if (flags & HD_CLHNDS) 
	XFillRectangle(theDisp, hd->win, theGC, x-2,y-2, 5,5);
      else {
	XSetForeground(theDisp, theGC, hd->bg);
	XFillRectangle(theDisp, hd->win, theGC, x-1, y-1, 3,3);
	XSetForeground(theDisp, theGC, hd->fg);
	XDrawPoint(theDisp, hd->win, theGC, x, y);
	XDrawRectangle(theDisp, hd->win, theGC, x-2, y-2, 4,4);
      }
    }

    else {  /* not a range;  hue/sat dial */
      int r;

      /* compute x,y position from stval/satval */
      a = hdg2xdg(hd->stval) * DEG2RAD;
      r = ((HD_RADIUS - 4) * hd->satval) / 100;
      pol2xy(hd->x, hd->y, a, r, &x, &y);

      if (flags & HD_CLHNDS) 
	XFillRectangle(theDisp, hd->win, theGC, x-2,y-2, 5,5);
      else {
	XFillRectangle(theDisp, hd->win, theGC, hd->x-1, hd->y-1, 3,3);

	XSetForeground(theDisp, theGC, hd->bg);
	XFillRectangle(theDisp, hd->win, theGC, x-1, y-1, 3,3);
	XSetForeground(theDisp, theGC, hd->fg);
	XDrawPoint(theDisp, hd->win, theGC, x, y);
	XDrawRectangle(theDisp, hd->win, theGC, x-2, y-2, 4,4);
      }
    }
  }
    



  if ((flags & HD_DIR || flags & HD_CLHNDS) && hd->range) {
    int xdg1, xdg2, xdlen;

    if (flags & HD_CLHNDS) XSetForeground(theDisp, theGC, hd->bg);
                      else XSetForeground(theDisp, theGC, hd->fg);

    if (hd->ccwise) {
      xdg1 = hdg2xdg(hd->stval);
      xdg2 = hdg2xdg(hd->enval);
    }
    else {
      xdg1 = hdg2xdg(hd->enval);
      xdg2 = hdg2xdg(hd->stval);
    }

    xdlen = xdg2 - xdg1;
    if (xdlen<0) xdlen += 360;   /* note: 0 len means no range */

    if (xdlen>1) {
      XDrawArc(theDisp, hd->win, theGC, hd->x - ((HD_RADIUS*3)/5),
	       hd->y - ((HD_RADIUS*3)/5), (HD_RADIUS*6)/5, (HD_RADIUS*6)/5,
	       xdg1 * 64, xdlen * 64);
    }

    if (xdlen > 16) {
      xdg1 = hdg2xdg(hd->enval);
      if (hd->ccwise) xdg2 = xdg1-10;
                 else xdg2 = xdg1+10;

      pol2xy(hd->x, hd->y, xdg2 * DEG2RAD, ((HD_RADIUS*3)/5) + 3, &x, &y);
      pol2xy(hd->x, hd->y, xdg1 * DEG2RAD, ((HD_RADIUS*3)/5),     &x1,&y1);
      XDrawLine(theDisp, hd->win, theGC, x, y, x1, y1);

      if (hd->ccwise) xdg2 = xdg1-16;
                 else xdg2 = xdg1+16;
      pol2xy(hd->x, hd->y, xdg2 * DEG2RAD, ((HD_RADIUS*3)/5) - 2, &x, &y);
      XDrawLine(theDisp, hd->win, theGC, x, y, x1, y1);
    }
  }


  if (flags & HD_VALS) {
    char vstr[32];

    XSetFont(theDisp, theGC, monofont);
    XSetForeground(theDisp, theGC, hd->fg);
    XSetBackground(theDisp, theGC, hd->bg);

    if (hd->range) {
      sprintf(vstr,"%3d\007,%3d\007 %s", hd->stval, hd->enval, 
	      hd->ccwise ? "CCW" : " CW");
    }
    else {
      sprintf(vstr,"%3d\007 %3d%%", hd->stval, hd->satval);
    }
      
    XDrawImageString(theDisp, hd->win, theGC,
		     hd->x - XTextWidth(monofinfo, vstr, strlen(vstr))/2,
		     hd->y + HD_RADIUS + 24, vstr, strlen(vstr));
    XSetFont(theDisp, theGC, mfont);
  }


  if (flags & HD_TITLE) {
    XSetForeground(theDisp, theGC, hd->fg);
    ULineString(hd->win, hd->str, hd->x - HD_RADIUS - 15,
		hd->y - HD_RADIUS - 4);
  }


  if (flags & HD_BUTTS) {
    if (hd->range) {
      for (i=0; i<N_HDBUTT; i++) BTRedraw(&hd->hdbutt[i]);
    }
    else {
      for (i=0; i<N_HDBUTT2; i++) BTRedraw(&hd->hdbutt[i]);
      CBRedraw(&hd->enabCB);
    }
  }

  if (!hd->range && !hd->enabCB.val) {  /* draw dimmed */
    XSync(theDisp, False);
    DimRect(hd->win, hd->x-HD_RADIUS-15, hd->y-HD_RADIUS-4-ASCENT,
	    2*HD_RADIUS+30,(2*HD_RADIUS+4+ASCENT+80), hd->bg);
    XSync(theDisp, False);
    CBRedraw(&hd->enabCB);
  }
}


    
/**************************************************/
static int HDClick(hd,mx,my)
HDIAL *hd;
int mx, my;
{
  /* called when a click received.  checks whether HDTrack needs to be
     called.  Returns '1' if click is in HD dial area, 0 otherwise */

  int bnum,maxb;
  BUTT *bp;

  if (CBClick(&hd->enabCB, mx, my)) {
    if (CBTrack(&hd->enabCB)) {
      HDRedraw(hd, HD_ALL);
      return 1;
    }
  }

  if (!hd->range && !hd->enabCB.val) return 0;    /* disabled */


  if ( ((mx - hd->x) * (mx - hd->x)  +  (my - hd->y) * (my - hd->y)) 
      < (HD_RADIUS * HD_RADIUS)) {
    return HDTrack(hd,mx,my);
  }

  if (hd->range) maxb = N_HDBUTT;  else maxb = N_HDBUTT2;

  for (bnum=0; bnum<maxb; bnum++) {
    bp = &hd->hdbutt[bnum];
    if (PTINRECT(mx,my, bp->x, bp->y, bp->w, bp->h)) break;
  }
  if (bnum==maxb) return 0;

  if (bnum==HDB_FLIP && hd->range) {
    if (BTTrack(bp)) {
      int t;
      HDRedraw(hd, HD_CLHNDS);
      t = hd->stval;  hd->stval = hd->enval;  hd->enval = t;
      hd->ccwise = !hd->ccwise;
	  HDRedraw(hd, HD_HANDS | HD_DIR | HD_VALS);
      return 1;
    }
    return 0;
  }

  else {    /* track buttons til mouse up */
    Window rW, cW;
    int    rx,ry,x,y;
    unsigned int mask;

    bp->lit = 1;  BTRedraw(bp);  /* light it up */

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

      /* check to see if state needs toggling */
      if (( bp->lit && !PTINRECT(x,y,bp->x,bp->y,bp->w,bp->h)) ||
	  (!bp->lit &&  PTINRECT(x,y,bp->x,bp->y,bp->w,bp->h))) {
	bp->lit = !bp->lit;
	BTRedraw(bp);
      }

      if (bp->lit) {
	switch (bnum) {
	case HDB_ROTL:
	  HDRedraw(hd, HD_CLHNDS);
	  hd->stval--;  if (hd->stval<0) hd->stval += 360;
	  hd->enval--;  if (hd->enval<0) hd->enval += 360;
	  HDRedraw(hd, HD_HANDS | HD_DIR | HD_VALS);
	  break;

	case HDB_ROTR:
	  HDRedraw(hd, HD_CLHNDS);
	  hd->stval++;  if (hd->stval>=360) hd->stval -= 360;
	  hd->enval++;  if (hd->enval>=360) hd->enval -= 360;
	  HDRedraw(hd, HD_HANDS | HD_DIR | HD_VALS);
	  break;

	/* case HDB_DESAT: */
	/* case HDB_SAT:   */
	case HDB_EXPND:
	case HDB_SHRNK:
	  if (hd->range) {
	    if ((bnum == HDB_EXPND) &&
		(( hd->ccwise && hd->enval == (hd->stval+1))  ||
		 (!hd->ccwise && hd->enval == (hd->stval-1))))
		{ /* max size:  can't grow */ }

	    else if (bnum == HDB_SHRNK && hd->stval == hd->enval)
	      { /* min size, can't shrink */ }

	    else {   /* can shrink or grow */
	      HDRedraw(hd, HD_CLHNDS);
	      if ((bnum==HDB_EXPND &&  hd->ccwise) ||
		  (bnum==HDB_SHRNK && !hd->ccwise)) {
		if ((hd->stval & 1) == (hd->enval & 1))
		  hd->stval = (hd->stval + 1 + 360) % 360;
		else
		  hd->enval = (hd->enval - 1 + 360) % 360;
	      }

	      else {
		if ((hd->stval & 1) == (hd->enval & 1))
		  hd->stval = (hd->stval - 1 + 360) % 360;
		else
		  hd->enval = (hd->enval + 1 + 360) % 360;
	      }
	      HDRedraw(hd, HD_HANDS | HD_DIR | HD_VALS);
	    }
	  }

	  else {   /* hue/sat dial:  SAT/DESAT */
	    if (bnum == HDB_DESAT && hd->satval>0) {
	      HDRedraw(hd, HD_CLHNDS);
	      hd->satval--;  if (hd->satval<0) hd->satval = 0;
	      HDRedraw(hd, HD_HANDS | HD_VALS);
	    }
	      
	    else if (bnum == HDB_SAT && hd->satval<100) {
	      HDRedraw(hd, HD_CLHNDS);
	      hd->satval++;  if (hd->satval>100) hd->satval = 100;
	      HDRedraw(hd, HD_HANDS | HD_VALS);
	    }
	  }
	      
	  break;
	  
	}
	Timer(150);
      }
    }

    XFlush(theDisp);
  }

  if (bp->lit) {  bp->lit = 0;  BTRedraw(bp); }
    
  return 1;
}



/**************************************************/
static int HDTrack(hd,mx,my)
HDIAL *hd;
int mx,my;
{
  /* called when clicked in dial area.  tracks dragging handles around...
     returns '1' if anything changed */

  Window       rW,cW;
  int          rx,ry, x,y, ival,j, rv;
  unsigned int mask;

  rv = 0;

  if (!hd->range) {     /* hue/sat dial */
    int ihue, isat, newhue, newsat;
    double dx,dy,dist;

    ihue = hd->stval;  isat = hd->satval;

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

      /* compute new value based on mouse pos */
      newhue = computeHDval(hd, x,y);
      if (newhue<0) newhue = hd->stval;  /* at 'spaz' point, keep hue const */

      dx = x - hd->x;  dy = y - hd->y;
      dist = sqrt(dx*dx + dy*dy);
      
      newsat = (int) (dist / ((double) (HD_RADIUS - 4)) * 100);
      RANGE(newsat,0,100);

      if (newhue != hd->stval || newsat != hd->satval) {
	HDRedraw(hd, HD_CLHNDS);
	hd->stval = newhue;  hd->satval = newsat;
	HDRedraw(hd, HD_HANDS | HD_VALS);
      }
    }

    rv = (hd->stval != ihue || hd->satval != isat);
  }

  else {   /* the hard case */
    double a;
    int    handle = 0, *valp;

    /* determine if we're in either of the handles */
    a = hdg2xdg(hd->stval) * DEG2RAD;
    pol2xy(hd->x, hd->y, a, HD_RADIUS-4, &x,&y);
    if (PTINRECT(mx,my,x-3,y-3,7,7)) handle = 1;

    a = hdg2xdg(hd->enval) * DEG2RAD;
    pol2xy(hd->x, hd->y, a, HD_RADIUS-4, &x,&y);
    if (PTINRECT(mx,my,x-3,y-3,7,7)) handle = 2;
    


    if (!handle) {  /* not in either, rotate both */
      int oldj, len, origj;

      if (!hd->ccwise) {
	len = ((hd->enval - hd->stval + 360) % 360);
	oldj = (hd->stval + len/2 + 360) % 360;
      }
      else {
	len = ((hd->stval - hd->enval + 360) % 360);
	oldj = (hd->enval + len/2 + 360) % 360;
      }

      origj = j = oldj;

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

	/* compute new value based on mouse pos */
	j = computeHDval(hd, x,y);
	if (j>=0 && j != oldj) {
	  oldj = j;
	  HDRedraw(hd, HD_CLHNDS);
	  if (!hd->ccwise) {
	    hd->stval = (j - len/2 + 360) % 360;
	    hd->enval = (j + len/2 + 360) % 360;
	  }
	  else {
	    hd->stval = (j + len/2 + 360) % 360;
	    hd->enval = (j - len/2 + 360) % 360;
	  }
	  HDRedraw(hd, HD_HANDS | HD_DIR | HD_VALS);
	}
      }
      rv = (origj != j);
    }
	    

    else {  /* in one of the handles */
      if (handle==1) valp = &(hd->stval);  else valp = &(hd->enval);
      ival = *valp;

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

	/* compute new value based on mouse pos */
	j = computeHDval(hd, x,y);
	if (j>=0 && j != *valp) {
	  int ndist, ddist;

	  HDRedraw(hd, HD_CLHNDS);

	  if (!hd->ccwise) {
	    ddist = (hd->enval - hd->stval + 360) % 360;
	    if (handle==1) 
	      ndist = (hd->enval - j + 360) % 360;
	    else
	      ndist = (j - hd->stval + 360) % 360;
	  }
	  else {
	    ddist = (hd->stval - hd->enval + 360) % 360;
	    if (handle==1) 
	      ndist = (j - hd->enval + 360) % 360;
	    else
	      ndist = (hd->stval - j + 360) % 360;
	  }

	  if (abs(ddist - ndist) >= 180 && ddist<180) 
	    hd->ccwise = !hd->ccwise;
	  
	  *valp = j;
	  HDRedraw(hd, HD_HANDS | HD_DIR | HD_VALS);
	}
      }
      rv = (*valp != ival);
    }
  }

  return rv;
}
    
      

/**************************************************/
static int hdg2xdg(hdg)
int hdg;
{
  int xdg;

  xdg = 270 - hdg;
  if (xdg < 0)   xdg += 360;
  if (xdg > 360) xdg -= 360;

  return xdg;
}


/**************************************************/
static void pol2xy(cx, cy, ang, rad, xp, yp)
int cx, cy, rad, *xp, *yp;
double ang;
{
  *xp = cx + (int) (cos(ang) * (double) rad);
  *yp = cy - (int) (sin(ang) * (double) rad);
}

  
/***************************************************/
static int computeHDval(hd, x, y)
HDIAL *hd;
int x, y;
{
  int dx, dy;
  double angle;

  /* compute dx, dy (distance from center).  Note: +dy is *up* */
  dx = x - hd->x;  dy = hd->y - y;

  /* if too close to center, return -1 (invalid) avoid 'spazzing' */
  if (abs(dx) < 3 && abs(dy) < 3) return -1;

  /* figure out angle of vector dx,dy */
  if (dx==0) {     /* special case */
    if (dy>0) angle =  90.0;
         else angle = -90.0;
  }
  else if (dx>0) angle = atan((double)  dy / (double)  dx) * RAD2DEG;
  else           angle = atan((double) -dy / (double) -dx) * RAD2DEG + 180.0;

  angle = 270 - angle;   /* map into h-degrees */
  if (angle >= 360.0) angle -= 360.0;
  if (angle <    0.0) angle += 360.0;

  return (int) angle;
}



    
/****************************************************/
static void initHmap()
{
  int i;
  for (i=0; i<N_HMAP; i++) init1hmap(i);
}


/****************************************************/
static void init1hmap(i)
int i;
{
  int cang, width;

  width = 360 / N_HMAP;

  cang = i * width;
  hmap[i].src_st  = hmap[i].dst_st = (cang - width/2 + 360) % 360;
  hmap[i].src_en  = hmap[i].dst_en = (cang - width/2 + width +360) % 360;
  hmap[i].src_ccw = hmap[i].dst_ccw = 0;
}


/****************************************************/
static void dials2hmap()
{
  int i;
  i = RBWhich(hueRB);

  hmap[i].src_st  = srcHD.stval;
  hmap[i].src_en  = srcHD.enval;
  hmap[i].src_ccw = srcHD.ccwise;

  hmap[i].dst_st  = dstHD.stval;
  hmap[i].dst_en  = dstHD.enval;
  hmap[i].dst_ccw = dstHD.ccwise;
}


/****************************************************/
static void hmap2dials()
{
  int i;
  i = RBWhich(hueRB);

  srcHD.stval  = hmap[i].src_st;
  srcHD.enval  = hmap[i].src_en;
  srcHD.ccwise = hmap[i].src_ccw;

  dstHD.stval  = hmap[i].dst_st;
  dstHD.enval  = hmap[i].dst_en;
  dstHD.ccwise = hmap[i].dst_ccw;

  HDRedraw(&srcHD, HD_ALL | HD_CLEAR);
  HDRedraw(&dstHD, HD_ALL | HD_CLEAR);
}


/****************************************************/
static void build_hremap()
{
  int i,j, st1, en1, inc1, len1, st2, en2, inc2, len2;
  int a1, a2;

  /* start with a 1:1 mapping */
  for (i=0; i<360; i++) hremap[i] = i;

  for (i=0; i<N_HMAP; i++) {
    if ((hmap[i].src_st  != hmap[i].dst_st) ||
	(hmap[i].src_en  != hmap[i].dst_en) ||
	(hmap[i].src_ccw != hmap[i].dst_ccw)) {   /* not a 1:1 mapping */

      st1  = hmap[i].src_st;  
      en1  = hmap[i].src_en;
      if (hmap[i].src_ccw) {
	inc1 = -1; 
	len1 = (st1 - en1 + 360) % 360;
      }
      else {
	inc1 = 1;
	len1 = (en1 - st1 + 360) % 360;
      }

      st2 = hmap[i].dst_st;
      en2 = hmap[i].dst_en;
      if (hmap[i].dst_ccw) {
	inc2 = -1; 
	len2 = (st2 - en2 + 360) % 360;
      }
      else {
	inc2 = 1;
	len2 = (en2 - st2 + 360) % 360;
      }

      if (len1==0) {
	a1 = st1;
	a2 = st2;
	hremap[a1] = a2;
      }
      else {
	if (DEBUG) fprintf(stderr,"mapping %d: %d,%d %s  %d,%d %s\n",
			   i, hmap[i].src_st, hmap[i].src_en,
			   hmap[i].src_ccw ? "ccw" : "cw",
			   hmap[i].dst_st, hmap[i].dst_en,
			   hmap[i].dst_ccw ? "ccw" : "cw");

	for (j=0, a1=st1; j<=len1; a1 = (a1 + inc1 + 360) % 360, j++) {
	  a2 = (((inc2 * len2 * j) / len1 + st2) + 360) % 360;
	  hremap[a1] = a2;
	  if (DEBUG) fprintf(stderr,"(%d->%d)  ", a1, a2);
	}
	if (DEBUG) fprintf(stderr,"\n");
      }
    }
  }
}

