/*
 *
 * $Header: /usr/u/wjr/src/ADT/RCS/image.c,v 1.16 1992/08/20 21:09:19 rucklidg Exp $
 *
 * Copyright (c) 1990, 1991, 1992 Cornell University.  All Rights Reserved.  
 *  
 * Copyright (c) 1991, 1992 Xerox Corporation.  All Rights Reserved.  
 *  
 * Use, reproduction, preparation of derivative works, and distribution
 * of this software is permitted.  Any copy of this software or of any
 * derivative work must include both the above copyright notices of
 * Cornell University and Xerox Corporation and this paragraph.  Any
 * distribution of this software or derivative works must comply with all
 * applicable United States export control laws.  This software is made
 * available AS IS, and XEROX CORPORATION DISCLAIMS ALL WARRANTIES,
 * EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE,
 * AND NOTWITHSTANDING ANY OTHER PROVISION CONTAINED HEREIN, ANY
 * LIABILITY FOR DAMAGES RESULTING FROM THE SOFTWARE OR ITS USE IS
 * EXPRESSLY DISCLAIMED, WHETHER ARISING IN CONTRACT, TORT (INCLUDING
 * NEGLIGENCE) OR STRICT LIABILITY, EVEN IF XEROX CORPORATION IS ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGES.
 */

static char rcsid[] = "@(#)$Header: /usr/u/wjr/src/ADT/RCS/image.c,v 1.16 1992/08/20 21:09:19 rucklidg Exp $";


#include "misc.h"
#include "image.h"
#include "magic.h"
#include "imConv.h"
#include "filtered-fp.h"

#include <stdio.h>
#include <ctype.h>

/* File tags */
/* Standard pbmplus tags */
#define	TAG_BINARY	"P4"
#define	TAG_GRAY	"P5"
#define	TAG_RGB		"P6"
/* Nonstandard wjr/gregk tags */
#define	TAG_FLOAT	"Q1"
#define	TAG_DOUBLE	"Q2"
#define	TAG_LONG	"Q3"
#define	TAG_SHORT	"Q4"
#define TAG_HSV         "Q10"
#define TAG_RGBF        "Q11"

typedef struct {
  ImageType type;
  char *tag;
  boolean dummy;
} ImageDescriptor;

static ImageDescriptor IMAGE_DESC[] = {{IMAGE_GRAY,   TAG_GRAY,   TRUE},
				       {IMAGE_FLOAT,  TAG_FLOAT,  FALSE},
				       {IMAGE_RGB,    TAG_RGB,    TRUE},
				       {IMAGE_DOUBLE, TAG_DOUBLE, FALSE},
				       {IMAGE_BINARY, TAG_BINARY, FALSE},
				       {IMAGE_LONG,   TAG_LONG,   FALSE},
				       {IMAGE_SHORT,  TAG_SHORT,  FALSE},
				       {IMAGE_HSV,    TAG_HSV,    FALSE},
				       {IMAGE_RGBF,   TAG_RGBF,   FALSE}};

#define NIMAGE_DESCS  ((sizeof (IMAGE_DESC)) / (sizeof (ImageDescriptor)))


boolean ImageAllowLoadConversion = 0;


/*
 * This routine allocates a rectangular area of width*height*el_size bytes
 * (suitable for holding width*height elements of el_size size), and
 * initialises it to zero. It then allocates and sets up an array of
 * height pointers into this, each one pointing to a row (width*el_size bytes).
 *
 * Each pointer is offset by xbase elements, and the returned pointer is
 * offset by ybase rows; this means that the first element in this array
 * is accessed by array[ybase][xbase]. Note that xbase and ybase may be
 * negative.
 *
 * Note that this isn't really type-safe: it takes this (void **) pointer
 * which will get cast, for example, into a (float **) pointer, which isn't
 * really legit. On machines where all pointers have the same format,
 * it will work. The problem is also that dereferencing a (float **)
 * should get you a (float *), but will actually point to the bit-pattern
 * of a (void *), which could be cast into that (float *) with no problems,
 * but which might not work if it is simply *treated* as a (float *)
 * without the cast. Bah. Doing it right would be too horrendous for words.
 * C just can't handle generics very well...
 */
static void **allocImage(int width, int height, int xbase, int ybase,
			 unsigned el_size, void *wrap)
{
  register int i;
  register void *mainArea;
  register void **ptrArea;
  register char *p;
  register void **q;
  extern void *calloc();

  
  assert(width > 0  &&  height > 0);
  
  if (wrap == NULL) {			     /* Allocate the actual image data area */
    mainArea = calloc(height, width*el_size);
    if (mainArea == (void *)NULL) {
      return((void **)NULL);
    }
  }
  else {				     /* wrapping existing storage */
    mainArea = wrap;
  }

  /* Allocate a pointer for each row */
  ptrArea = (void **)malloc(height * sizeof(void *));
  if (ptrArea == (void **)NULL) {
    if (wrap == NULL)
      free(mainArea);
    return((void **)NULL);
  }

  /* Set up the row pointers to point to the (offset) rows */
  for (i = 0, q = ptrArea, p = (char *)mainArea;
       i < height;
       i++, q++, p += width*el_size) {
    *q = (void *)(p - xbase * (int)el_size);
  }

  /* and return the (offset) row pointer area */
  return(ptrArea - ybase);
}

/*
 * This routine gets rid of a memory block allocated by allocImage above.
 */
static void freeImage(void **ptr, int xbase, int ybase,
		      unsigned el_size, boolean wrapped)
{
  if (!wrapped)     /* Free the memory block. This should give the first element. */
    free((void *)((char *)(*(ptr + ybase)) + xbase * (int)el_size));

  free((void *)(ptr + ybase));		     /* and the pointer block */
}


typedef struct {uchar x;} GenStruct;
imDefStructImage(GenericStructImage, GenStruct, void *); /* a generic structure image */

