/*
 * 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 DoCrop()
 *     static void DoCrop1(int, int, int, int, int)
 *            void UnCrop()
 *            void AutoCrop()
 *            void Rotate(int)
 *     static void RotatePic(byte *, int *, int *, int)
 *     static void FloydDitherize8(byte *)
 *     static void FloydDitherize1(XImage *)
 *            void FSDither(byte *, int, int, byte *)
 *            void CreateXImage()
 *            
 */

/*
 * 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 express 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"

#ifdef HAVE_FSQUICK
#include "fsQuick.h"
#endif

#ifdef __STDC__
static void do_zoom(int, int);
static void do_unzoom(void);
static void DoCrop1(int, int, int, int, int);
static void RotatePic(byte *, int *, int *, int);
static void FloydDitherize8(byte *);
static void FloydDitherize1(XImage *);
#else
static void do_zoom(), do_unzoom();
static void DoCrop1(), RotatePic(), FloydDitherize8();
static void FloydDitherize1();
#endif


#define DO_CROP 0
#define DO_ZOOM 1


/***********************************/
void Resize(w,h)
int w,h;
{
  int          cy,ex,ey,*cxarr, *cxarrp;
  byte        *clptr,*elptr,*epptr;
  static char *rstr = "Resizing Image.  Please wait...";

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

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

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

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

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

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

  epicmode = EM_RAW;   SetEpicMode();

  if (w==cWIDE && h==cHIGH) {  /* 1:1 expansion.  point epic at cpic */
    if (epic != cpic && epic!=NULL) free(epic);
    epic = cpic;  eWIDE = cWIDE;  eHIGH = cHIGH;
    if (psUp) PSResize();   /* if PSDialog is open, mention size change  */
  }

  else {  /* have to actually SCALE THE PIC.  Drats! */
    WaitCursor();

    /* if it's a big image, this could take a while.  mention it,
        but only if we're actually changing the size, and only if we're
	not using the root window */

    if (w*h>(500*500) && (w!=eWIDE || h!=eHIGH) && !useroot && mainW) {
      XSetForeground(theDisp, theGC, fg);
      XSetBackground(theDisp, theGC, bg);
      XDrawImageString(theDisp,mainW,theGC,CENTERX(mfinfo,w/2,rstr),
		       CENTERY(mfinfo,h/2),rstr, strlen(rstr));
      XFlush(theDisp);
    }

    /* first, kill the old epic, if one exists */
    if (epic!=NULL && epic!=cpic) {
      free(epic);  epic = NULL;
    }

    /* create a new epic of the appropriate size */
    eWIDE = w;  eHIGH = h;
    epic = (byte *) malloc(w*h);
    if (epic==NULL) {
      sprintf(str,"unable to malloc a %dx%d image\n",w,h);
      FatalError(str);
    }
    if (psUp) PSResize();   /* if PSDialog is open, mention size change  */

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

    /* OPTIMIZATON IDEA.  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] = (cWIDE * ex) / eWIDE;

    elptr = epptr = epic;

    for (ey=0;  ey<eHIGH;  ey++, elptr+=eWIDE) {
      if ((ey&127) == 0) WaitCursor();
      cy = (cHIGH * ey) / eHIGH;
      epptr = elptr;
      clptr = cpic + (cy * cWIDE);
      for (ex=0, cxarrp = cxarr;  ex<eWIDE;  ex++, epptr++) 
	*epptr = clptr[*cxarrp++];
    }
    free(cxarr);
  }

  /* now make something displayable out of epic */
  CreateXImage();
}
                


/***********************************/
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;

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


  /* compute a cropping rectangle (in screen coordinates) that's half 
     the size of eWIDE,eHIGH, centered around x,y, but fully enclosed 
     by the window */

  w = eWIDE/2;  h = eHIGH/2;
  crx1 = mx - w/2;  cry1 = my - 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;

  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;


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


/***********************************/
static void do_unzoom()
{
  int x,y,x2,y2,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;

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


/***********************************/
void DoCrop()
{
  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;

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


static void DoCrop1(x,y,w,h,zm)
int x,y,w,h,zm;
{
  int   i,j;
  byte *cp, *pp;
  double expw, exph;

  BTSetActive(&but[BCROP],0);
  /* epicmode = EM_RAW;   SetEpicMode(); */

  /* dispose of old cpic and epic */
  if (epic && epic != cpic) free(epic);
  if (cpic && cpic !=  pic) free(cpic);
  epic = 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,"%s: cropping to %dx%d rectangle at %d,%d\n",
		     cmd, cWIDE, cHIGH, cXOFF, cYOFF);

  /* kill old Ximage so that Resize will be forced to generate a new one */
  xvDestroyImage(theImage);
  theImage = NULL;

  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);
    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 + cXOFF;
      for (j=0; j<cWIDE; j++) 
	*cp++ = *pp++;
    }
  }


  /* generate an appropriate 'epic' */
  if (epicmode == EM_RAW || epicmode == EM_DITH) {
    if (zm == DO_CROP) Resize((int) (cWIDE * expw), (int) (cHIGH * exph));
                  else Resize(eWIDE, eHIGH);
  }

  else if (epicmode == EM_SMOOTH) {
    if (zm == DO_CROP) {
      eWIDE = (int) (cWIDE * expw);
      eHIGH = (int) (cHIGH * exph);
    }
    Smooth();
  }
  
  if (epicmode == EM_DITH) ColorDither(NULL, eWIDE, eHIGH);



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


  if (zm == DO_CROP) {
    /* shrink window */
    WCrop((int) (cWIDE * expw), (int) (cHIGH * exph));
  }
  else DrawWindow(0, 0, eWIDE, eHIGH);
  
  SetCursors(-1);
}



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

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

  BTSetActive(&but[BUNCROP],0);
  epicmode = EM_RAW;   SetEpicMode();

  /* dispose of old cpic and epic */
  if (epic && epic != cpic) free(epic);
  if (cpic && cpic !=  pic) free(cpic);
  epic = cpic = NULL;
  
  /* kill old Ximage so that Resize will be forced to generate a new one */
  xvDestroyImage(theImage);
  theImage = NULL;


  w = (pWIDE * eWIDE) / cWIDE;   h = (pHIGH * eHIGH) / cHIGH;
  if (w>maxWIDE || h>maxHIGH) {
    w = pWIDE / normFact;  h = pHIGH / normFact;
  }

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


  /* generate an appropriate 'epic' */
  if (epicmode == EM_RAW || epicmode == EM_DITH) {
    Resize(w, h);
  }
  else if (epicmode == EM_SMOOTH) { 
    eWIDE = w;  eHIGH = h;  
    Smooth();
  }

  if (epicmode == EM_DITH) ColorDither(NULL, eWIDE, eHIGH);


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

