/*
 * xvimage.c - image manipulation functions (crop,resize,rotate...) for XV
 *
 *  Author:    John Bradley, University of Pennsylvania
 *                (bradley@cis.upenn.edu)
 *
 *  Contains:
 *            void Resize(int, int)
 *            void GenerateEpic(w,h)
 *            void Crop()
 *            void UnCrop()
 *            void AutoCrop()
 *            void DoCrop(x,y,w,h)
 *            void Rotate(int)
 *            void RotatePic();
 *            void InstallNewPic(void);
 *            void DrawEpic(void);
 *            byte *FSDither()
 *            void CreateXImage()
 *            void Set824Menus( pictype );
 *            void Change824Mode( pictype );
 *            void ConvertPics824(oldtype, newtype);
 *            
 */

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


#include "xv.h"


#ifdef __STDC__
static void do_zoom(int, int);
static void compute_zoom_crop(int, int);
static void do_unzoom(void);
static void crop1(int, int, int, int, int);
static int  doAutoCrop24(void);
static void FloydDitherize1(XImage *, byte *, int, int, int, 
			    byte *, byte *,byte *);
static int  highbit(unsigned long);
#else
static void do_zoom(), compute_zoom_crop(), do_unzoom();
static void crop1();
static int  doAutoCrop24();
static void FloydDitherize1();
static int  highbit();
#endif


#define DO_CROP 0
#define DO_ZOOM 1


/***********************************/
void Resize(w,h)
int w,h;
{
  static char *rstr = "Resizing Image.  Please wait...";

  RANGE(w,1,maxWIDE);  RANGE(h,1,maxHIGH);

  if (psUp) PSResize();   /* if PSDialog is open, mention size change  */

  /* if same size, and Ximage created, do nothing */
  if (w==eWIDE && h==eHIGH && theImage!=NULL) return;

  if (DEBUG) fprintf(stderr,"Resize(%d,%d)  eSIZE=%d,%d  cSIZE=%d,%d\n",
		     w,h,eWIDE,eHIGH,cWIDE,cHIGH);

  BTSetActive(&but[BCROP],0);
  SetCropString(but[BCROP].active);

  if (epicMode == EM_SMOOTH) {  /* turn off smoothing */
    epicMode = EM_RAW;  SetEpicMode();
  }

  GenerateEpic(w,h);
  CreateXImage();
}
                


/***********************************/
void GenerateEpic(w,h)
int w,h;
{
  int          cy,ex,ey,*cxarr, *cxarrp;
  byte        *clptr,*elptr,*epptr;

  WaitCursor();
  clptr = NULL;  cxarrp = NULL;  cy = 0;  /* shut up compiler */

  SetISTR(ISTR_EXPAND, "%.5g%% x %.5g%%  (%d x %d)",
	  100.0 * ((float) w) / cWIDE, 
	  100.0 * ((float) h) / cHIGH, w, h);

  if (DEBUG) 
    fprintf(stderr,"GenerateEpic(%d,%d) eSIZE=%d,%d cSIZE=%d,%d epicode=%d\n",
		     w,h,eWIDE,eHIGH,cWIDE,cHIGH, epicMode);


  FreeEpic();                   /* get rid of the old one */
  eWIDE = w;  eHIGH = h;


  if (epicMode == EM_SMOOTH) {  
    if (picType == PIC8) {
      epic = SmoothResize(cpic, cWIDE, cHIGH, eWIDE, eHIGH,
			  rMap,gMap,bMap, rdisp,gdisp,bdisp, numcols);
    }
    else {  /* PIC24 */
      epic = Smooth24(cpic, 1, cWIDE, cHIGH, eWIDE, eHIGH, NULL, NULL, NULL);
    }

    if (epic) return;   /* success */
    else {
      /* failed.  Try to generate a *raw* image, at least... */
      epicMode = EM_RAW;  SetEpicMode();
      /* fall through to rest of code */
    }
  }


  /* generate a 'raw' epic, as we'll need it for ColorDither if EM_DITH */
    
  if (eWIDE==cWIDE && eHIGH==cHIGH) {  /* 1:1 expansion.  point epic at cpic */
    epic = cpic;
  }
  else {
    /* run the rescaling algorithm */
    int bperpix;

    bperpix = (picType == PIC8) ? 1 : 3;

    WaitCursor();

    /* create a new epic of the appropriate size */

    epic = (byte *) malloc(eWIDE * eHIGH * bperpix);
    if (!epic) FatalError("GenerateEpic():  unable to malloc 'epic'");

    /* the scaling routine.  not really all that scary after all... */

    /* OPTIMIZATON:  Malloc an eWIDE array of ints which will hold the
       values of the equation px = (pWIDE * ex) / eWIDE.  Faster than doing 
       a mul and a div for every point in picture */

    cxarr = (int *) malloc(eWIDE * sizeof(int));
    if (!cxarr) FatalError("unable to allocate cxarr");

    for (ex=0; ex<eWIDE; ex++) 
      cxarr[ex] = bperpix * ((cWIDE * ex) / eWIDE);

    elptr = epptr = epic;

    for (ey=0;  ey<eHIGH;  ey++, elptr+=(eWIDE*bperpix)) {
      if ((ey&127) == 0) WaitCursor();
      cy = (cHIGH * ey) / eHIGH;
      epptr = elptr;
      clptr = cpic + (cy * cWIDE * bperpix);

      if (bperpix == 1) {
	for (ex=0, cxarrp = cxarr;  ex<eWIDE;  ex++, epptr++) 
	  *epptr = clptr[*cxarrp++];
      }
      else {
	int j;  byte *cp;

	for (ex=0, cxarrp = cxarr; ex<eWIDE; ex++,cxarrp++) {
	  cp = clptr + *cxarrp;
	  for (j=0; j<bperpix; j++) 
	    *epptr++ = *cp++;
	}
      }
    }
    free(cxarr);
  }


  /* at this point, we have a raw epic.  Potentially dither it */
  if (picType == PIC8 && epicMode == EM_DITH) {
    byte *tmp;

    tmp = DoColorDither(NULL, epic, eWIDE, eHIGH, rMap,gMap,bMap, 
			rdisp,gdisp,bdisp, numcols);
    if (tmp) {  /* success */
      FreeEpic();
      epic = tmp;
    }
    else {  /* well... just use the raw image. */
      epicMode = EM_RAW;  SetEpicMode();
    }
  }
}
                


/***********************************/
void DoZoom(x,y,button)
     int x,y,button;
{
  if      (button == Button1) do_zoom(x,y);
  else if (button == Button3) do_unzoom();
  else XBell(theDisp,0);
}


/***********************************/
static void do_zoom(mx,my)
     int mx,my;
{
  int i,w,h,x,y,x2,y2,ox,oy;

  /* if there's already a cropping rectangle drawn, turn it off */
  if (but[BCROP].active) InvCropRect();

  ox = mx;  oy = my;
  compute_zoom_crop(mx,my);
  InvCropRect();

  /* track until Button1 is released */
  while (1) {
    Window rW, cW;  unsigned int mask;  int rx, ry;
    if (XQueryPointer(theDisp, mainW, &rW, &cW, &rx, &ry, &mx, &my, &mask)) {
      if (!(mask & Button1Mask)) break;  /* button released */
      
      if (mx!=ox || my!=oy) {
	InvCropRect();
	compute_zoom_crop(mx,my);
	InvCropRect();
	ox = mx;  oy = my;
      }
    }
  }
      
  for (i=0; i<6; i++) {
    InvCropRect();
    XFlush(theDisp);
    Timer(150);
  }


  /* figure out what the crop rectangles coordinates are in pic coordinates */
  x = cXOFF + (crx1 * cWIDE) / eWIDE;
  y = cYOFF + (cry1 * cHIGH) / eHIGH;
  x2 = cXOFF + (crx2 * cWIDE) / eWIDE;
  y2 = cYOFF + (cry2 * cHIGH) / eHIGH;
  w = (x2 - x);
  h = (y2 - y);

  if (w<1) w = 1;
  if (x+w > pWIDE) w = pWIDE - x;
  if (h<1) h = 1;
  if (y+h > pHIGH) h = pHIGH - y;


  crop1(x,y,w,h, DO_ZOOM);
}