void *im_NewOrWrap_(ImageType type, int width, int height, int xbase, int ybase,
		    void *raw, int elemsize, int headsize, void *dupImg)
{
  void **areaPtr;
  ImageHeader *newHeader;
  

  if (width <= 0  ||  height <= 0) {
    return (void *) NULL;
  }

  /* Allocate and initialise the header */
  newHeader = (ImageHeader *)malloc(sizeof(* newHeader));
  if (newHeader == (ImageHeader *)NULL) {
    return((void *)NULL);
  }

  newHeader->width     = width;
  newHeader->height    = height;
  newHeader->xbase     = xbase;
  newHeader->ybase     = ybase;
  newHeader->tag       = type;
  newHeader->elemsize  = elemsize;
  newHeader->headsize  = headsize;
  newHeader->docstring = NULLSTRING;
  newHeader->wrapped   = (raw == NULL) ? FALSE : TRUE;
  newHeader->Ximage    = (void *) NULL;

  if (elemsize != 0) {
    assert(raw == NULL && type == IMAGE_STRUCT);
  }
  else {
    assert(type != IMAGE_STRUCT);
  }
  
  /*
   * Allocate the data area for the appropriate type of image, 
   * allocate and initialise the actual *Image and return it.
   * This exists in one form for each type of Image.
   */

#define ALLOC_IMG(imtype, elemtype)                                                    \
    {                                                                                  \
      imtype newImg;                                                                   \
                                                                                       \
      newImg = (imtype)malloc(sizeof(* newImg));                                       \
      if (newImg == (imtype)NULL) {                                                    \
	free((void *)newHeader);                                                       \
	return((void *)NULL);                                                          \
      }                                                                                \
      areaPtr = allocImage(width, height, xbase, ybase, sizeof(elemtype), raw);        \
      if (areaPtr == (void **)NULL) {                                                  \
	free((void *)newHeader);                                                       \
	free((void *)newImg);                                                          \
	return((void *)NULL);                                                          \
      }                                                                                \
      newImg->header = newHeader;                                                      \
      newImg->data = (elemtype **) areaPtr;                                            \
      newImg->userdata.ptr = ((dupImg == NULL)                                         \
			      ? (void *) NULL                                          \
			      : ((imtype) dupImg)->userdata.ptr);                      \
      newHeader->anyim     = (AnyImage) newImg;                                        \
                                                                                       \
      return ((void *)newImg);                                                         \
      break;                                                                           \
    }
    
  switch(type) {
  case IMAGE_GRAY:   ALLOC_IMG(GrayImage,   uchar);
  case IMAGE_FLOAT:  ALLOC_IMG(FloatImage,  float);
  case IMAGE_RGB:    ALLOC_IMG(RGBImage,    RGB);
  case IMAGE_DOUBLE: ALLOC_IMG(DoubleImage, double);
  case IMAGE_BINARY: ALLOC_IMG(BinaryImage, char);
  case IMAGE_LONG:   ALLOC_IMG(LongImage,   long);
  case IMAGE_PTR:    ALLOC_IMG(PtrImage,    void *);
  case IMAGE_SHORT:  ALLOC_IMG(ShortImage,  short);
  case IMAGE_HSV:    ALLOC_IMG(HSVImage,    HSV);
  case IMAGE_RGBF:   ALLOC_IMG(RGBFloatImage,    RGBFloat);

  case IMAGE_STRUCT: {
    GenericStructImage newImg;

    newImg = (GenericStructImage) malloc(headsize);
    if (newImg == (GenericStructImage) NULL) {
      free((void *) newHeader);
      return((void *) NULL);
    }
    areaPtr = allocImage(width, height, xbase, ybase, elemsize, raw);
    if (areaPtr == (void **) NULL) {
      free((void *) newHeader);
      free((void *) newImg);
      return((void *) NULL);
    }

    if (dupImg != NULL) {		     /* if doing an imDup() need to */
      int i;				     /* copy the userdata field */

      for (i = 0; i < headsize; i++) {
	((char *) newImg)[i] = ((char *) dupImg)[i];
      }
    }
    newImg->header = newHeader;
    newImg->data = (GenStruct **) areaPtr;
    return ((void *)newImg);
    break;
  }
    
  default: {
    panic("bad image tag");
  }
  }
#undef ALLOC_IMG

  /*NOTREACHED*/
  return NULL;
}


void im_Free_(void *im, ImageType type)
{
  void **areaPtr=0;
  ImageHeader *header=0;
  unsigned el_size=0;


  assert(im != (void *) NULL);

  /* Pull out the data area, the header, and the size of the element */

#define FREE_IMG(imtype, elemtype)              \
    {                                           \
      areaPtr = (void **) (((imtype) im)->data);  \
      header  = ((imtype) im)->header;           \
      el_size = sizeof(elemtype);               \
      break;                                    \
    }

  switch(type) {
  case IMAGE_GRAY:   FREE_IMG(GrayImage, uchar);	
  case IMAGE_FLOAT:  FREE_IMG(FloatImage, float);	
  case IMAGE_RGB:    FREE_IMG(RGBImage, RGB);
  case IMAGE_DOUBLE: FREE_IMG(DoubleImage, double);
  case IMAGE_BINARY: FREE_IMG(BinaryImage, char);
  case IMAGE_LONG:   FREE_IMG(LongImage, long);
  case IMAGE_PTR:    FREE_IMG(PtrImage, void *);
  case IMAGE_SHORT:  FREE_IMG(ShortImage, short);
  case IMAGE_HSV:    FREE_IMG(HSVImage, HSV);
  case IMAGE_RGBF:   FREE_IMG(RGBFloatImage, RGBFloat);

  case IMAGE_STRUCT: {
    areaPtr = (void **) (((GenericStructImage) im)->data);
    header  = ((GenericStructImage) im)->header;
    el_size = header->elemsize;
    break;
  }

  default: {
    panic("bad image tag");
  }
  }
#undef FREE_IMG
    
  /* and get rid of all of them */

  if (header->docstring != NULLSTRING)
    strFree(header->docstring);
  freeImage(areaPtr, header->xbase, header->ybase, el_size, header->wrapped);
  free((void *) im);
  free((void *) header);
}


static void *do_imLoad_(ImageType types, char *filename, void *im, ImageHeader *header);
static void *do_imLoadF_(ImageType types, FILE *inFile, void *im, ImageHeader *header);


void *imLoad(ImageType types, char *filename)
{
  return do_imLoad_(types, filename, (void *) NULL, (void *) NULL);
}

void *im_LoadInto_(void *im, ImageHeader *header, char *filename)
{
  return do_imLoad_((ImageType) 0, filename, im, header);
}

void *imLoadF(ImageType types, FILE *inFile)
{
  return do_imLoadF_(types, inFile, (void *) NULL, (void *) NULL);
}

void *im_LoadFInto_(void *im, ImageHeader *header, FILE *inFile)
{
  return do_imLoadF_((ImageType) 0, inFile, im, header);
}


static void *do_imLoad_(ImageType types, char *file, void *image, ImageHeader *header)
{
  FILE *inFile;
  void *im;
  boolean newimage;
  

  /* Open the file, imLoadF on it, then close it */

  newimage = (image == (void *) NULL  ||  header == (ImageHeader *) NULL);
  
  inFile = ((file == (char *) NULL) ? stdin : fopen(file, "r"));
  if (inFile == (FILE *)NULL) {
    return ((void *)NULL);
  }

  im = do_imLoadF_(types, inFile, image, header);

  if (file != (char *) NULL  &&  fclose(inFile) == EOF) {
    /*
     * It would be a strange day that fclose on a read-only file
     * gave an error, but...
     */
    if (newimage  &&  im) {
      im_Free_(im, ((AnyImage) im)->header->tag);
    }
    return ((void *) NULL);
  }

  return (im);
}