/***********************************/
void AutoCrop()
{
  byte *cp, *cp1;
  int  i, ctop, cbot, cleft, cright;
  byte bgcol;

  ctop = cbot = cleft = cright = 0;

  /* 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)
    DoCrop1(cXOFF+cleft, cYOFF+ctop, 
	    cWIDE-(cleft+cright), cHIGH-(ctop+cbot), DO_CROP);
}


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

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

  RotatePic(pic, &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, &cWIDE, &cHIGH,dir);
  }
  else { cWIDE = pWIDE;  cHIGH = pHIGH; }

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

  CreateXImage();
  WRotate();
}


/************************/
static void RotatePic(pic, wp, hp, dir)
byte *pic;
int *wp, *hp;
int 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;
  unsigned int w,h;

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

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


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

  free(pic1);

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

  

/***********************************/
void Flip(dir)
int dir;
{
  /* dir=0: flip horizontally, else vertically */
  WaitCursor();

  FlipPic(pic, pWIDE, pHIGH, dir);

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

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

  CreateXImage();
  if (mainW && !useroot) DrawWindow(0, 0, eWIDE, eHIGH);
}


/************************/
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;

  if (dir==0) {                /* horizontal flip */
    for (i=0; i<h; i++) {
      plin = pic + i*w;
      for (j=0; j<w/2; j++) {
	k = plin[j];  
	plin[j] = plin[w - 1 - j];
	plin[w - 1 - j] = k;
      }
    }
  }

  else {                      /* vertical flip */
    for (i=0; i<w; i++)
      for (j=0; j<h/2; j++) {
	k = pic[j*w + i];
	pic[j*w + i] = pic[(h - 1 - j)*w + i];
	pic[(h - 1 -j)*w + i] = k;
      }
  }
}

  