/***********************************/
static void compute_zoom_crop(x,y)
     int x,y;
{
  int w,h;

  w = eWIDE/2;  h = eHIGH/2;
  crx1 = x - w/2;  cry1 = y - h/2;  
  if (crx1 < 0) crx1 = 0;
  if (cry1 < 0) cry1 = 0;
  if (crx1 > eWIDE-w) crx1 = eWIDE-w;
  if (cry1 > eHIGH-h) cry1 = eHIGH-h;
  
  crx2 = crx1 + w;
  cry2 = cry1 + h;
}


/***********************************/
static void do_unzoom()
{
  int x,y,w,h;

  /* compute a cropping rectangle (in screen coordinates) that's twice 
     the size of eWIDE,eHIGH, centered around eWIDE/2, eHIGH/2, but no
     larger than pWIDE,PHIGH */

  if (!but[BUNCROP].active) {    /* not cropped, can't zoom out */
    XBell(theDisp, 0);
    return;
  }

  crx1 = -eWIDE/2;   cry1 = -eHIGH/2;

  /* figure out what the crop rectangles coordinates are in pic coordinates */
  x = cXOFF + (crx1 * cWIDE - (eWIDE/2)) / eWIDE;
  y = cYOFF + (cry1 * cHIGH - (eHIGH/2)) / eHIGH;
  w = cWIDE*2;
  h = cHIGH*2;
  RANGE(w, 1, pWIDE);
  RANGE(h, 1, pHIGH);

  if (x<0) x = 0;
  if (y<0) y = 0;
  if (x+w > pWIDE) x = pWIDE - w;
  if (y+h > pHIGH) y = pHIGH - h;

  crop1(x,y,w,h, DO_ZOOM);
}


/***********************************/
void Crop()
{
  int i, x, y, x2, y2, w, h;

  if (!but[BCROP].active) return;

  /* turn off the cropping rectangle */
  InvCropRect();  BTSetActive(&but[BCROP],0);

  /* sort crx1,crx2,cry1,cry2 so that crx1,cry1 are top left corner */
  if (crx1>crx2) { i = crx1; crx1 = crx2; crx2 = i; }
  if (cry1>cry2) { i = cry1; cry1 = cry2; cry2 = i; }

  /* see if cropping to same size, in which case do nothing */
  if (crx2-crx1 == eWIDE && cry2-cry1 == eHIGH) return;

  /* figure out what the crop rectangles coordinates are in pic coordinates */
  x = cXOFF + (crx1 * cWIDE) / eWIDE;
  y = cYOFF + (cry1 * cHIGH) / eHIGH;
  x2 = cXOFF + (crx2 * cWIDE) / eWIDE;
  y2 = cYOFF + (cry2 * cHIGH) / eHIGH;
  w = (x2 - x) + 1;
  h = (y2 - y) + 1;

  if (w<1) w = 1;
  if (x+w > pWIDE) w = pWIDE - x;
  if (h<1) h = 1;
  if (y+h > pHIGH) h = pHIGH - y;

  crop1(x,y,w,h,DO_CROP);
}


/**********************************/
static void crop1(x,y,w,h,zm)
int x,y,w,h,zm;
{
  int   i,j,oldew,oldeh;
  byte *cp, *pp;

  oldew = eWIDE;  oldeh = eHIGH;

  DoCrop(x,y,w,h);
  if (zm == DO_ZOOM) { eWIDE = oldew;  eHIGH = oldeh; }

  GenerateEpic(eWIDE, eHIGH);

  if (useroot) DrawEpic();
  else {
    if (zm == DO_CROP) {
      WCrop(eWIDE, eHIGH);  /* shrink window */
      CreateXImage();
    }
    else DrawEpic();
  }
  
  SetCursors(-1);
}


/***********************************/
void UnCrop()
{
  int w,h;

  if (cpic == pic) return;     /* not cropped */

  BTSetActive(&but[BUNCROP],0);
  
  if (epicMode == EM_SMOOTH) {   /* turn off smoothing */
    epicMode = EM_RAW;  SetEpicMode();
  }

  /* dispose of old cpic and epic */
  FreeEpic();
  if (cpic && cpic !=  pic) free(cpic);
  cpic = NULL;
  

  w = (pWIDE * eWIDE) / cWIDE;   h = (pHIGH * eHIGH) / cHIGH;
  if (w>maxWIDE || h>maxHIGH) {
    /* return to 'normal' size */
    if (pWIDE>maxWIDE || pHIGH>maxHIGH) {
      double r,wr,hr;
      wr = ((double) pWIDE) / maxWIDE;
      hr = ((double) pHIGH) / maxHIGH;

      r = (wr>hr) ? wr : hr;   /* r is the max(wr,hr) */
      w = (int) ((pWIDE / r) + 0.5);
      h = (int) ((pHIGH / r) + 0.5);
    }
    else { w = pWIDE;  h = pHIGH; }
  }

  cpic = pic;  cXOFF = cYOFF = 0;  cWIDE = pWIDE;  cHIGH = pHIGH;


  /* generate an appropriate 'epic' */
  GenerateEpic(w,h);
  CreateXImage();


  WUnCrop();
  SetCropString(but[BCROP].active);
}
  

/***********************************/
void AutoCrop()
{
  /* called when AutoCrop button is pressed */

  if (DoAutoCrop()) {
    if (useroot) DrawEpic();
    else {
      CreateXImage();
      WCrop(eWIDE, eHIGH);
    }
  }
  
  SetCursors(-1);
}


/***********************************/
int DoAutoCrop()
{
  /* returns '1' if any cropping was actually done. */

  byte *cp, *cp1;
  int  i, ctop, cbot, cleft, cright, bperpix;
  byte bgcol;

  ctop = cbot = cleft = cright = 0;

  bperpix = (picType == PIC8) ? 1 : 3;

  if (picType == PIC24) return( doAutoCrop24() );


  /* crop the top */
  cp = cpic;
  bgcol = cp[0];

  while (ctop+1 < cHIGH) {
    /* see if we can delete this line */
    for (i=0, cp1=cp; i<cWIDE && *cp1==bgcol; i++, cp1++);
    if (i==cWIDE) { cp += cWIDE;  ctop++; }
    else break;
  }


  /* crop the bottom */
  cp = cpic + (cHIGH-1) * cWIDE;
  bgcol = cp[0];

  while (ctop + cbot + 1 < cHIGH) {
    /* see if we can delete this line */
    for (i=0, cp1=cp; i<cWIDE && *cp1==bgcol; i++,cp1++);
    if (i==cWIDE) { cp -= cWIDE;  cbot++; }
    else break;
  }


  /* crop the left side */
  cp = cpic;
  bgcol = cp[0];

  while (cleft + 1 < cWIDE) {
    /* see if we can delete this line */
    for (i=0, cp1=cp; i<cHIGH && *cp1==bgcol; i++, cp1 += cWIDE);
    if (i==cHIGH) { cp++; cleft++; }
    else break;
  }


  /* crop the right side */
  cp = cpic + cWIDE-1;
  bgcol = cp[0];

  while (cleft + cright + 1 < cWIDE) {
    /* see if we can delete this line */
    for (i=0, cp1=cp; i<cHIGH && *cp1==bgcol; i++, cp1 += cWIDE);
    if (i==cHIGH) { cp--; cright++; }
    else break;
  }

  /* do the actual cropping */
  if (cleft || ctop || cbot || cright) {
    DoCrop(cXOFF+cleft, cYOFF+ctop, 
	    cWIDE-(cleft+cright), cHIGH-(ctop+cbot));
    return 1;
  }

  return 0;
}