static ImageType get_image_header(ImageType types, FILE *fp, int *w, int *h, String *doc)
{
  int ichar, headerlen;
  char header[21];
  boolean dummy = FALSE;
  ImageType type = (ImageType) 0;
  int i;
  boolean found;
  

  if (magicStart())
    return (ImageType) 0;

  headerlen = 0;
  while (isspace(((char) ichar = magicGetChar(fp))))
    ;

  header[headerlen++] = (char) ichar;
  while ((ichar = magicGetChar(fp)) != EOF  &&
	 !isspace((char) ichar)  &&
	 headerlen < 20) {
    header[headerlen++] = (char) ichar;
  }
  header[headerlen] = (char) 0;

  if (ichar == EOF) {
    return (ImageType) 0;
  }

  for (i = 0, found = FALSE; !found  &&  i < NIMAGE_DESCS; i++) {
    if (!strcmp(IMAGE_DESC[i].tag, header)) {
      type  = IMAGE_DESC[i].type;
      dummy = IMAGE_DESC[i].dummy;
      found = TRUE;
    }
  }

  if (!found  ||  !(type & types)) {
    return (ImageType) 0;
  }

  if (magicGetInt(fp, w)  ||  magicGetInt(fp, h)  ||  (dummy && magicGetInt(fp, NULL))) {
    return (ImageType) 0;
  }

  if (doc != (String *) NULL) {
    *doc = magicComment();
  }

  return type;
}


static void *do_imLoadF_guts(ImageType types, FILE *inFile, void *image, ImageHeader *header);


static void *
do_imLoadF_(ImageType types, FILE *inFile, void *image, ImageHeader *header)
{
  ImageType trytypes, totype;
  boolean newimage = (image == (void *) NULL  ||  header == (ImageHeader *) NULL);
  AnyImage animg, goodimg;
  
  if (ImageAllowLoadConversion  &&  newimage) {
    trytypes = IMAGE_BINARY | IMAGE_GRAY | IMAGE_SHORT | IMAGE_LONG | IMAGE_FLOAT |
               IMAGE_DOUBLE | IMAGE_RGB | IMAGE_RGBF | IMAGE_HSV;
  }
  else {
    trytypes = types;
  }

  animg = (AnyImage) do_imLoadF_guts(trytypes, inFile, image, header);
  if (animg == NULL) {
    return NULL;
  }
  if (!newimage) {
    return animg;
  }
  else if (imGetType(animg) & types) {
    return animg;
  }
  else {
    totype = imConvGoodConversion(imGetType(animg), types);
#if 0
    goodimg = imConv(animg, totype);
#else
    goodimg = NULL;
#endif

    if (goodimg == NULL) {
      return NULL;
    }

    imDocString(goodimg) = imDocString(animg);
    imDocString(animg) = NULL;
    imFree(animg);
    return goodimg;
  }
}



static void *do_imLoadF_guts(ImageType types, FILE *inFile, void *image, ImageHeader *header)
{
  int width;
  int height;
  unsigned el_size=0;
  void *im;
  void *rect = (void *)NULL;
  ImageType truetype;
  String docstring = NULLSTRING;
  boolean newimage;
  int c1, c2;
    
  assert(inFile != (FILE *)NULL);

  newimage = (image == (void *) NULL  ||  header == (ImageHeader *) NULL);

  if (!newimage) {
    types = header->tag;
  }

  
  if ((c1 = getc(inFile)) == EOF) {
    return NULL;
  }
  
  if ((c2 = getc(inFile)) == EOF) {
    return NULL;
  }

  if (c1 == 0037  &&  (c2 == 0213 || c2 == 0235)) { /* compressed -- note OCTAL CONSTANTS! */
    int chrs[2] = { c1, c2 };                /* 0213=>gzip; 0235=>compress */
    char *args[] = { "gunzip", "-", NULL };

    inFile = filtered_fp_putchars(inFile, "/usr/local/bin/gunzip", args, chrs, 2);
  }
  else {
    ungetc(c2, inFile);
    ungetc(c1, inFile);
  }

  truetype = get_image_header(types, inFile, &width, &height, &docstring);

  if (truetype == (ImageType) 0) {
    return (void *) NULL;
  }

  /* Now know the width and height and type of the image as stored. */

  if (newimage) {
    im = imNew(truetype, (int) width, (int) height);

    if (im == (void *)NULL) {
      return((void *)NULL);
    }
  }
  else {
    if (header->width != width  ||  header->height != height) {
      return ((void *) NULL);
    }

    im = image;
  }
  
  /*
   * Figure out how large each element is in the file, and also
   * pull out a pointer to the data area... need to
   * get a type for image to do this. Bah. This code is horrendous.
   */
 
#define LOAD_IMG(imtype, elemtype)        \
  { imtype img = (imtype) im;             \
    el_size = sizeof(elemtype);           \
    rect    = (void *)(img->data[img->header->ybase] + img->header->xbase); \
    img->header->docstring = docstring;   \
    break;                                \
  }
  
  switch (truetype) {
  case IMAGE_GRAY:   LOAD_IMG(GrayImage, uchar);
  case IMAGE_FLOAT:  LOAD_IMG(FloatImage, float);
  case IMAGE_RGB:    LOAD_IMG(RGBImage, RGB);
  case IMAGE_DOUBLE: LOAD_IMG(DoubleImage, double);
  case IMAGE_BINARY: LOAD_IMG(BinaryImage, char);
  case IMAGE_LONG:   LOAD_IMG(LongImage, long);
  case IMAGE_SHORT:  LOAD_IMG(ShortImage, short);
  case IMAGE_HSV:    LOAD_IMG(HSVImage, HSV);
  case IMAGE_RGBF:    LOAD_IMG(RGBFloatImage, RGBFloat);

  case IMAGE_STRUCT: {
    panic("loading structure images not yet supported");
    break;
  }
    
  case IMAGE_PTR: {
    panic("attempt to load pointer image");
    break;
  }

  default: {
    panic("bad image tag");
  }
  }
#undef LOAD_IMG

  /* Read in the data block */
  if (truetype != IMAGE_BINARY) {
    if (fread((char *)rect, (int)el_size, (int)(width * height), inFile) !=
	width * height) {
      if (newimage)  im_Free_(im, truetype);
      return((void *)NULL);
    }
  }
  else {
    /* For binary images, have to read differently */
    register int i, j, c, bitsleft;

    c = EOF;
    for (i = 0; i < height; i++) {
      bitsleft = 0;
      for (j = 0; j < width; j++) {
	if (bitsleft == 0) {
	  c = getc(inFile);
	  if (c == EOF) {
	    if (newimage)  im_Free_(im, truetype);
	    return((void *)NULL);
	  }
	  bitsleft = 8;
	}
	/* High bit first */
	if (c & (1 << (--bitsleft))) {
	  imRef((BinaryImage)im, j, i) = 1;
	}
	/* It's already zero by default */
      }
    }
  }

  return(im);
}


int im_Save_(void *im, ImageHeader *header, char *filename)
{
  FILE *outFile;
    
  assert(im != (void *)NULL);
  assert(header != (ImageHeader *)NULL);

  /* Open the file, imSaveF on it, then close it */
  outFile = ((filename == (char *) NULL) ? stdout : fopen(filename, "w"));
  if (outFile == (FILE *)NULL) {
    return(-1);
  }

  /* Write the image */
  if (im_SaveF_(im, header, outFile) == -1  &&  filename != (char *) NULL) {
    (void)fclose(outFile);
    return(-1);
  }

  /* and close the file */
  if (filename != (char *) NULL  &&  fclose(outFile) == EOF) {
    /* Bah. */
    return(-1);
  }

  return(0);
}