/************************/
static void FloydDitherize8(image)
     byte *image;
{
  /* takes epic, and builds a black&white dithered version of it.
     stores result in 8bit Pixmap format in 'image' */

  int i;
  byte *p;

  FSDither(epic, eWIDE, eHIGH, image);

  /* set to 'black' and 'white' instead of '0' and '1' */
  if (black != 0 || white != 1) {
    for (i=eWIDE*eHIGH, p=image; i>0; i--, p++) {
      if (*p) *p = white;  else *p = black;
    }
  }
}



#ifdef HAVE_FSQUICK
/************************/
static void FloydDitherize1(ximage)
     XImage *ximage;
{
  /*
   * Like FloydDitherize8, but output is a 1-bit per pixel XYBitmap,
   * packed 8 pixels per byte.  (Now uses fsQuick  -- David Rosen)
   */

  byte               fsgamcr_r[256];
  static int         initialized=0;
  static fsQTablesT *fsQTablesP;

  if (DEBUG) fprintf(stderr,"Ditherizing1...");

  if (!initialized) {
    fsQTablesP = fsQInit((int)black, ximage->bitmap_bit_order==LSBFirst);
    initialized=1;
  }

  /* combine colormap and gamma correction in single map table: */
  { register int i;
    for (i=256; --i>=0;) fsgamcr_r[i]=fsgamcr[MONO(r[i],g[i],b[i])];
  }

  if ((sizeof(*epic) != sizeof(fsQPicT)) || 
      (sizeof(fsQBitmapT) != sizeof(byte)) ||
      (fsQ_BITMAP_BITS != 8))
    FatalError("FloydDitherize1: type mismatch with fsQuick.h definitions");

  fsQuick(epic, fsgamcr_r, (int) eHIGH, (int) eWIDE,
	  (fsQPicT *) ximage->data, fsQTablesP, (int) ximage->bytes_per_line);

  if (DEBUG) fprintf(stderr,"done\n");
}

#else /* don't HAVE_FSQUICK */