/***********************************/
static int doAutoCrop24()
{
  /* returns '1' if any cropping was actually done */

  byte *cp, *cp1;
  int  i, ctop, cbot, cleft, cright;
  byte bgR, bgG, bgB;

  ctop = cbot = cleft = cright = 0;

  if (picType != PIC24) FatalError("doAutoCrop24 called when pic!=PIC24");

  /* crop the top */
  cp = cpic;
  bgR = cp[0];  bgG = cp[1];  bgB = cp[2];

  while (ctop+1 < cHIGH) {  /* see if we can delete this line */
    for (i=0, cp1=cp; i<cWIDE && cp1[0]==bgR && cp1[1]==bgG && cp1[2]==bgB; 
	 i++, cp1+=3);

    if (i==cWIDE) { cp += cWIDE*3;  ctop++; }
    else break;
  }


  /* crop the bottom */
  cp = cpic + (cHIGH-1) * cWIDE*3;
  bgR = cp[0];  bgG = cp[1];  bgB = cp[2];

  while (ctop + cbot + 1 < cHIGH) {  /* see if we can delete this line */
    for (i=0, cp1=cp; i<cWIDE && cp1[0]==bgR && cp1[1]==bgG && cp1[2]==bgB; 
	 i++, cp1+=3);

    if (i==cWIDE) { cp -= cWIDE*3;  cbot++; }
    else break;
  }


  /* crop the left side */
  cp = cpic;
  bgR = cp[0];  bgG = cp[1];  bgB = cp[2];

  while (cleft + 1 < cWIDE) {  /* see if we can delete this line */
    for (i=0, cp1=cp; i<cHIGH && cp1[0]==bgR && cp1[1]==bgG && cp1[2]==bgB; 
	 i++, cp1 += (cWIDE * 3));

    if (i==cHIGH) { cp+=3; cleft++; }
    else break;
  }


  /* crop the right side */
  cp = cpic + (cWIDE-1) * 3;
  bgR = cp[0];  bgG = cp[1];  bgB = cp[2];

  while (cleft + cright + 1 < cWIDE) {  /* see if we can delete this line */
    for (i=0, cp1=cp; i<cHIGH && cp1[0]==bgR && cp1[1]==bgG && cp1[2]==bgB; 
	 i++, cp1 += (cWIDE*3));

    if (i==cHIGH) { cp-=3; cright++; }
    else break;
  }


  /* do the actual cropping */
  if (cleft || ctop || cbot || cright) {
    DoCrop(cXOFF+cleft, cYOFF+ctop, 
	    cWIDE-(cleft+cright), cHIGH-(ctop+cbot));
    return 1;
  }

  return 0;
}


/*******************************/
void DoCrop(x,y,w,h)
     int x,y,w,h;
{
  /* given a cropping rectangle in image coordinates, it regens cpic
     and sticks likely values into eWIDE,eHIGH, assuming you wanted to
     crop.  epic is not regnerated (but is freed) */

  int   i,j,k,bperpix;
  byte *cp, *pp;
  double expw, exph;


  bperpix = (picType == PIC8) ? 1 : 3;

  BTSetActive(&but[BCROP],0);

  /* get the cropping rectangle inside pic, if it isn't... */
  RANGE(x, 0, pWIDE-1);
  RANGE(y, 0, pHIGH-1);
  if (w<1) w=1;
  if (h<1) h=1;
  if (x+w > pWIDE) w = pWIDE-x;
  if (y+h > pHIGH) h = pHIGH-y;



  FreeEpic();
  if (cpic && cpic !=  pic) free(cpic);
  cpic = NULL;


  expw = (double) eWIDE / (double) cWIDE;
  exph = (double) eHIGH / (double) cHIGH;

  crx1 = (int) ((x - cXOFF) * expw);
  cry1 = (int) ((y - cYOFF) * exph);

  cXOFF = x;  cYOFF = y;  cWIDE = w;  cHIGH = h;
  if (DEBUG) fprintf(stderr,"DoCrop(): cropping to %dx%d rectangle at %d,%d\n",
		     cWIDE, cHIGH, cXOFF, cYOFF);


  if (cWIDE == pWIDE && cHIGH == pHIGH) {   /* not really cropping */
    cpic = pic;
    cXOFF = cYOFF = 0;
  }
  else {
    /* at this point, we want to generate cpic, which will contain a
       cWIDE*cHIGH subsection of 'pic', top-left at cXOFF,cYOFF */

    cpic = (byte *) malloc(cWIDE * cHIGH * bperpix);
    if (cpic == NULL) {
      fprintf(stderr,"%s: unable to allocate memory for cropped image\n", cmd);
      WUnCrop();
      cpic = pic;  cXOFF = cYOFF = 0;  cWIDE = pWIDE;  cHIGH = pHIGH;
      SetCropString(but[BCROP].active);
      return;
    }

    /* copy relevant pixels from pic to cpic */
    cp = cpic;
    for (i=0; i<cHIGH; i++) {
      pp = pic + (i+cYOFF) * (pWIDE*bperpix) + (cXOFF * bperpix);
      for (j=0; j<cWIDE*bperpix; j++) 
	*cp++ = *pp++;
    }
  }


  SetCropString(but[BCROP].active);
  BTSetActive(&but[BUNCROP], (cpic!=pic));

  eWIDE = (int) (cWIDE * expw);  
  eHIGH = (int) (cHIGH * exph);

  SetCursors(-1);
}



/***********************************/
void Rotate(dir)
int dir;
{
  /* called when rotate CW and rotate CCW controls are clicked */
  /* dir=0: clockwise, else counter-clockwise */

  DoRotate(dir);
  CreateXImage();
  WRotate();
}


/***********************************/
void DoRotate(dir)
int dir;
{
  int i;

  /* dir=0: 90 degrees clockwise, else 90 degrees counter-clockwise */
  WaitCursor();

  RotatePic(pic, picType, &pWIDE, &pHIGH, dir);

  /* rotate clipped version and modify 'clip' coords */
  if (cpic != pic && cpic != NULL) {
    if (!dir) {
      i = pWIDE - (cYOFF + cHIGH);      /* have to rotate offsets */
      cYOFF = cXOFF;
      cXOFF = i;
    }
    else {
      i = pHIGH - (cXOFF + cWIDE);
      cXOFF = cYOFF;
      cYOFF = i;
    }
    WaitCursor();
    RotatePic(cpic, picType, &cWIDE, &cHIGH,dir);
  }
  else { cWIDE = pWIDE;  cHIGH = pHIGH; }

  /* rotate expanded version */
  if (epic != cpic && epic != NULL) {
    WaitCursor();
    RotatePic(epic, picType, &eWIDE, &eHIGH,dir);
  }
  else { eWIDE = cWIDE;  eHIGH = cHIGH; }
}


/************************/
void RotatePic(pic, ptype, wp, hp, dir)
byte *pic;
int  *wp, *hp;
int   ptype, dir;
{
  /* rotates a w*h array of bytes 90 deg clockwise (dir=0) 
     or counter-clockwise (dir != 0).  swaps w and h */

  byte *pic1, *pix1, *pix;
  int          i,j,bperpix;
  unsigned int w,h;

  bperpix = (ptype == PIC8) ? 1 : 3;

  w = *wp;  h = *hp;  
  pix1 = pic1 = (byte *) malloc(w*h*bperpix);
  if (!pic1) FatalError("Not enough memory to rotate!");

  /* do the rotation */
  if (dir==0) {
    for (i=0; i<w; i++) {       /* CW */
      if (bperpix == 1) {
	for (j=h-1, pix=pic+(h-1)*w + i;  j>=0;  j--, pix1++, pix-=w) 
	  *pix1 = *pix;
      }
      else {
	int bperlin = w*bperpix;
	int k;

	for (j=h-1, pix=pic+(h-1)*w*bperpix + i*bperpix;  
	     j>=0;  j--, pix -= bperlin) 
	  for (k=0; k<bperpix; k++) *pix1++ = pix[k];
      }
    }
  }
  else {
    for (i=w-1; i>=0; i--) {    /* CCW */
      if (bperpix == 1) {
	for (j=0, pix=pic+i; j<h; j++, pix1++, pix+=w) 
	  *pix1 = *pix;
      }
      else {
	int k;
	int bperlin = w*bperpix;

	for (j=0, pix=pic+i*bperpix; j<h; j++, pix+=bperlin) 
	  for (k=0; k<bperpix; k++) *pix1++ = pix[k];
      }
    }
  }


  /* copy the rotated buffer into the original buffer */
  xvbcopy(pic1, pic, w*h*bperpix);

  free(pic1);

  /* swap w and h */
  *wp = h;  *hp = w;
}

  

/***********************************/
void Flip(dir)
int dir;
{
  /* dir=0: flip horizontally, else vertically
   *
   * Note:  flips pic, cpic, and epic.  Doesn't touch Ximage, nor does it draw
   */

  WaitCursor();
  FlipPic(pic, pWIDE, pHIGH, dir);

  /* flip clipped version */
  if (cpic && cpic != pic) {
    WaitCursor();
    FlipPic(cpic, cWIDE, cHIGH, dir);
  }

  /* flip expanded version */
  if (epic && epic != cpic) {
    WaitCursor();
    FlipPic(epic, eWIDE, eHIGH, dir);
  }
}