int im_SaveF_(void *im, ImageHeader *header, FILE *outFile)
{
  void *rect=0;
  unsigned el_size=0;
  char *startString=0;
  boolean dummypresent=0;
  char *docstring;
  

  assert(outFile != (FILE *)NULL);
  assert(im != (void *)NULL);
  assert(header != (ImageHeader *)NULL);

  /*
   * Get a pointer to the data area, the size of each element, and
   * the format of the header. The header may include a dummy entry
   * (some of the p*m formats have a maxval entry in the header - we
   * just use the maximum value we can represent).
   */

#define SAVE_IMG(imtype, elemtype, tag, dummy)      \
  {                                                 \
    rect = (void *) &(((((imtype)im)->data)[header->ybase])[header->xbase]);   \
    el_size = sizeof(elemtype);                     \
    startString = tag;                              \
    dummypresent = dummy;                           \
    break;                                          \
  }
  
  switch (header->tag) {
  case IMAGE_GRAY:   SAVE_IMG(GrayImage, uchar, TAG_GRAY, TRUE);	    
  case IMAGE_FLOAT:  SAVE_IMG(FloatImage, float, TAG_FLOAT, FALSE);	    
  case IMAGE_RGB:    SAVE_IMG(RGBImage, RGB, TAG_RGB, TRUE);
  case IMAGE_DOUBLE: SAVE_IMG(DoubleImage, double, TAG_DOUBLE, FALSE);
  case IMAGE_BINARY: SAVE_IMG(BinaryImage, char, TAG_BINARY, FALSE);
  case IMAGE_LONG:   SAVE_IMG(LongImage, long, TAG_LONG, FALSE);
  case IMAGE_SHORT:  SAVE_IMG(ShortImage, short, TAG_SHORT, FALSE);
  case IMAGE_HSV:    SAVE_IMG(HSVImage, HSV, TAG_HSV, FALSE);
  case IMAGE_RGBF:    SAVE_IMG(RGBFloatImage, RGBFloat, TAG_RGBF, FALSE);

  case IMAGE_STRUCT: {
    panic("saving structure images not yet supported");
    break;
  }
    
  case IMAGE_PTR: {
    panic("attempt to save pointer image");
    break;
  }

  default: {
    panic("bad image tag");
  }
  }
#undef SAVE_IMG

  /* Write out the header */
  if (fprintf(outFile, "%s\n\n", startString) == EOF) {
    return (-1);
  }

  docstring = strGet(header->docstring);

  if (docstring != (char *) NULL) {	     /* carefully write out docstring */
    if (putc('#', outFile) == EOF)
      return (-1);
    
    for ( ; *docstring != (char) 0; docstring++) {
      if (putc(*docstring, outFile) == EOF)
	return (-1);

      if ((*docstring == '\n'  ||  *docstring == '\r') && docstring[1] != '#') {
	if (putc('#', outFile) == EOF)
	  return (-1);
	if (docstring[1] != ' ')
	  if (putc(' ', outFile) == EOF)
	    return (-1);
      }
    }

    if (putc('\n', outFile) == EOF  ||  putc('\n', outFile) == EOF)
      return (-1);
  }  
  
  if (dummypresent) {
    if (fprintf(outFile, "%d %d %d\n", header->width, header->height,
		COLRNG - 1) == EOF) {
      return(-1);
    }
  }
  else {
    if (fprintf(outFile, "%d %d\n", header->width, header->height) == EOF) {
      return(-1);
    }
  }
	

  /* Write out the data block */
  if (header->tag != IMAGE_BINARY) {
    if (fwrite((char *)rect, (int)el_size,
	       (int)(header->width * header->height), outFile) !=
	header->width * header->height) {
      return(-1);
    }
  }
  else {
    /* For binary images, have to write differently */
    register int i, j, c, bitsleft, height, width;
	
    height = (int)header->height + header->ybase;
    width = (int)header->width + header->xbase;

    for (i = header->ybase; i < height; i++) {
      bitsleft = 8;
      c = 0;
      for (j = header->xbase; j < width; j++) {
	--bitsleft;
	if (imRef((BinaryImage)im, j, i)) {
	  c |= 1 << bitsleft;
	}
	if ((bitsleft == 0) || (j == width - 1)) {
	  if (putc(c, outFile) == EOF) {
	    return(-1);
	  }
	  bitsleft = 8;
	  c = 0;
	}
      }
    }
  }

  /* and successfully return */
  return(0);
}

void *im_Dup_(void *im, ImageHeader *header)
{
  void *dup;
  unsigned el_size=0;
  char *oldbase=0;
  char *newbase=0;

  assert(im != (void *)NULL);
  assert(header != (ImageHeader *)NULL);

  /* Make a copy. */
  
  dup = im_NewOrWrap_(header->tag, (int) header->width, (int) header->height,
		      header->xbase, header->ybase, NULL,
		      header->elemsize, header->headsize, im);
  if (dup == (void *)NULL) {
    return((void *)NULL);
  }

#define DUP_IMG(imtype, elemtype)      \
  {                                    \
    el_size = sizeof(elemtype);        \
    oldbase = (void *) &(((imtype)im)->data[header->ybase][header->xbase]);   \
    newbase = (void *) &(((imtype)dup)->data[header->ybase][header->xbase]);  \
    break;                             \
  }

  switch(header->tag) {
  case IMAGE_GRAY:   DUP_IMG(GrayImage, uchar);
  case IMAGE_FLOAT:  DUP_IMG(FloatImage, float);
  case IMAGE_RGB:    DUP_IMG(RGBImage, RGB);
  case IMAGE_DOUBLE: DUP_IMG(DoubleImage, double);
  case IMAGE_BINARY: DUP_IMG(BinaryImage, char);
  case IMAGE_LONG:   DUP_IMG(LongImage, long);
  case IMAGE_PTR:    DUP_IMG(PtrImage, void *);
  case IMAGE_SHORT:  DUP_IMG(ShortImage, short);
  case IMAGE_HSV:    DUP_IMG(HSVImage, HSV);
  case IMAGE_RGBF:    DUP_IMG(RGBFloatImage, RGBFloat);

  case IMAGE_STRUCT: {
    el_size = header->elemsize;
    oldbase = (((char *) (((GenericStructImage) im)->data[header->ybase])) +
	       el_size * header->xbase);
    newbase = (((char *) (((GenericStructImage) dup)->data[header->ybase])) +
	       el_size * header->xbase);
    break;
  }

  default: {
    panic("bad image tag");
  }
  }
#undef DUP_IMG
  
  /* Do the copy */
  (void)memcpy(newbase, oldbase, (int)(el_size * header->width * header->height));

  return(dup);
}