/************************/
static void FloydDitherize1(ximage)
     XImage *ximage;
{
  /* same as FloydDitherize8, but output is a 1-bit per pixel XYBitmap,
     packed 8 pixels per byte */

  register short *dp;
  register byte   pix8, bit;
  short          *dithpic;
  int             i, j, err, bperln, order;
  byte           *pp, *image, w1, b1, w8, b8, rgb[256];


  /* first thing to do is build rgb[], which will hold the B/W intensity
     of the colors in the r,g,b arrays */
  for (i=0; i<256; i++)
    rgb[i] = MONO(r[i], g[i], b[i]);

  image  = (byte *) ximage->data;
  bperln = ximage->bytes_per_line;
  order  = ximage->bitmap_bit_order;

  if (DEBUG) fprintf(stderr,"Ditherizing1...");

  dithpic = (short *) malloc(eWIDE * eHIGH * sizeof(short));
  if (dithpic == NULL) FatalError("not enough memory to ditherize");

  w1 = white&0x1;  b1=black&0x1;
  w8 = w1<<7;  b8 = b1<<7;        /* b/w bit in high bit */
  
  /* copy rgb[epic] into dithpic so that we can run the algorithm */
  pp = epic;  dp = dithpic;
  for (i=eHIGH * eWIDE; i>0; i--) *dp++ = fsgamcr[rgb[*pp++]];

  dp = dithpic;
  pp = image;

  for (i=0; i<eHIGH; i++) {
    pp = image + i*bperln;

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

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

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

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

	if (i<eHIGH-1) {
	  dp[eWIDE] += ((err*5)/16);
	  if (j>0)       dp[eWIDE-1] += ((err*3)/16);
	  if (j<eWIDE-1) dp[eWIDE+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<eWIDE; j++,dp++) {
	if (*dp<128) { err = *dp;     pix8 |= b1; }
	        else { err = *dp-255; pix8 |= w1; }

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

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

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

  if (DEBUG) fprintf(stderr,"done\n");

  free(dithpic);
}

#endif /* HAVE_FSQUICK */


/************************/
void FSDither(inpic, w, h, outpic)
byte *inpic, *outpic;
int   w,h;
{
  /* takes inpic, and builds a black&white dithered version of it.
     stores result as 1 byte per pixel format in 'image'
     black = 0;  white = 1;
     temporarily mallocs a w*h array of SHORTS.
     (need to be signed, also to avoid overflow problems.)  */

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

  short *dp, *dithpic;
  int    i, j, err, w1, h1;
  byte  *pp, rgb[256];

  if (DEBUG) fprintf(stderr,"Ditherizing...");

  /* first thing to do is build rgb[], which will hold the B/W intensity
     of the colors in the r,g,b arrays */
  for (i=0; i<256; i++)
    rgb[i] = MONO(r[i], g[i], b[i]);


  dithpic = (short *) malloc(w*h * sizeof(short));
  if (dithpic == NULL) FatalError("not enough memory to ditherize");

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

  /* copy rgb[inpic] into dithpic so that we can run the algorithm */
  pp = inpic;  dp = dithpic;
  for (i=w*h; i>0; i--) *dp++ = fsgamcr[rgb[*pp++]];

  dp = dithpic;  pp = outpic;
  for (i=0; i<h; i++) {
    if ((i&15) == 0) WaitCursor();
    for (j=0; j<w; j++,dp++,pp++) {
      if (*dp<128) { err = *dp;     *pp = 0; }
              else { err = *dp-255; *pp = 1; }

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

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

  if (DEBUG) fprintf(stderr,"done\n");

  free(dithpic);
}






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


  /*
   * this has to do the tricky bit of converting the data in 'epic'
   * into something usable for X.
   *
   * Algorithm notes:
   *   if dispDEEP is 8, nothing has to be done other than create an
   *      Ximage (ZPixmap, depth=8) and point it at the 'epic' data.
   *
   *   if dispDEEP is 1, format'll be an XYBitmap, special case code
   *   
   *   if dispDEEP is 4, format'll be a ZPixmap, 4 or 8 bits per pixel
   *
   *   if dispDEEP is 6, format'll be a ZPixmap, 8 bits per pixel
   *
   *   if dispDEEP is 16, format'll be a ZPixmap, 16 bits per pixel
   *
   *   if dispDEEP is 24 or 32, format'll be a ZPixmap.  32 bits per pixel
   *
   */

  if (DEBUG) 
    fprintf(stderr,"%s: creating a %dx%d Ximage, %d bits deep\n",
	    cmd, eWIDE, eHIGH, dispDEEP);

  /* destroy old image and imagedata, if there is one */
  xvDestroyImage(theImage);
  theImage = NULL;

  if (!epic) {
    /* fprintf(stderr,"CreateXImage called while epic was null\n"); */
    Resize(eWIDE,eHIGH);
    return;
  }

  switch (dispDEEP) 
    {

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

	for (i=0, pp=tmppic, ip=imagedata; i<eHIGH; i++) {
	  if ((i&0x7f) == 0) WaitCursor();
	  for (j=0; j<eWIDE; j++, ip++, pp++) *ip = *pp;
	  for (j=0; j<nullCount; j++, ip++)   *ip = 0;
	}

	free(tmppic);
      }
      else {
	for (i=0, pp=epic, ip=imagedata; i<eHIGH; i++) {
	  if ((i&0x7f) == 0) WaitCursor();
	  for (j=0; j<eWIDE; j++, ip++, pp++) 
	    *ip = (byte) cols[*pp];
	  for (j=0; j<nullCount; j++, ip++) *ip = 0;
	}
      }
      
      theImage = XCreateImage(theDisp,theVisual,dispDEEP,ZPixmap,0,
			      (char *) imagedata, eWIDE, eHIGH, 32, imWIDE);
      if (!theImage) FatalError("couldn't create theImage!");
      }
      break;

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

    case 1:
      {
      byte  *imagedata;

      theImage = XCreateImage(theDisp, theVisual, dispDEEP, XYPixmap, 0, NULL, 
			      eWIDE, eHIGH, 8, 0);
      if (!theImage) FatalError("couldn't create theImage!");
      imagedata = (byte *) malloc(theImage->bytes_per_line * eHIGH);
      if (!imagedata) FatalError("couldn't malloc imagedata");
      theImage->data = (char *) imagedata;
      FloydDitherize1(theImage);
      }
      break;
      
    /*********************************/
      
    case 4: {
      byte  *imagedata, *ip, *pp;
      byte *lip;
      int  bperline, half, j;

      theImage = XCreateImage(theDisp, theVisual, dispDEEP, ZPixmap, 0, NULL, 
			      eWIDE, eHIGH, 8, 0);
      if (!theImage) FatalError("couldn't create theImage!");

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

      if (ncols==0) {            /* ditherize */
	byte *dith;
	dith = (byte *) malloc(eWIDE * eHIGH);
	if (!dith) FatalError("can't create dithered image");
	FloydDitherize8(dith);

	if (theImage->bits_per_pixel == 4) {
	  for (i=0, pp=dith, lip=imagedata; i<eHIGH; i++, lip+=bperline)
	    for (j=0, ip=lip, half=0; j<eWIDE; j++,pp++,half++) {
	      if (ImageByteOrder(theDisp) == LSBFirst) {
		if (half&1) { *ip = *ip + ((*pp&0x0f)<<4);  ip++; }
		else *ip = *pp&0x0f;
	      }
	      else {
		if (half&1) { *ip = *ip + (*pp&0x0f);  ip++; }
		else *ip = (*pp&0x0f) << 4;
	      }
	    }
	}
	else if (theImage->bits_per_pixel == 8)
	  memcpy(imagedata, dith, eWIDE*eHIGH);
	
	else FatalError("This display is too bizarre.  Can't create XImage.");

	free(dith);
      }

      else {     /* don't ditherize */
	if (theImage->bits_per_pixel == 4) {
	  for (i=0, pp=epic, lip=imagedata; i<eHIGH; i++, lip+=bperline) {
	    if ((i&127) == 0) WaitCursor();
	    for (j=0, ip=lip, half=0; j<eWIDE; j++,pp++,half++) {
	      if (ImageByteOrder(theDisp) == LSBFirst) {
		if (half&1) { *ip = *ip + ((cols[*pp]&0x0f)<<4);  ip++; }
		else *ip = cols[*pp]&0x0f;
	      }
	      else {
		if (half&1) { *ip = *ip + (cols[*pp]&0x0f);  ip++; }
		else *ip = (cols[*pp]&0x0f) << 4;
	      }
	    }
	  }
	}
	else if (theImage->bits_per_pixel == 8) {
	  for (i=eWIDE*eHIGH, pp=epic, ip=imagedata; i>0; i--,pp++,ip++) {
	    if ((i&0x1ffff) == 0) WaitCursor();
	    *ip = (byte) cols[*pp];
	  }
	}
	else FatalError("This display's too bizarre.  Can't create XImage.");
      }
      
      }
      break;
      

    /*********************************/
      
    case 2: {  /* by M.Kossa@frec.bull.fr (Marc Kossa) */

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

      theImage = XCreateImage(theDisp, theVisual, dispDEEP, ZPixmap, 0, NULL, 
			            eWIDE, eHIGH, 8, 0);
      if (!theImage) FatalError("couldn't create theImage!");

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

      if (ncols==0) {            /* ditherize */
	byte *dith;
	dith = (byte *) malloc(eWIDE * eHIGH);
	if (!dith) FatalError("can't create dithered image");
	FloydDitherize8(dith);


	if (theImage->bits_per_pixel == 2) {
	  for (i=0, pp=dith, lip=imagedata; i<eHIGH; i++, lip+=bperline) {
	    if ((i&127) == 0) WaitCursor();
	    for (j=0, ip=lip, half=0; j<eWIDE; j++,pp++,half++) {
	      if (half%4 == 0) *ip = *pp&0x03;
	      else if (half%4 == 1) { *ip = *ip + ((*pp&0x03)<<2);  ip++; }
	      else if (half%4 == 2) { *ip = *ip + ((*pp&0x03)<<4);  ip++; }
	      else  *ip = *ip + ((*pp&0x03)<<6);  ip++;
	    }
	  }
	}

	else if (theImage->bits_per_pixel == 4) { 
	  for (i=0, pp=dith, lip=imagedata; i<eHIGH; i++, lip+=bperline) {
	    if ((i&127) == 0) WaitCursor();
	    for (j=0, ip=lip, half=0; j<eWIDE; j++,pp++,half++) {
	      if (half&1) { *ip = *ip + ((*pp&0x0f)<<4);  ip++; }
	               else *ip = *pp&0x0f;
	    }
	  }
	}

	else if (theImage->bits_per_pixel == 8)
	  memcpy(imagedata, dith, eWIDE*eHIGH);
	
	else FatalError("This display is too bizarre.  Can't create XImage.");
	
	free(dith);
      }

      else {     /* don't ditherize */

	if (theImage->bits_per_pixel == 2) {
	  for (i=0, pp=epic, lip=imagedata; i<eHIGH; i++, lip+=bperline) {
	    if ((i&127) == 0) WaitCursor();
	    for (j=0, ip=lip, half=0; j<eWIDE; j++,pp++,half++) {
	      if (half%4 == 0) *ip = cols[*pp]&0x03;
	      else if (half%4 == 1) { *ip = *ip+((cols[*pp]&0x03)<<2); ip++; }
	      else if (half%4 == 2) { *ip = *ip+((cols[*pp]&0x03)<<4); ip++; }
	      else  *ip = *ip + ((cols[*pp]&0x03)<<6); ip++;
	    }
	  }
	}

	else if (theImage->bits_per_pixel == 4) {
	  for (i=0, pp=epic, lip=imagedata; i<eHIGH; i++, lip+=bperline) {
	    if ((i&127) == 0) WaitCursor();
	    for (j=0, ip=lip, half=0; j<eWIDE; j++,pp++,half++) {
	      if (half&1) { *ip = *ip + ((cols[*pp]&0x0f)<<4);  ip++; }
	      else *ip = cols[*pp]&0x0f;
	    }
	  }
	}

	else if (theImage->bits_per_pixel == 8) {
	  for (i=eWIDE*eHIGH, pp=epic, ip=imagedata; i>0; i--,pp++,ip++) {
	    if ((i&0x1ffff) == 0) WaitCursor();
	    *ip = (byte) cols[*pp];
	  }
	}
	
	else FatalError("This display's too bizarre.  Can't create XImage.");
      }
    }
      break;
      

    /*********************************/
      
    case 6: {
      byte  *imagedata, *ip, *pp;
      int  bperline;

      theImage = XCreateImage(theDisp, theVisual, dispDEEP, ZPixmap, 0, NULL, 
			      eWIDE, eHIGH, 8, 0);
      if (!theImage) FatalError("couldn't create theImage!");

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

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

      if (ncols==0) FloydDitherize8(imagedata);
      else {
	for (i=eWIDE*eHIGH, pp=epic, ip=imagedata; i>0; i--,pp++,ip++) {
	  if ((i&0x1ffff) == 0) WaitCursor();
	  *ip = (byte) cols[*pp];
	}
      }
    }
    break;
      
    /*********************************/

    case 16: {
      short  *imagedata, *ip;
      byte  *pp;
      imagedata = (short *) malloc(2*eWIDE*eHIGH);
      if (!imagedata) FatalError("couldn't malloc imagedata");

      theImage = XCreateImage(theDisp,theVisual,dispDEEP,ZPixmap,0,
			      (char *) imagedata, eWIDE, eHIGH, 16, 0);
      if (!theImage) FatalError("couldn't create theImage!");

      if (theImage->byte_order == MSBFirst)
	for (i=eWIDE*eHIGH, pp=epic, ip=imagedata; i>0; i--,pp++) {
	  if ((i&0x1ffff) == 0) WaitCursor();
	  *ip++ = cols[*pp] & 0xffff;
	}
      else
	for (i=eWIDE*eHIGH, pp=epic, ip=imagedata; i>0; i--,pp++) {
	  if ((i&0x1ffff) == 0) WaitCursor();
	  *ip++ = ((cols[*pp] >> 8) & 0xff) | ((cols[*pp] & 0xff) >> 8);
	}
    }
    break;

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

    case 24:
    case 32:
      {
      byte  *imagedata, *ip, *pp;
      imagedata = (byte *) malloc(4*eWIDE*eHIGH);
      if (!imagedata) FatalError("couldn't malloc imagedata");
      
      theImage = XCreateImage(theDisp,theVisual,dispDEEP,ZPixmap,0,
			      (char *) imagedata, eWIDE, eHIGH, 32, 0);
      if (!theImage) FatalError("couldn't create theImage!");
      
      if (theImage->byte_order == MSBFirst) 
	for (i=eWIDE*eHIGH, pp=epic, ip=imagedata; i>0; i--,pp++) {
	  if ((i&0x1ffff) == 0) WaitCursor();
	  *ip++ = 0;
	  *ip++ = (cols[*pp]>>16) & 0xff;
	  *ip++ = (cols[*pp]>>8) & 0xff;
	  *ip++ =  cols[*pp] & 0xff;
	}
      else 
	for (i=eWIDE*eHIGH, pp=epic, ip=imagedata; i>0; i--,pp++) {
	  if ((i&0x1ffff) == 0) WaitCursor();
	  *ip++ =  cols[*pp] & 0xff;
	  *ip++ = (cols[*pp]>>8) & 0xff;
	  *ip++ = (cols[*pp]>>16) & 0xff;
	  *ip++ = 0;
	}
      }      
      break;

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

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