/************************/
void FlipPic(pic, w, h, dir)
byte *pic;
int w, h;
int dir;
{
  /* flips a w*h array of bytes horizontally (dir=0) or vertically (dir!=0) */

  byte *plin;
  int   i,j,k,l,bperpix,bperlin;

  bperpix = (picType == PIC8) ? 1 : 3;
  bperlin = w * bperpix;

  if (dir==0) {                /* horizontal flip */
    byte *leftp, *rightp;

    for (i=0; i<h; i++) {
      plin   = pic + i*bperlin;
      leftp  = plin;
      rightp = plin + (w-1)*bperpix;

      for (j=0; j<w/2; j++, rightp -= (2*bperpix)) {
	for (l=0; l<bperpix; l++, leftp++, rightp++) {
	  k = *leftp;  *leftp = *rightp;  *rightp = k;
	}
      }
    }
  }

  else {                      /* vertical flip */
    byte *topp, *botp;

    for (i=0; i<w; i++) {
      topp = pic + i*bperpix;
      botp = pic + (h-1)*bperlin + i*bperpix;

      for (j=0; j<h/2; j++, topp+=(w-1)*bperpix, botp-=(w+1)*bperpix) {
	for (l=0; l<bperpix; l++, topp++, botp++) {
	  k = *topp;  *topp = *botp;  *botp = k;
	}
      }
    }
  }
}

  


/************************/
void InstallNewPic()
{
  /* given a new pic and colormap, (or new 24-bit pic) installs everything,
     regens cpic and epic, and redraws image */

  /* toss old cpic and epic, if any */
  FreeEpic();
  if (cpic && cpic != pic) free(cpic);
  cpic = NULL;

  /* toss old colors, and allocate new ones */
  NewPicGetColors(0,0);

  /* generate cpic,epic,theImage from new 'pic' */
  crop1(cXOFF, cYOFF, cWIDE, cHIGH, DO_ZOOM);
  HandleDispMode();
}



/***********************************/
void DrawEpic()
{
  /* given an 'epic', builds a new Ximage, and draws it.  Basically
     called whenever epic is changed, or whenever color allocation 
     changes (ie, the created X image will look different for the 
     same epic) */

  CreateXImage();
  if (useroot) MakeRootPic();
          else DrawWindow(0,0,eWIDE,eHIGH);

  if (but[BCROP].active) InvCropRect();
}


/************************************/
void KillOldPics()
{
  /* throw away all previous images */

  FreeEpic();
  if (cpic && cpic != pic) free(cpic);
  if (pic) free(pic); 
  xvDestroyImage(theImage);   theImage = NULL;
  pic = egampic = epic = cpic = NULL;

  if (picComments) free(picComments);
  picComments = (char *) NULL;
  ChangeCommentText();
}

  

/************************/
static void FloydDitherize1(ximage,pic824,ptype, wide, high, rmap, gmap, bmap)
     XImage *ximage;
     byte   *pic824, *rmap, *gmap, *bmap;
     int     ptype, wide, high;
{
  /* does floyd-steinberg ditherizing algorithm.  
   *
   * takes a wide*high input image, of type 'ptype' (PIC8, PIC24)
   *     (if PIC8, colormap is specified by rmap,gmap,bmap)
   *
   * output is a 1-bit per pixel XYBitmap, packed 8 pixels per byte
   *
   * Note: this algorithm is *only* used when running on a 1-bit display
   */

  register byte   pix8, bit;
  int            *thisline, *nextline;
  int            *thisptr, *nextptr, *tmpptr;
  int             i, j, err, bperpix, bperln, order;
  byte           *pp, *image, w1, b1, w8, b8, rgb[256];


  if (ptype == PIC8) {   /* monoify colormap */
    for (i=0; i<256; i++)
      rgb[i] = MONO(rmap[i], gmap[i], bmap[i]);
  }


  image   = (byte *) ximage->data;
  bperln  = ximage->bytes_per_line;
  order   = ximage->bitmap_bit_order;
  bperpix = (ptype == PIC8) ? 1 : 3;


  thisline = (int *) malloc(wide * sizeof(int));
  nextline = (int *) malloc(wide * sizeof(int));
  if (!thisline || !nextline) 
    FatalError("ran out of memory in FloydDitherize1()\n");


  /* load up first line of picture */
  pp = pic824;
  if (ptype == PIC24) {
    for (j=0, tmpptr = nextline; j<wide; j++, pp+=3)
      *tmpptr++ = fsgamcr[MONO(pp[0], pp[1], pp[2])];
  }
  else {
    for (j=0, tmpptr = nextline; j<wide; j++, pp++)
      *tmpptr++ = fsgamcr[rgb[*pp]];
  }

      
  w1 = white&0x1;  b1=black&0x1;
  w8 = w1<<7;  b8 = b1<<7;        /* b/w bit in high bit */
  

  for (i=0; i<high; i++) {
    if ((i&63) == 0) WaitCursor();

    /* get next line of image */
    tmpptr = thisline;  thisline = nextline;  nextline = tmpptr;  /* swap */
    if (i!=high-1) {
      pp = pic824 + (i+1) * wide * bperpix;
      if (ptype == PIC24) {
	for (j=0, tmpptr = nextline; j<wide; j++, pp+=3)
	  *tmpptr++ = fsgamcr[MONO(pp[0], pp[1], pp[2])];
      }
      else {
	for (j=0, tmpptr = nextline; j<wide; j++, pp++)
	  *tmpptr++ = fsgamcr[rgb[*pp]];
      }
    }

    thisptr = thisline;  nextptr = nextline;

    pp  = image + i*bperln;


    if (order==LSBFirst) {
      bit = pix8 = 0;
      for (j=0; j<wide; j++, thisptr++, nextptr++) {
	if (*thisptr<128) { err = *thisptr;     pix8 |= b8; }
	             else { err = *thisptr-255; pix8 |= w8; }

	if (bit==7) { *pp++ = pix8;  bit=pix8=0; }
	       else { pix8 >>= 1;  bit++; }

	if (j<wide-1) thisptr[1] += ((err*7)/16);

	if (i<high-1) {
	  nextptr[0] += ((err*5)/16);
	  if (j>0)      nextptr[-1] += ((err*3)/16);
	  if (j<wide-1) nextptr[ 1] += (err/16);
	}
      }
      if (bit) *pp++ = pix8>>(7-bit);  /* write partial byte at end of line */
    }

    else {   /* order==MSBFirst */
      bit = pix8 = 0;
      for (j=0; j<wide; j++, thisptr++, nextptr++) {
	if (*thisptr<128) { err = *thisptr;     pix8 |= b1; }
	             else { err = *thisptr-255; pix8 |= w1; }

	if (bit==7) { *pp++ = pix8;  bit=pix8=0; }
 	       else { pix8 <<= 1; bit++; }

	if (j<wide-1) thisptr[1] += ((err*7)/16);

	if (i<high-1) {
	  nextptr[0] += ((err*5)/16);
	  if (j>0)       nextptr[-1] += ((err*3)/16);
	  if (j<wide-1)  nextptr[ 1] += (err/16);
	}
      }
      if (bit) *pp++ = pix8<<(7-bit);  /* write partial byte at end of line */
    }
  }


  free(thisline);  free(nextline);
}