void *im_Copy_(void *im, ImageHeader *ih, void *dst, ImageHeader *dh)
{
  unsigned el_size=0;
  char *oldbase=0;
  char *newbase=0;

  assert(im != (void *)NULL);
  assert(ih != (ImageHeader *)NULL);
  assert(dst != (void *)NULL);
  assert(dh != (ImageHeader *)NULL);
  assert(ih->width == dh->width);
  assert(ih->height == dh->height);
  assert(ih->tag == dh->tag);
  

#define COPY_IMG(imtype, elemtype)      \
  {                                    \
    el_size = sizeof(elemtype);        \
    oldbase = (void *) &(((imtype)im)->data[ih->ybase][ih->xbase]);   \
    newbase = (void *) &(((imtype)dst)->data[dh->ybase][dh->xbase]);  \
    break;                             \
  }

  switch(ih->tag) {
  case IMAGE_GRAY:   COPY_IMG(GrayImage, uchar);
  case IMAGE_FLOAT:  COPY_IMG(FloatImage, float);
  case IMAGE_RGB:    COPY_IMG(RGBImage, RGB);
  case IMAGE_DOUBLE: COPY_IMG(DoubleImage, double);
  case IMAGE_BINARY: COPY_IMG(BinaryImage, char);
  case IMAGE_LONG:   COPY_IMG(LongImage, long);
  case IMAGE_PTR:    COPY_IMG(PtrImage, void *);
  case IMAGE_SHORT:  COPY_IMG(ShortImage, short);
  case IMAGE_HSV:    COPY_IMG(HSVImage, HSV);
  case IMAGE_RGBF:   COPY_IMG(RGBFloatImage, RGBFloat);

  case IMAGE_STRUCT: {
    int i;	
    assert(ih->headsize == dh->headsize);

    /* copy the userdata field */
    for (i = 0; i < ih->headsize; i++) {
      ((char *) dst)[i] = ((char *) im)[i];
    }

    ((GenericStructImage) dst)->data = (GenStruct **) dh->anyim;
    ((GenericStructImage) dst)->header = dh;
    
    el_size = ih->elemsize;
    oldbase = (((char *) (((GenericStructImage) im)->data[ih->ybase])) +
	       el_size * ih->xbase);
    newbase = (((char *) (((GenericStructImage) dst)->data[dh->ybase])) +
	       el_size * dh->xbase);
    break;
  }

  default: {
    panic("bad image tag");
  }
  }
#undef COPY_IMG
  
  /* Do the copy */
  (void)memcpy(newbase, oldbase, (int)(el_size * ih->width * ih->height));

  return(dst);
}

void *im_Down_(void *im, ImageHeader *header, int dx, int dy)
{
  void *dup;
  int i, j, w, h;

  assert(im != (void *)NULL);
  assert(header != (ImageHeader *)NULL);

  /* Make a copy. */

  w = (int) header->width  / dx;
  h = (int) header->height / dy;
  dup = im_NewOrWrap_(header->tag, w, h,
		      header->xbase, header->ybase, NULL,
		      header->elemsize, header->headsize, im);
  if (dup == (void *)NULL) {
    return((void *)NULL);
  }

#define DOWN_IMG(imtype, elemtype)                                                 \
  {                                                                                \
    imtype dimg = (imtype) dup, img = (imtype) im;                                 \
    for (j = 0; j < h; j++)                                                        \
      for (i = 0; i < w; i++)                                                      \
        imRef(dimg, header->xbase + i, header->ybase + j) =                        \
          imRef(img, header->xbase + dx*i, header->ybase + dy*j);                  \
    break;                                                                         \
  }                                                                                \

  switch(header->tag) {
  case IMAGE_GRAY:   DOWN_IMG(GrayImage, uchar);
  case IMAGE_FLOAT:  DOWN_IMG(FloatImage, float);
  case IMAGE_RGB:    DOWN_IMG(RGBImage, RGB);
  case IMAGE_DOUBLE: DOWN_IMG(DoubleImage, double);
  case IMAGE_BINARY: DOWN_IMG(BinaryImage, char);
  case IMAGE_LONG:   DOWN_IMG(LongImage, long);
  case IMAGE_PTR:    DOWN_IMG(PtrImage, void *);
  case IMAGE_SHORT:  DOWN_IMG(ShortImage, short);
  case IMAGE_HSV:    DOWN_IMG(HSVImage, HSV);
  case IMAGE_RGBF:   DOWN_IMG(RGBFloatImage, RGBFloat);

  case IMAGE_STRUCT: {
    panic("imDown not defined for struct images");
    break;
  }

  default: {
    panic("bad image tag");
  }
  }
#undef DOWN_IMG

  return(dup);
}


void im_Init_(void *im, ImageHeader *header, void *val)
{
  assert(im != (void *)NULL);
  assert(header != (ImageHeader *)NULL);

#define INIT_IMG(arraytype, elemtype)               \
  {                                                 \
    elemtype *begin;                                \
    elemtype *end;                                  \
    elemtype  value;                                \
                                                    \
    begin = imGetStore((arraytype) im);             \
    end   = begin + imGetStoreLen((arraytype) im);  \
    value = *((elemtype *) val);                    \
                                                    \
    while (begin < end)                             \
      *(begin++) = value;                           \
    break;                                          \
  }

  switch(header->tag) {
  case IMAGE_GRAY:   INIT_IMG(GrayImage, uchar);
  case IMAGE_FLOAT:  INIT_IMG(FloatImage, float);
  case IMAGE_RGB:    INIT_IMG(RGBImage, RGB);
  case IMAGE_DOUBLE: INIT_IMG(DoubleImage, double);
  case IMAGE_BINARY: INIT_IMG(BinaryImage, char);
  case IMAGE_LONG:   INIT_IMG(LongImage, long);
  case IMAGE_PTR:    INIT_IMG(PtrImage, void *);
  case IMAGE_SHORT:  INIT_IMG(ShortImage, short);
  case IMAGE_HSV:    INIT_IMG(HSVImage, HSV);
  case IMAGE_RGBF:    INIT_IMG(RGBFloatImage, RGBFloat);

  case IMAGE_STRUCT: {
    char *begin, *end, *value;
    int elsize, i;

    elsize = header->elemsize;
    begin = (((char *) (((GenericStructImage) im)->data[header->ybase])) +
	     elsize * header->xbase);
    end   = begin + elsize * imGetStoreLen((GenericStructImage) im);
    value = (char *) val;

    while (begin < end)
      for (i = 0; i < elsize; i++)
	*(begin++) = value[i];
    break;
  }

  default: {
    panic("bad image tag");
  }
  }
#undef INIT_IMG
  
  return;
}


void im_SetOffset_(void *im, ImageHeader *header, int xbase, int ybase)
{
  int oldxbase, oldybase, maxy, y;

  
  assert(im != (void *)NULL);
  assert(header != (ImageHeader *)NULL);

  oldxbase = header->xbase;
  oldybase = header->ybase;
  maxy     = ybase + header->height - 1;

  header->xbase = xbase;
  header->ybase = ybase;
  
#define REBASE_IMG(imtype)                    \
  { imtype image = (imtype) im;               \
    image->data += (oldybase - ybase);        \
    for (y = ybase; y <= maxy; y++)           \
      image->data[y] += (oldxbase - xbase);   \
    break;                                    \
  }
  
  switch(header->tag) {
  case IMAGE_GRAY:   REBASE_IMG(GrayImage);
  case IMAGE_FLOAT:  REBASE_IMG(FloatImage);
  case IMAGE_RGB:    REBASE_IMG(RGBImage);
  case IMAGE_DOUBLE: REBASE_IMG(DoubleImage);
  case IMAGE_BINARY: REBASE_IMG(BinaryImage);
  case IMAGE_LONG:   REBASE_IMG(LongImage);
  case IMAGE_PTR:    REBASE_IMG(PtrImage);
  case IMAGE_SHORT:  REBASE_IMG(ShortImage);
  case IMAGE_HSV:    REBASE_IMG(HSVImage);
  case IMAGE_RGBF:   REBASE_IMG(RGBFloatImage);

  case IMAGE_STRUCT: {
    GenericStructImage image = (GenericStructImage) im;
    int elsize = header->elemsize;
    
    image->data += (oldybase - ybase);
    for (y = ybase; y <= maxy; y++)
      ((char *) (image->data[y])) += elsize*(oldxbase - xbase);
    break;
  }

  default: {
    panic("bad image tag");
  }
  }
#undef REBASE_IMG

  return;
}



void im_ApplyV_(void *f, void *im, ImageHeader *header)
{
  assert(im != (void *)NULL);
  assert(header != (ImageHeader *)NULL);

#define APPLY_IMG(arraytype, elemtype)               \
  {                                                  \
    elemtype (*func)(elemtype valp);                 \
    elemtype *begin;                                \
    elemtype *end;                                  \
                                                    \
    func = (typeof (func)) f;                        \
    begin = imGetStore((arraytype) im);             \
    end   = begin + imGetStoreLen((arraytype) im);  \
                                                    \
    while (begin < end) {                           \
      *begin = func(*begin);                        \
      begin++;                                      \
    }                                               \
    break;                                          \
  }

  switch(header->tag) {
  case IMAGE_GRAY:   APPLY_IMG(GrayImage, uchar);
  case IMAGE_FLOAT:  APPLY_IMG(FloatImage, float);
  case IMAGE_RGB:    APPLY_IMG(RGBImage, RGB);
  case IMAGE_DOUBLE: APPLY_IMG(DoubleImage, double);
  case IMAGE_BINARY: APPLY_IMG(BinaryImage, char);
  case IMAGE_LONG:   APPLY_IMG(LongImage, long);
  case IMAGE_PTR:    APPLY_IMG(PtrImage, void *);
  case IMAGE_SHORT:  APPLY_IMG(ShortImage, short);
  case IMAGE_HSV:    APPLY_IMG(HSVImage, HSV);
  case IMAGE_RGBF:   APPLY_IMG(RGBFloatImage, RGBFloat);

  case IMAGE_STRUCT: {
    panic("apply for structure images not yet supported");
    break;
  }
    
  default: {
    panic("bad image tag");
  }
  }
#undef APPLY_IMG
  
  return;
}

void im_Apply_(void *f, void *im, ImageHeader *header)
{
  int x, y, x0, y0, xm, ym;

  
  assert(im != (void *)NULL);
  assert(header != (ImageHeader *)NULL);

#define APPLY_IMG(arraytype, elemtype)               \
  {                                                  \
    arraytype img;                                   \
    void (*func)(elemtype *valp, int x, int y);      \
    elemtype *valp;                                  \
                                                     \
    img  = (arraytype) im;                           \
    func = (typeof (func)) f;                        \
                                                     \
    x0 = img->header->xbase;                         \
    y0 = img->header->ybase;                         \
    xm = x0 + img->header->width;                    \
    ym = y0 + img->header->height;                   \
                                                     \
    valp = img->data[y0] + x0;                       \
    for (y = y0; y < ym; y++)                        \
      for (x = x0; x < xm; x++, valp++)              \
	(*func)(valp, x, y);                         \
    break;                                           \
  }

  switch(header->tag) {
  case IMAGE_GRAY:   APPLY_IMG(GrayImage, uchar);
  case IMAGE_FLOAT:  APPLY_IMG(FloatImage, float);
  case IMAGE_RGB:    APPLY_IMG(RGBImage, RGB);
  case IMAGE_DOUBLE: APPLY_IMG(DoubleImage, double);
  case IMAGE_BINARY: APPLY_IMG(BinaryImage, char);
  case IMAGE_LONG:   APPLY_IMG(LongImage, long);
  case IMAGE_PTR:    APPLY_IMG(PtrImage, void *);
  case IMAGE_SHORT:  APPLY_IMG(ShortImage, short);
  case IMAGE_HSV:    APPLY_IMG(HSVImage, HSV);
  case IMAGE_RGBF:   APPLY_IMG(RGBFloatImage, RGBFloat);

  case IMAGE_STRUCT: {
    GenericStructImage img = (GenericStructImage) im;
    void (*func)(char *valp, int x, int y) = (typeof (func)) f;
    char *valp;
    int elemsize = header->elemsize;

    x0 = img->header->xbase;
    y0 = img->header->ybase;
    xm = x0 + img->header->width;
    ym = y0 + img->header->height;

    valp = ((char *) (img->data[y0]))  +  elemsize * x0;
    for (y = y0; y < ym; y++)
      for (x = x0; x < xm; x++, valp += elemsize)
	(*func)(valp, x, y);
    break;
  }
    
  default: {
    panic("bad image tag");
  }
  }
#undef APPLY_IMG
  
  return;
}