/************************/
byte *FSDither(inpic, intype, w, h, rmap, gmap, bmap, 
	      bval, wval)
     byte *inpic, *rmap, *gmap, *bmap;
     int   w,h, intype, bval, wval;
{
  /* takes an input pic of size w*h, and type 'intype' (PIC8 or PIC24),
   *                (if PIC8, colormap specified by rmap,gmap,bmap)
   * and does the floyd-steinberg dithering algorithm on it.
   * generates (mallocs) a w*h 1-byte-per-pixel 'outpic', using 'bval'
   * and 'wval' as the 'black' and 'white' pixel values, respectively
   */

  int    i, j, err, w1, h1;
  byte  *pp, *outpic, rgb[256];
  int   *thisline, *nextline, *thisptr, *nextptr, *tmpptr;


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

  if (intype == PIC8) {       /* monoify colormap */
    for (i=0; i<256; i++)
      rgb[i] = MONO(rmap[i], gmap[i], bmap[i]);
  }


  thisline = (int *) malloc(w * sizeof(int));
  nextline = (int *) malloc(w * sizeof(int));
  if (!thisline || !nextline) 
    FatalError("ran out of memory in FSDither()\n");


  w1 = w-1;  h1 = h-1;

  /* load up first line of picture */
  pp = inpic;
  if (intype == PIC24) {
    for (j=0, tmpptr=nextline; j<w; j++, pp+=3)
      *tmpptr++ = fsgamcr[MONO(pp[0], pp[1], pp[2])];
  }
  else {
    for (j=0, tmpptr=nextline; j<w; j++, pp++)
      *tmpptr++ = fsgamcr[rgb[*pp]];
  }


  for (i=0; i<h; i++) {
    if ((i&31) == 0) WaitCursor();

    /* get next line of picture */
    tmpptr = thisline;  thisline = nextline;  nextline = tmpptr;  /* swap */
    if (i!=h1) {
      if (intype == PIC24) {
	pp = inpic + (i+1) * w * 3;
	for (j=0, tmpptr=nextline; j<w; j++, pp+=3)
	  *tmpptr++ = fsgamcr[MONO(pp[0], pp[1], pp[2])];
      }
      else {
	pp = inpic + (i+1) * w;
	for (j=0, tmpptr = nextline; j<w; j++, pp++)
	  *tmpptr++ = fsgamcr[rgb[*pp]];
      }
    }

    pp  = outpic + i * w;
    thisptr = thisline;  nextptr = nextline;

    for (j=0; j<w; j++, pp++, thisptr++, nextptr++) {
      if (*thisptr<128) { err = *thisptr;     *pp = (byte) bval; }
                   else { err = *thisptr-255; *pp = (byte) wval; }

      if (j<w1) thisptr[1] += ((err*7)/16);

      if (i<h1) {
        nextptr[0] += ((err*5)/16);
        if (j>0)  nextptr[-1] += ((err*3)/16);
        if (j<w1) nextptr[ 1] += (err/16);
      }
    }
  }

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






/***********************************/
void CreateXImage()
{
  xvDestroyImage(theImage);   theImage = NULL;

  if (!epic) GenerateEpic(eWIDE, eHIGH);  /* shouldn't happen... */

  if (picType == PIC24) {  /* generate egampic */
    if (egampic && egampic != epic) free(egampic);
    egampic = GammifyPic24(epic, eWIDE, eHIGH);
    if (!egampic) egampic = epic;
  }


  if (picType == PIC8) 
    theImage = Pic8ToXImage(epic, eWIDE, eHIGH, cols, rMap, gMap, bMap);
  else if (picType == PIC24)
    theImage = Pic24ToXImage(egampic, eWIDE, eHIGH);
}




/***********************************/
XImage *Pic8ToXImage(pic8, wide, high, xcolors, rmap, gmap, bmap)
     byte          *pic8, *rmap, *gmap, *bmap;
     int            wide, high;
     unsigned long *xcolors;
{
  /*
   * this has to do the tricky bit of converting the data in 'pic8'
   * into something usable for X.
   *
   */


  int     i;
  unsigned long xcol;
  XImage *xim;
  byte   *dithpic;

  xim = (XImage *) NULL;
  dithpic = (byte *) NULL;

  if (!pic8) return xim;  /* shouldn't happen */

  if (DEBUG > 1) 
    fprintf(stderr,"Pic8ToXImage(): creating a %dx%d Ximage, %d bits deep\n",
	    wide, high, dispDEEP);


  /* special case: 1-bit display */
  if (dispDEEP == 1) {
    byte  *imagedata;

    xim = XCreateImage(theDisp, theVisual, dispDEEP, XYPixmap, 0, NULL, 
			    wide, high, 32, 0);
    if (!xim) FatalError("couldn't create xim!");

    imagedata = (byte *) malloc(xim->bytes_per_line * high);
    if (!imagedata) FatalError("couldn't malloc imagedata");

    xim->data = (char *) imagedata;
    FloydDitherize1(xim, pic8, PIC8, wide, high, rmap, gmap, bmap);
    return xim;
  }


  /* if ncols==0, do a 'black' and 'white' dither */
  if (ncols == 0) {
    /* note that if dispDEEP > 8, dithpic will just have '0' and '1' instead 
       of 'black' and 'white' */

    dithpic = FSDither(pic8, PIC8, wide, high, rmap, gmap, bmap,
		       (dispDEEP <= 8) ? black : 0, 
		       (dispDEEP <= 8) ? white : 1);
  }



  switch (dispDEEP) {

  case 8: {
    byte  *imagedata, *ip, *pp;
    int   j, imWIDE, nullCount;
  
    nullCount = (4 - (wide % 4)) & 0x03;  /* # of padding bytes per line */
    imWIDE = wide + nullCount;
 
    /* Now create the image data - pad each scanline as necessary */
    imagedata = (byte *) malloc(imWIDE * high);
    if (!imagedata) FatalError("couldn't malloc imagedata");
    
    pp = (dithpic) ? dithpic : pic8;

    for (i=0, ip=imagedata; i<high; i++) {
      if ((i&0x7f) == 0) WaitCursor();

      if (dithpic) {
	for (j=0; j<wide; j++, ip++, pp++) *ip = *pp;  /* pp already is Xval */
      }
      else {
	for (j=0; j<wide; j++, ip++, pp++) *ip = (byte) xcolors[*pp];
      }

      for (j=0; j<nullCount; j++, ip++) *ip = 0;
    }
      
    xim = XCreateImage(theDisp,theVisual,dispDEEP,ZPixmap,0,
		       (char *) imagedata, wide, high, 32, imWIDE);
    if (!xim) FatalError("couldn't create xim!");
  }
    break;



    /*********************************/
      
  case 4: {
    byte  *imagedata, *ip, *pp;
    byte *lip;
    int  bperline, half, j;

    xim = XCreateImage(theDisp, theVisual, dispDEEP, ZPixmap, 0, NULL, 
			      wide, high, 8, 0);
    if (!xim) FatalError("couldn't create xim!");

    bperline = xim->bytes_per_line;
    imagedata = (byte *) malloc(bperline * high);
    if (!imagedata) FatalError("couldn't malloc imagedata");
    xim->data = (char *) imagedata;

    
    pp = (dithpic) ? dithpic : pic8;

    if (xim->bits_per_pixel == 4) {
      for (i=0, lip=imagedata; i<high; i++, lip+=bperline) {
	if ((i&127) == 0) WaitCursor();

	for (j=0, ip=lip, half=0; j<wide; j++,pp++,half++) {
	  xcol = ((dithpic) ? *pp : xcolors[*pp]) & 0x0f;

	  if (ImageByteOrder(theDisp) == LSBFirst) {
	    if (half&1) { *ip = *ip + (xcol<<4);  ip++; }
	    else *ip = xcol;
	  }
	  else {
	    if (half&1) { *ip = *ip + xcol;  ip++; }
	    else *ip = xcol << 4;
	  }
	}
      }
    }

    else if (xim->bits_per_pixel == 8) {
      for (i=wide*high, ip=imagedata; i>0; i--,pp++,ip++) {
	if ((i&0x1ffff) == 0) WaitCursor();
	*ip = (dithpic) ? *pp : (byte) xcolors[*pp];
      }
    }

    else FatalError("This display's too bizarre.  Can't create XImage.");
  }
    break;
      

    /*********************************/
      
  case 2: {  /* by M.Kossa@frec.bull.fr (Marc Kossa) */
             /* MSBFirst mods added by dale@ntg.com (Dale Luck) */
             /* additional fixes by  evol@infko.uni-koblenz.de 
		(Randolf Werner) for NeXT 2bit grayscale with MouseX */

    byte  *imagedata, *ip, *pp;
    byte *lip;
    int  bperline, half, j;

    xim = XCreateImage(theDisp, theVisual, dispDEEP, ZPixmap, 0, NULL, 
			    wide, high, 8, 0);
    if (!xim) FatalError("couldn't create xim!");

    bperline = xim->bytes_per_line;
    imagedata = (byte *) malloc(bperline * high);
    if (!imagedata) FatalError("couldn't malloc imagedata");
    xim->data = (char *) imagedata;

    pp = (dithpic) ? dithpic : pic8;

    if (xim->bits_per_pixel == 2) {
      for (i=0, lip=imagedata; i<high; i++, lip+=bperline) {
	if ((i&127) == 0) WaitCursor();
	for (j=0, ip=lip, half=0; j<wide; j++,pp++,half++) {
	  xcol = ((dithpic) ? *pp : xcolors[*pp]) & 0x03;

	  if (xim->bitmap_bit_order == LSBFirst) {
	    if      (half%4==0) *ip  = xcol;
	    else if (half%4==1) *ip |= (xcol<<2);
	    else if (half%4==2) *ip |= (xcol<<4);
	    else              { *ip |= (xcol<<6); ip++; }
	  }

	  else {  /* MSBFirst.  NeXT, among others */
	    if      (half%4==0) *ip  = (xcol<<6);
	    else if (half%4==1) *ip |= (xcol<<4);
	    else if (half%4==2) *ip |= (xcol<<2);
	    else              { *ip |=  xcol;     ip++; }
	  }
	}
      }
    }

    else if (xim->bits_per_pixel == 4) {
      for (i=0, lip=imagedata; i<high; i++, lip+=bperline) {
	if ((i&127) == 0) WaitCursor();

	for (j=0, ip=lip, half=0; j<wide; j++,pp++,half++) {
	  xcol = ((dithpic) ? *pp : xcolors[*pp]) & 0x0f;

	  if (xim->bitmap_bit_order == LSBFirst) {
	    if (half&1) { *ip |= (xcol<<4);  ip++; }
	    else *ip = xcol;
	  }

	  else { /* MSBFirst */
	    if (half&1) { *ip |= xcol;  ip++; }
	    else *ip = xcol << 4;
	  }
	}
      }
    }

    else if (xim->bits_per_pixel == 8) {
      for (i=wide*high, ip=imagedata; i>0; i--,pp++,ip++) {
	if ((i&0x1ffff) == 0) WaitCursor();
	*ip = (dithpic) ? *pp : (byte) xcolors[*pp];
      }
    }
      
    else FatalError("This display's too bizarre.  Can't create XImage.");
  }
    break;
      

  /*********************************/

  case 5:
  case 6: {
    byte  *imagedata, *ip, *pp;
    int  bperline;
    
    xim = XCreateImage(theDisp, theVisual, dispDEEP, ZPixmap, 0, NULL, 
			    wide, high, 8, 0);
    if (!xim) FatalError("couldn't create xim!");

    if (xim->bits_per_pixel != 8)
      FatalError("This display's too bizarre.  Can't create XImage.");

    bperline = xim->bytes_per_line;
    imagedata = (byte *) malloc(bperline * high);
    if (!imagedata) FatalError("couldn't malloc imagedata");
    xim->data = (char *) imagedata;

    pp = (dithpic) ? dithpic : pic8;

    for (i=wide*high, ip=imagedata; i>0; i--,pp++,ip++) {
      if ((i&0x1ffff) == 0) WaitCursor();
      *ip = (dithpic) ? *pp : (byte) xcolors[*pp];
    }
  }
    break;
      

  /*********************************/

  case 12:
  case 16: {
    short  *imagedata, *ip;
    byte  *pp;

    imagedata = (short *) malloc(2*wide*high);
    if (!imagedata) FatalError("couldn't malloc imagedata");

    xim = XCreateImage(theDisp,theVisual,dispDEEP,ZPixmap,0,
			    (char *) imagedata, wide, high, 16, 0);
    if (!xim) FatalError("couldn't create xim!");

    if (dispDEEP == 12 && xim->bits_per_pixel != 16) {
      char buf[128];
      sprintf(buf,"No code for this type of display (depth=%d, bperpix=%d)",
	      dispDEEP, xim->bits_per_pixel);
      FatalError(buf);
    }

    pp = (dithpic) ? dithpic : pic8;

    if (xim->byte_order == MSBFirst) {
      for (i=wide*high, ip=imagedata; i>0; i--,pp++) {
	if ((i&0x1ffff) == 0) WaitCursor();
	if (dithpic) {
	  *ip++ = ((*pp) ? white : black) & 0xffff;
	}
	else *ip++ = xcolors[*pp] & 0xffff;
      }
    }
    else {   /* LSBFirst */
      for (i=wide*high, ip=imagedata; i>0; i--,pp++) {
	if ((i&0x1ffff) == 0) WaitCursor();

	if (dithpic) xcol = ((*pp) ? white : black) & 0xffff;
	        else xcol = xcolors[*pp];

	*ip++ = ((xcol>>8) & 0xff) | ((xcol&0xff) << 8);
      }
    }
  }
    break;

      
    /*********************************/

  case 24:
  case 32: {
    byte  *imagedata, *ip, *pp;
    imagedata = (byte *) malloc(4*wide*high);
    if (!imagedata) FatalError("couldn't malloc imagedata");
      
    xim = XCreateImage(theDisp,theVisual,dispDEEP,ZPixmap,0,
			    (char *) imagedata, wide, high, 32, 0);
    if (!xim) FatalError("couldn't create xim!");

    pp = (dithpic) ? dithpic : pic8;
      
    if (xim->byte_order == MSBFirst) {
      for (i=wide*high, ip=imagedata; i>0; i--,pp++) {
	if ((i&0x1ffff) == 0) WaitCursor();
	xcol = (dithpic) ? ((*pp) ? white : black) : xcolors[*pp];

	*ip++ = 0;
	*ip++ = (xcol>>16) & 0xff;
	*ip++ = (xcol>>8) & 0xff;
	*ip++ =  xcol & 0xff;
      }
    }

    else {  /* LSBFirst */
      for (i=wide*high, ip=imagedata; i>0; i--,pp++) {
	xcol = (dithpic) ? ((*pp) ? white : black) : xcolors[*pp];

	if ((i&0x1ffff) == 0) WaitCursor();
	*ip++ =  xcol & 0xff;
	*ip++ = (xcol>>8) & 0xff;
	*ip++ = (xcol>>16) & 0xff;
	*ip++ = 0;
      }
    }
  }
    break;


    /*********************************/
    
  default: 
    sprintf(str,"no code to handle this display type (%d bits deep)",
	    dispDEEP);
    FatalError(str);
    break;
  }

  return(xim);
}

static int foo = 0;

/***********************************/
XImage *Pic24ToXImage(pic24, wide, high)
     byte          *pic24;
     int            wide, high;
{
  /*
   * this has to do the none-to-simple bit of converting the data in 'pic24'
   * into something usable by X.
   *
   * There are two major approaches:  if we're displaying on a TrueColor
   * or DirectColor display, we've got all the colors we're going to need,
   * and 'all we have to do' is convert 24-bit RGB pixels into whatever
   * variation of RGB the X device in question wants.  No color allocation
   * is involved.
   *
   * Alternately, if we're on a PseudoColor, GrayScale, StaticColor or 
   * StaticGray display, we're going to continue to operate in an 8-bit 
   * mode.  (In that by this point, a 3/3/2 standard colormap has been
   * created for our use (though all 256 colors may not be unique...), and
   * we're just going to display the 24-bit picture by dithering with those
   * colors
   *
   */

  int     i,j;
  XImage *xim;

  xim     = (XImage *) NULL;

  if (!pic24) return xim;  /* shouldn't happen */


  /* special case: 1-bit display.  Doesn't much matter *what* the visual is */
  if (dispDEEP == 1) {
    byte  *imagedata;

    xim = XCreateImage(theDisp, theVisual, dispDEEP, XYPixmap, 0, NULL, 
		       wide, high, 32, 0);
    if (!xim) FatalError("couldn't create xim!");

    imagedata = (byte *) malloc(xim->bytes_per_line * high);
    if (!imagedata) FatalError("couldn't malloc imagedata");

    xim->data = (char *) imagedata;
    FloydDitherize1(xim, pic24, PIC24, wide, high, NULL, NULL, NULL);

    return xim;
  }




  if (theVisual->class == TrueColor || theVisual->class == DirectColor) {

    /************************************************************************/
    /* Non-ColorMapped Visuals:  TrueColor, DirectColor                     */
    /************************************************************************/

    unsigned long r, g, b, rmask, gmask, bmask, xcol;
    int           rshift, gshift, bshift, bperpix, bperline, border;
    byte         *imagedata, *lip, *ip, *pp;


    /* compute various shifting constants that we'll need... */

    rmask = theVisual->red_mask;
    gmask = theVisual->green_mask;
    bmask = theVisual->blue_mask;

    rshift = 7 - highbit(rmask);
    gshift = 7 - highbit(gmask);
    bshift = 7 - highbit(bmask);


    xim = XCreateImage(theDisp, theVisual, dispDEEP, ZPixmap, 0, NULL,
		       wide, high, 32, 0);
    if (!xim) FatalError("couldn't create X image!");

    bperline = xim->bytes_per_line;
    bperpix  = xim->bits_per_pixel;
    border   = xim->byte_order;

    imagedata = (byte *) malloc(high * bperline);
    if (!imagedata) FatalError("couldn't malloc imagedata");

    xim->data = (char *) imagedata;

    if (bperpix != 8 && bperpix != 16 && bperpix != 24 && bperpix != 32) {
      char buf[128];
      sprintf(buf,"Sorry, no code written to handle %d-bit %s",
	      bperpix, "TrueColor/DirectColor displays!");
      FatalError(buf);
    }


    lip = imagedata;  pp = pic24;
    for (i=0; i<high; i++, lip+=bperline) {
      for (j=0, ip=lip; j<wide; j++) {
	r = *pp++;  g = *pp++;  b = *pp++;

	/* shift r,g,b so that high bit of 8-bit color specification is 
	 * aligned with high bit of r,g,b-mask in visual, 
	 * AND each component with its mask,
	 * and OR the three components together
	 */

	/* shift the bits around */
	if (rshift<0) r = r << (-rshift);
	         else r = r >> rshift;
	
	if (gshift<0) g = g << (-gshift);
	         else g = g >> gshift;

	if (bshift<0) b = b << (-bshift);
	         else b = b >> bshift;

	r = r & rmask;
	g = g & gmask;
	b = b & bmask;

	xcol = r | g | b;

	if (bperpix == 32) {
	  if (border == MSBFirst) {
	    *ip++ = (xcol>>24) & 0xff;
	    *ip++ = (xcol>>16) & 0xff;
	    *ip++ = (xcol>>8)  & 0xff;
	    *ip++ =  xcol      & 0xff;
	  }
	  else {  /* LSBFirst */
	    *ip++ =  xcol      & 0xff;
	    *ip++ = (xcol>>8)  & 0xff;
	    *ip++ = (xcol>>16) & 0xff;
	    *ip++ = (xcol>>24) & 0xff;
	  }
	}

	else if (bperpix == 24) {
	  if (border == MSBFirst) {
	    *ip++ = (xcol>>16) & 0xff;
	    *ip++ = (xcol>>8)  & 0xff;
	    *ip++ =  xcol      & 0xff;
	  }
	  else {  /* LSBFirst */
	    *ip++ =  xcol      & 0xff;
	    *ip++ = (xcol>>8)  & 0xff;
	    *ip++ = (xcol>>16) & 0xff;
	  }
	}

	else if (bperpix == 16) {
	  if (border == MSBFirst) {
	    *ip++ = (xcol>>8)  & 0xff;
	    *ip++ =  xcol      & 0xff;
	  }
	  else {  /* LSBFirst */
	    *ip++ =  xcol      & 0xff;
	    *ip++ = (xcol>>8)  & 0xff;
	  }
	}

	else if (bperpix == 8) {
	  *ip++ =  xcol      & 0xff;
	}
      }
    }
  }

  else {

    /************************************************************************/
    /* CMapped Visuals:  PseudoColor, GrayScale, StaticGray, StaticColor... */
    /************************************************************************/

    byte *pic8;
    int   bwdith;

    /* in all cases, make an 8-bit version of the image, either using
       'black' and 'white', or the stdcmap */

    bwdith = 0;

    if (ncols == 0 && dispDEEP != 1) {   /* do 'black' and 'white' dither */
      /* note that if dispDEEP > 8, pic8 will just have '0' and '1' instead 
	 of 'black' and 'white' */

      pic8 = FSDither(pic24, PIC24, wide, high, NULL, NULL, NULL, 
		      (dispDEEP <= 8) ? black : 0, 
		      (dispDEEP <= 8) ? white : 1);
      bwdith = 1;
    }

    else {                               /* do color dither using stdcmap */
      pic8 = Do332ColorDither(pic24, NULL, wide, high, NULL, NULL, NULL,
			      stdrdisp, stdgdisp, stdbdisp, 256);
    }

    if (!pic8) FatalError("out of memory in Pic24ToXImage()\n");


    /* DISPLAY-DEPENDENT code follows... */


    switch (dispDEEP) {


    case 8: {
      byte  *imagedata, *ip, *pp;
      int   j, imWIDE, nullCount;
  
      nullCount = (4 - (wide % 4)) & 0x03;  /* # of padding bytes per line */
      imWIDE = wide + nullCount;
 
      /* Now create the image data - pad each scanline as necessary */
      imagedata = (byte *) malloc(imWIDE * high);
      if (!imagedata) FatalError("couldn't malloc imagedata");
      
      for (i=0, pp=pic8, ip=imagedata; i<high; i++) {
	if ((i&0x7f) == 0) WaitCursor();

	if (bwdith)
	  for (j=0; j<wide; j++, ip++, pp++) *ip = *pp;
	else
	  for (j=0; j<wide; j++, ip++, pp++) *ip = stdcols[*pp];

	for (j=0; j<nullCount; j++, ip++)  *ip = 0;
      }

      xim = XCreateImage(theDisp, theVisual, dispDEEP, ZPixmap, 0,
			 (char *) imagedata, wide, high, 32, imWIDE);
      if (!xim) FatalError("couldn't create xim!");
    }
      break;


      /*********************************/
      
    case 4: {
      byte         *imagedata, *ip, *pp;
      byte         *lip;
      int           bperline, half, j;
      unsigned long xcol;
      
      xim = XCreateImage(theDisp, theVisual, dispDEEP, ZPixmap, 0, NULL, 
			 wide, high, 32, 0);
      if (!xim) FatalError("couldn't create xim!");

      bperline = xim->bytes_per_line;
      imagedata = (byte *) malloc(bperline * high);
      if (!imagedata) FatalError("couldn't malloc imagedata");
      xim->data = (char *) imagedata;

      pp = pic8;

      if (xim->bits_per_pixel == 4) {
	for (i=0, lip=imagedata; i<high; i++, lip+=bperline) {
	  if ((i&127) == 0) WaitCursor();

	  for (j=0, ip=lip, half=0; j<wide; j++,pp++,half++) {
	    xcol = ((bwdith) ? *pp : stdcols[*pp]) & 0x0f;

	    if (xim->byte_order == LSBFirst) {
	      if (half&1) { *ip = *ip + (xcol<<4);  ip++; }
	      else *ip = xcol;
	    }
	    else {
	      if (half&1) { *ip = *ip + xcol;  ip++; }
	      else *ip = xcol << 4;
	    }
	  }
	}
      }

      else if (xim->bits_per_pixel == 8) {
	for (i=0,lip=imagedata; i<high; i++,lip+=bperline) {
	  if ((i&127)==0) WaitCursor();
	  for (j=0,ip=lip; j<wide; j++,pp++,ip++) {
	    *ip = (bwdith) ? *pp : (byte) stdcols[*pp];
	  }
	}
      }

      else FatalError("This display's too bizarre.  Can't create XImage.");
    }
      break;
      


      /*********************************/
      
    case 2: {  /* by M.Kossa@frec.bull.fr (Marc Kossa) */
               /* MSBFirst mods added by dale@ntg.com (Dale Luck) */
               /* additional fixes by  evol@infko.uni-koblenz.de 
		  (Randolf Werner) for NeXT 2bit grayscale with MouseX */

      byte  *imagedata, *ip, *pp;
      byte *lip;
      int  bperline, half, j;
      unsigned long xcol;

      xim = XCreateImage(theDisp, theVisual, dispDEEP, ZPixmap, 0, NULL, 
			 wide, high, 32, 0);
      if (!xim) FatalError("couldn't create xim!");

      bperline = xim->bytes_per_line;
      imagedata = (byte *) malloc(bperline * high);
      if (!imagedata) FatalError("couldn't malloc imagedata");
      xim->data = (char *) imagedata;

      pp = pic8;

      if (xim->bits_per_pixel == 2) {
	for (i=0, lip=imagedata; i<high; i++, lip+=bperline) {
	  if ((i&127) == 0) WaitCursor();
	  for (j=0, ip=lip, half=0; j<wide; j++,pp++,half++) {
	    xcol = ((bwdith) ? *pp : stdcols[*pp]) & 0x03;

	    if (xim->bitmap_bit_order == LSBFirst) {
	      if      (half%4==0) *ip  = xcol;
	      else if (half%4==1) *ip |= (xcol<<2);
	      else if (half%4==2) *ip |= (xcol<<4);
	      else              { *ip |= (xcol<<6); ip++; }
	    }

	    else {  /* MSBFirst.  NeXT, among others */
	      if      (half%4==0) *ip  = (xcol<<6);
	      else if (half%4==1) *ip |= (xcol<<4);
	      else if (half%4==2) *ip |= (xcol<<2);
	      else              { *ip |=  xcol;     ip++; }
	    }
	  }
	}
      }

      else if (xim->bits_per_pixel == 4) {
	for (i=0, lip=imagedata; i<high; i++, lip+=bperline) {
	  if ((i&127) == 0) WaitCursor();

	  for (j=0, ip=lip, half=0; j<wide; j++,pp++,half++) {
	    xcol = ((bwdith) ? *pp : stdcols[*pp]) & 0x0f;

	    if (xim->bitmap_bit_order == LSBFirst) {
	      if (half&1) { *ip |= (xcol<<4);  ip++; }
	      else *ip = xcol;
	    }

	    else { /* MSBFirst */
	      if (half&1) { *ip |= xcol;  ip++; }
	      else *ip = xcol << 4;
	    }
	  }
	}
      }

      else if (xim->bits_per_pixel == 8) {
	for (i=0, lip=imagedata; i<high; i++, lip+=bperline) {
	  if ((i&127) == 0) WaitCursor();

	  for (j=0, ip=lip; j<wide; j++,pp++,ip++) {
	    *ip = ((bwdith) ? *pp : stdcols[*pp]) & 0xff;
	  }
	}
      }
      
      else FatalError("This display's too bizarre.  Can't create XImage.");
    }
      break;
      

      /*********************************/
    
    case 6: {
      byte  *imagedata, *lip, *ip, *pp;
      int  bperline;
    
      xim = XCreateImage(theDisp, theVisual, dispDEEP, ZPixmap, 0, NULL, 
			 wide, high, 32, 0);
      if (!xim) FatalError("couldn't create xim!");
      
      if (xim->bits_per_pixel != 8)
	FatalError("This display's too bizarre.  Can't create XImage.");

      bperline = xim->bytes_per_line;
      imagedata = (byte *) malloc(bperline * high);
      if (!imagedata) FatalError("couldn't malloc imagedata");
      xim->data = (char *) imagedata;

      pp = pic8;

      for (i=0, lip=imagedata; i<high; i++, lip+=bperline) {
	if ((i&127) == 0) WaitCursor();

	for (j=0, ip=lip; j<wide; j++,pp++,ip++) {
	  *ip = ((bwdith) ? *pp : stdcols[*pp]) & 0x3f;

	}
      }
    }
      break;

      
      /*********************************/

    case 16: {
      short  *imagedata, *ip, *lip;
      byte   *pp;
      int     bperline;
      unsigned long xcol;

      imagedata = (short *) malloc(2*wide*high);
      if (!imagedata) FatalError("couldn't malloc imagedata");

      xim = XCreateImage(theDisp,theVisual,dispDEEP,ZPixmap,0,
			 (char *) imagedata, wide, high, 32, 0);
      if (!xim) FatalError("couldn't create xim!");
      bperline = xim->bytes_per_line;

      pp = pic8;

      if (xim->byte_order == MSBFirst) {
	for (i=0, lip=imagedata; i<high; i++, lip+=bperline) {
	  if ((i&127) == 0) WaitCursor();

	  for (j=0, ip=lip; j<wide; j++,pp++) {
	    *ip++ = ((bwdith) ? *pp : stdcols[*pp]) & 0xffff;
	  }
	}
      }

      else {   /* LSBFirst */
	for (i=0, lip=imagedata; i<high; i++, lip+=bperline) {
	  if ((i&127) == 0) WaitCursor();

	  for (j=0, ip=lip; j<wide; j++,pp++) {
	    xcol = ((bwdith) ? *pp : stdcols[*pp]) & 0xffff;
	    *ip++ = ((xcol>>8) & 0xff) | ((xcol&0xff) << 8);
	  }
	}
      }
    }
      break;

      
      /*********************************/

      /* this wouldn't seem likely to happen, but what the heck... */

    case 24:
    case 32: {
      byte  *imagedata, *ip, *pp;
      unsigned long xcol;
      int bperpix;

      imagedata = (byte *) malloc(4*wide*high);
      if (!imagedata) FatalError("couldn't malloc imagedata");
      
      xim = XCreateImage(theDisp,theVisual,dispDEEP,ZPixmap,0,
			 (char *) imagedata, wide, high, 32, 0);
      if (!xim) FatalError("couldn't create xim!");

      bperpix = xim->bits_per_pixel;

      pp = pic8;
      
      if (xim->byte_order == MSBFirst) {
	for (i=wide*high, ip=imagedata; i>0; i--,pp++) {
	  if ((i&0x1ffff) == 0) WaitCursor();
	  xcol = (bwdith) ? *pp : stdcols[*pp];

	  if (bperpix == 32) *ip++ = 0;
	  *ip++ = (xcol>>16) & 0xff;
	  *ip++ = (xcol>>8)  & 0xff;
	  *ip++ =  xcol      & 0xff;
	}
      }

      else {  /* LSBFirst */
	for (i=wide*high, ip=imagedata; i>0; i--,pp++) {
	  xcol = (bwdith) ? *pp : stdcols[*pp];

	  if ((i&0x1ffff) == 0) WaitCursor();
	  *ip++ =  xcol      & 0xff;
	  *ip++ = (xcol>>8)  & 0xff;
	  *ip++ = (xcol>>16) & 0xff;
	  if (bperpix == 32) *ip++ = 0;
	}
      }
    }     
      break;

    }   /* end of the switch */

    free(pic8);  /* since we ALWAYS make a copy of it into imagedata */
  }


  return xim;
}



/***********************************************************/
void Set824Menus(mode)
     int mode;
{
  /* move checkmark */
  conv24MB.flags[CONV24_8BIT]  = (mode==PIC8);  
  conv24MB.flags[CONV24_24BIT] = (mode==PIC24);

  if (mode == PIC24) {
    dispMB.dim[DMB_COLNORM] = 1;
    dispMB.dim[DMB_COLPERF] = 1;
    dispMB.dim[DMB_COLOWNC] = 1;

    /* turn off RAW/DITH/SMOOTH buttons (caused by picType) */
    epicMode = EM_RAW;
    SetEpicMode();

    /* turn off autoapply mode */
    /* GamSetAutoApply(0); */       /* or not! */
  }

  else if (mode == PIC8) {
    dispMB.dim[DMB_COLNORM] = 0;
    dispMB.dim[DMB_COLPERF] = (dispMode == DMB_WINDOW) ? 0 : 1;
    dispMB.dim[DMB_COLOWNC] = (dispMode == DMB_WINDOW) ? 0 : 1;

    /* turn on RAW/DITH/SMOOTH buttons */
    epicMode = EM_RAW;
    SetEpicMode();

    /* possibly turn autoapply back on */
    /* GamSetAutoApply(-1); */  /* -1 means 'back to default setting' */
  }

  SetDirRButt(F_COLORS, -1);    /* enable/disable REDUCED COLOR */
}


/***********************************************************/
void Change824Mode(mode)
     int mode;
{
  static int oldcmapmode = -1;

  if (mode == picType) return;   /* same mode, do nothing */

  Set824Menus(mode);

  if (!pic) {  /* done all we wanna do when there's no pic */
    picType = mode;
    return;  
  }

  /* should probably actually *do* something involving colors, regenrating
     pic's, drawing an Ximage, etc. */

  if (mode == PIC24) {
    byte *pic24;

    WaitCursor();
    pic24 = Conv8to24(pic, pWIDE, pHIGH, rorg,gorg,borg);
    if (!pic24) FatalError("Ran out of memory in Change824Mode()\n");

    KillOldPics();
    pic = pic24;  picType = PIC24;

    Set824Menus(picType);            /* RAW/DITH/SMOOTH buttons change */
    InstallNewPic();
  }


  else if (mode == PIC8) {
    byte *pic8;

    WaitCursor();
    pic8 = Conv24to8(pic, pWIDE, pHIGH, ncols, rMap,gMap,bMap);
    if (!pic8) FatalError("Ran out of memory in Change824Mode()\n");

    KillOldPics();
    pic = pic8;  picType = PIC8;

    Set824Menus(picType);            /* RAW/DITH/SMOOTH buttons change */
    InstallNewPic();
  }

  /* may have to explicitly redraw image window if not using root */
}


/***********************************************************/
void FreeEpic()
{
  if (egampic && egampic != epic) free(egampic);
  if (epic && epic != cpic) free(epic);
  epic = egampic = NULL;
}


/***********************************************************/
void InvertPic24(pic24, w, h)
     byte *pic24;
     int   w,h;
{
  int i;

  for (i=w*h*3; i; i--, pic24++) *pic24 = 255 - *pic24;
}




/***********************/
static int highbit(ul)
unsigned long ul;
{
  /* returns position of highest set bit in 'ul' as an integer (0-31),
   or -1 if none */

  int i;
  for (i=31; ((ul&0x80000000) == 0) && i>=0;  i--, ul<<=1);
  return i;
}