void im_MaxAndMin_(void *im, ImageHeader *header, void *maxp, void *minp)
{
  assert(im != (void *)NULL);
  assert(header != (ImageHeader *)NULL);

#define MM_IMG(arraytype, elemtype)                                                    \
  {                                                                                    \
    arraytype img  = (arraytype) im;                                                   \
    elemtype *scan = imGetStore(img);                                                  \
    elemtype *end  = scan + imGetStoreLen(img);                                        \
    elemtype max   = *scan;                                                            \
    elemtype min   = *scan;                                                            \
                                                                                       \
    for (scan++; scan < end; scan++) {                                                 \
      register elemtype v = *scan;                                                     \
      if (v > max)                                                                     \
	max = v;                                                                       \
      else if (v < min)                                                                \
	min = v;                                                                       \
    }                                                                                  \
                                                                                       \
    *((elemtype *) maxp) = max;                                                        \
    *((elemtype *) minp) = min;                                                        \
                                                                                       \
    break;                                                                             \
  }

#define MM_IMG3(IMTYPE, ELTYPE, RR, GG, BB)                                            \
  {                                                                                    \
    IMTYPE img = (IMTYPE) im;                                                          \
    ELTYPE *scan = imGetStore(img);                                                    \
    ELTYPE *end  = scan + imGetStoreLen(img);                                          \
    ELTYPE max   = *scan;                                                              \
    ELTYPE min   = *scan;                                                              \
    ELTYPE v;                                                                          \
                                                                                       \
    for (scan++; scan < end; scan++) {                                                 \
      v = *scan;                                                                       \
                                                                                       \
      if (v.RR > max.RR)	max.RR = v.RR;                                         \
      else if (v.RR < min.RR)   min.RR = v.RR;                                         \
      if (v.GG > max.GG)	max.GG = v.GG;                                         \
      else if (v.GG < min.GG)   min.GG = v.GG;                                         \
      if (v.BB > max.BB)	max.BB = v.BB;                                         \
      else if (v.BB < min.BB)   min.BB = v.BB;                                         \
    }                                                                                  \
                                                                                       \
    *((ELTYPE *) maxp) = max;                                                          \
    *((ELTYPE *) minp) = min;                                                          \
                                                                                       \
    break;                                                                             \
  }

  switch(header->tag) {
  case IMAGE_GRAY:   MM_IMG(GrayImage, uchar);
  case IMAGE_FLOAT:  MM_IMG(FloatImage, float);
  case IMAGE_DOUBLE: MM_IMG(DoubleImage, double);
  case IMAGE_BINARY: MM_IMG(BinaryImage, char);
  case IMAGE_LONG:   MM_IMG(LongImage, long);
  case IMAGE_SHORT:  MM_IMG(ShortImage, short);
  case IMAGE_RGB:    MM_IMG3(RGBImage, RGB, r, g, b);
  case IMAGE_HSV:    MM_IMG3(HSVImage, HSV, h, s, v);
  case IMAGE_RGBF:   MM_IMG3(RGBFloatImage, RGBFloat, r, g, b);
    
  case IMAGE_PTR:
    panic("imMaxAndMin() not defined for pointer images");
    break;
    
  case IMAGE_STRUCT:
    panic("imMaxAndMin() not defined for struct images");
    break;
    
  default: {
    panic("bad image tag");
  }
  }
#undef MM_IMG
#undef MM_IMG3
  
  return;
}


/* On SPARCstations, for  */
/*      float: (*((long *) &x) & 0x7F800000) == 0x7F800000 => Inf or NaN */
/*     double: (*((long *) &x) & 0x7FD00000) == 0x7FD00000 => Inf or NaN */

void im_MaxAndMinNoInf_(void *im, ImageHeader *header, void *maxp, void *minp)
{
  assert(im != (void *)NULL);
  assert(header != (ImageHeader *)NULL);

#define MM_IMG(ARRAYTYPE, ELEMTYPE, BITS, INITIAL)                                     \
  {                                                                                    \
    ARRAYTYPE img  = (ARRAYTYPE) im;                                                   \
    ELEMTYPE *scan = imGetStore(img);                                                  \
    ELEMTYPE *end  = scan + imGetStoreLen(img);                                        \
    ELEMTYPE max   = INITIAL;                                                          \
    ELEMTYPE min   = INITIAL;                                                          \
                                                                                       \
    for ( ; scan < end; scan++) {                                                      \
      if ((*((long *) scan) & BITS) != BITS) {                                         \
	max = min = *scan;                                                             \
	break;                                                                         \
      }                                                                                \
    }                                                                                  \
    for (scan++; scan < end; scan++) {                                                 \
      if ((*((long *) scan) & BITS) != BITS) {                                         \
        register ELEMTYPE v = *scan;                                                   \
        if (v > max)                                                                   \
	  max = v;                                                                     \
        else if (v < min)                                                              \
	  min = v;                                                                     \
      }                                                                                \
    }                                                                                  \
                                                                                       \
    *((ELEMTYPE *) maxp) = max;                                                        \
    *((ELEMTYPE *) minp) = min;                                                        \
                                                                                       \
    break;                                                                             \
  }
  
#define MM_IMG3F(IMTYPE, ELTYPE, RR, GG, BB)                                           \
  {                                                                                    \
    IMTYPE img  = (IMTYPE) im;                                                         \
    ELTYPE *scan = imGetStore(img);                                                    \
    ELTYPE *end  = scan + imGetStoreLen(img);                                          \
    ELTYPE max   = ((ELTYPE) {FLOAT_NAN, FLOAT_NAN, FLOAT_NAN});                       \
    ELTYPE min   = ((ELTYPE) {FLOAT_NAN, FLOAT_NAN, FLOAT_NAN});                       \
    int found = 0;                                                                     \
                                                                                       \
    for ( ; scan < end  &&  found != 7; scan++) {                                      \
      if (found & 1) {                                                                 \
        if ((*((long *) &(scan->RR)) & 0x7f800000) != 0x7f800000) {                    \
          if (scan->RR > max.RR)        max.RR = scan->RR;                             \
          else if (scan->RR < min.RR)   min.RR = scan->RR;                             \
        }                                                                              \
      }                                                                                \
      else {                                                                           \
        if ((*((long *) &(scan->RR)) & 0x7f800000) != 0x7f800000) {                    \
          max.RR = min.RR = scan->RR;                                                  \
          found |= 1;                                                                  \
        }                                                                              \
      }                                                                                \
                                                                                       \
      if (found & 2) {                                                                 \
        if ((*((long *) &(scan->GG)) & 0x7f800000) != 0x7f800000) {                    \
          if (scan->GG > max.GG)        max.GG = scan->GG;                             \
          else if (scan->GG < min.GG)   min.GG = scan->GG;                             \
        }                                                                              \
      }                                                                                \
      else {                                                                           \
        if ((*((long *) &(scan->GG)) & 0x7f800000) != 0x7f800000) {                    \
          max.GG = min.GG = scan->GG;                                                  \
          found |= 2;                                                                  \
        }                                                                              \
      }                                                                                \
                                                                                       \
      if (found & 4) {                                                                 \
        if ((*((long *) &(scan->BB)) & 0x7f800000) != 0x7f800000) {                    \
          if (scan->BB > max.BB)        max.BB = scan->BB;                             \
          else if (scan->BB < min.BB)   min.BB = scan->BB;                             \
        }                                                                              \
      }                                                                                \
      else {                                                                           \
        if ((*((long *) &(scan->BB)) & 0x7f800000) != 0x7f800000) {                    \
          max.BB = min.BB = scan->BB;                                                  \
          found |= 4;                                                                  \
        }                                                                              \
      }                                                                                \
    }                                                                                  \
                                                                                       \
    for ( ; scan < end; scan++) {                                                      \
      if ((*((long *) &(scan->RR)) & 0x7f800000) != 0x7f800000) {                      \
        if (scan->RR > max.RR)        max.RR = scan->RR;                               \
        else if (scan->RR < min.RR)   min.RR = scan->RR;                               \
      }                                                                                \
      if ((*((long *) &(scan->GG)) & 0x7f800000) != 0x7f800000) {                      \
        if (scan->GG > max.GG)        max.GG = scan->GG;                               \
        else if (scan->GG < min.GG)   min.GG = scan->GG;                               \
      }                                                                                \
      if ((*((long *) &(scan->BB)) & 0x7f800000) != 0x7f800000) {                      \
        if (scan->BB > max.BB)        max.BB = scan->BB;                               \
        else if (scan->BB < min.BB)   min.BB = scan->BB;                               \
      }                                                                                \
    }                                                                                  \
                                                                                       \
    *((ELTYPE *) maxp) = max;                                                          \
    *((ELTYPE *) minp) = min;                                                          \
                                                                                       \
    break;                                                                             \
  }
  
  switch(header->tag) {
  case IMAGE_GRAY:   
  case IMAGE_BINARY:
  case IMAGE_LONG:
  case IMAGE_SHORT:
  case IMAGE_RGB:
  case IMAGE_PTR:
  case IMAGE_STRUCT:
    im_MaxAndMin_(im, header, maxp, minp);
    break;
    
  case IMAGE_FLOAT:  MM_IMG(FloatImage,  float,  0x7F800000, FLOAT_NAN);
  case IMAGE_DOUBLE: MM_IMG(DoubleImage, double, 0x7FD00000, DOUBLE_NAN);
  case IMAGE_RGBF:   MM_IMG3F(RGBFloatImage, RGBFloat, r, g, b);
  case IMAGE_HSV:    MM_IMG3F(HSVImage, HSV, h, s, v);
    
  default: {
    panic("bad image tag");
  }
  }
#undef MM_IMG
#undef MM_IMG3F
  
  return;
}



void *im_Crop_(void *im, ImageHeader *header, int xbase, int ybase, int w, int h)
{
  int oxb = header->xbase;
  int oyb = header->ybase;
  int ow  = header->width;
  int oh  = header->height;
  int ixb = MAX(oxb, xbase);
  int iyb = MAX(oyb, ybase);
  int ixm = MIN(oxb + ow - 1, xbase + w - 1);
  int iym = MIN(oyb + oh - 1, ybase + h - 1);
  int y;

  
  if (w <= 0  ||  h <= 0  ||  ixm <= ixb  ||  iym <= iyb) {
    return NULL;
  }
  
#define CROP_IMG(imtype, elemtype)                                                     \
  { imtype orig = (imtype) im;                                                         \
    imtype crop;                                                                       \
                                                                                       \
    if ((crop = (imtype) imNewOffset(header->tag, w, h, xbase, ybase)) == NULL) {      \
      return NULL;                                                                     \
    }                                                                                  \
                                                                                       \
    for (y = iyb; y <= iym; y++) {                                                     \
      elemtype *cropscan = &imRef(crop, ixb, y);                                       \
      elemtype *cropend  = &imRef(crop, ixm + 1, y);                                   \
      elemtype *origscan = &imRef(orig, ixb, y);                                       \
                                                                                       \
      for ( ; cropscan < cropend; ) {                                                  \
	*(cropscan++) = *(origscan++);                                                 \
                                                                                       \
      }                                                                                \
    }                                                                                  \
                                                                                       \
    return (void *) crop;                                                              \
  }

  switch(header->tag) {
  case IMAGE_GRAY:   CROP_IMG(GrayImage, uchar);
  case IMAGE_FLOAT:  CROP_IMG(FloatImage, float);
  case IMAGE_RGB:    CROP_IMG(RGBImage, RGB);
  case IMAGE_DOUBLE: CROP_IMG(DoubleImage, double);
  case IMAGE_BINARY: CROP_IMG(BinaryImage, char);
  case IMAGE_LONG:   CROP_IMG(LongImage, long);
  case IMAGE_PTR:    CROP_IMG(PtrImage, void *);
  case IMAGE_SHORT:  CROP_IMG(ShortImage, short);
  case IMAGE_HSV:    CROP_IMG(HSVImage, HSV);
  case IMAGE_RGBF:   CROP_IMG(RGBFloatImage, RGBFloat);

  case IMAGE_STRUCT: {
    GenericStructImage orig = (GenericStructImage) im;
    GenericStructImage crop;
    int elemsize = header->elemsize;

    if ((crop = (GenericStructImage) im_NewOrWrap_(header->tag, w, h, xbase, ybase,
						   NULL, header->elemsize,
						   header->headsize, NULL)) == NULL) {
      return NULL;
    }

    for (y = iyb; y <= iym; y++) {
      char *cropscan = ((char *) (crop->data[y])) + elemsize * ixb;
      char *cropend  = ((char *) (crop->data[y])) + elemsize * (ixm + 1);
      char *origscan = ((char *) (orig->data[y])) + elemsize * ixb;
      
      for ( ; cropscan < cropend; ) {
	*(cropscan++) = *(origscan++);
      }
    }

    return (void *) crop;
  }

  default: {
    panic("bad image tag");
  }
  }
#undef CROP_IMG

  return NULL;
}

#include <limits.h>

void *im_Scale_(void *im, ImageHeader *header, double scale)
{
  uchar v = 10, *vp = &v;
 
#define  INT_SCALE(vp, sc, lb, ub)    *vp = ROUND(BOUND(*vp * sc, lb, ub))
#define  FLOAT_SCALE(vp, sc, lb, ub)  *vp *= sc
#define  RGBFSCALE(vp, sc, lb, ub)    vp->r *= sc; vp->g *= sc; vp->b *= sc
#define  RGBSCALE(vp, sc, lb, ub)     vp->r = (uchar) ROUND(BOUND(vp->r * sc, lb, ub));\
                                      vp->g = (uchar) ROUND(BOUND(vp->g * sc, lb, ub));\
                                      vp->b = (uchar) ROUND(BOUND(vp->b * sc, lb, ub))
    
#define SCALE_IMG(imtype, elemtype, scaleit, lb, ub)                                      \
  { imtype img = (imtype) im;                                                             \
    elemtype *scan, *end;                                                                 \
                                                                                          \
    scan = imGetStore(img);                                                               \
    end  = scan + imGetStoreLen(img);                                                     \
    for ( ; scan < end; scan++) {                                                         \
      scaleit(scan, scale, lb, ub);                                                       \
    }                                                                                     \
                                                                                          \
    return im;                                                                            \
  }

  switch(header->tag) {
  case IMAGE_GRAY:   SCALE_IMG(GrayImage, uchar, INT_SCALE, 0, UCHAR_MAX);
  case IMAGE_SHORT:  SCALE_IMG(ShortImage, short, INT_SCALE, SHRT_MIN, SHRT_MAX);
  case IMAGE_LONG:   SCALE_IMG(LongImage, long, INT_SCALE, LONG_MIN, LONG_MAX);
  case IMAGE_FLOAT:  SCALE_IMG(FloatImage, float, FLOAT_SCALE, -, -);
  case IMAGE_DOUBLE: SCALE_IMG(DoubleImage, double, FLOAT_SCALE, -, -);
  case IMAGE_RGB:    SCALE_IMG(RGBImage, RGB, RGBSCALE, 0, UCHAR_MAX);
  case IMAGE_RGBF:   SCALE_IMG(RGBFloatImage, RGBFloat, RGBFSCALE, -, -);

  case IMAGE_BINARY: 
  case IMAGE_HSV:
  case IMAGE_PTR: 
  case IMAGE_STRUCT:
    panic("can't scale this image type");
    return NULL;
    break;

  default: {
    panic("bad image tag");
  }
  }
#undef SCALE_IMG

  return NULL;
}
