/*
 *
 * $Header: /usr/u/wjr/src/ADT/RCS/image.c,v 2.5 1994/07/24 23:32:57 wjr Exp $
 *
 * Copyright (c) 1990, 1991, 1992, 1993 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 2.5 1994/07/24 23:32:57 wjr Exp $";

/*
 *	image.c - Images.
 *
 * An image is just a rectangular array of some type (several types are
 * implemented here).
 *
 * Exports:
 *	type struct { unsigned char r, g, b } RGB
 *
 *	type ImageType
 *
 *	type GrayImage
 *	type FloatImage
 *	type RGBImage
 *	type DoubleImage
 *	type BinaryImage
 *	type LongImage
 *	type PtrImage
 *	type ShortImage
 *
 *	type GrayPixPtr
 *	type FloatPixPtr
 *	type RGBPixPtr
 *	type DoublePixPtr
 *	type BinaryPixPtr
 *	type LongPixPtr
 *	type PtrPixPtr
 *	type ShortPixPtr
 *
 *	constant ImageType IMAGE_GRAY
 *	constant ImageType IMAGE_FLOAT
 *	constant ImageType IMAGE_RGB
 *	constant ImageType IMAGE_DOUBLE
 *	constant ImageType IMAGE_BINARY
 *	constant ImageType IMAGE_LONG
 *	constant ImageType IMAGE_PTR
 *	constant ImageType IMAGE_SHORT
 *
 *	constant int COLRNG
 *
 *	void *imNew(ImageType type, unsigned width, unsigned height) - create
 *		a new image of the appropriate type. The pointer returned
 *		will be a GrayImage, FloatImage, RGBImage, DoubleImage, 
 *		BinaryImage or WhateverelseImage as appropriate.
 *
 *	void *imNewOffset(ImageType type, unsigned width, unsigned height,
 *			  int xbase, int ybase) - create a new image of
 *		the appropriate type whose addressing begins
 *		at (xbase, ybase).
 *		imNew is equivalent to xbase = ybase = 0.
 *
 *	void imFree(void *im) - free an image.
 *
 *	void imSetOffset(void *im, int newxbase, int newybase) - modify
 *		an image so that its addressing begins at
 *		(newxbase, newybase).
 *
 *	void *imLoad(ImageType type, char *filename) - load an image from
 *		a file. The pointer returned will be of the appropriate
 *		type. Its base will be (0,0).
 *
 *	void *imLoadF(ImageType type, FILE *inFile) - load an image
 *		from a stream. The pointer returned will be of the
 *		appropriate type.
 *
 *	int imSave(void *im, char *filename) - save an image into a file.
 *
 *	int imSaveF(void *im, FILE *outFile) - save an image to a stream.
 *
 *	void *imDup(void *im) - duplicate an image
 *
 *	void *imPromote(void *im, ImageType promoteto) - promote an image to
 *		some higher type.
 *
 *	void imSetPromotion(ImageType promotefrom, ImageType promoteto,
 *			    double slope, double offset) - change the method
 *		of image promotion.
 *
 *	void *imDemote(void *im, ImageType demoteto) - demote an image to some
 *		lower type.
 *
 *	void *imConvert(void *im, ImageType convertto) - convert (promote
 *		or demote as appropriate) an image to some other type.
 *
 *	unsigned imGetWidth(void *im) - get the width of an image.
 *
 *	unsigned imGetHeight(void *im) - get the height of an image.
 *
 *	int imGetXBase(void *im) - get the x-base of an image.
 *
 *	int imGetYBase(void *im) - get the y-base of an image.
 *
 *	int imGetXMax(void *im) - get the maximum valid X value of an image.
 *
 *	int imGetYMax(void *im) - get the maximum valid Y value of an image.
 *
 *	ImageType imGetType(void *im) - get the type of an image.
 *
 *	char *imGetComment(void *im) - get the comment attached to an image.
 *
 *	boolean imSetComment(void *im, char *comment) - set the comment
 *		attached to an image.
 *
 *	macro imRef(void *im, int x, int y) - access an element
 *		of some form of Image.
 *
 *	macro imGetPixPtr(void *im, int x, int y) - get a handle on a pixel of
 *		some form of Image.
 *
 *	macro imPtrRef(void *im, *PixPtr) - dereference a PixPtr of some type.
 *
 *	macro imPtrDup(void *im, *PixPtr) - duplicate a PixPtr of some type.
 *
 *	void imPtrUp(void *im, *PixPtr) - move a PixPtr up in the image.
 *
 *	void imPtrDown(void *im, *PixPtr) - move a PixPtr down in the image.
 *
 *	void imPtrLeft(void *im, *PixPtr) - move a PixPtr left in the image.
 *
 *	void imPtrRight(void *im, *PixPtr) - move a PixPtr right in the image.
 *
 *	boolean imPtrEq(void *im, *PixPtr1, *PixPtr2) - check two PixPtrs
 *		for equality.
 *
 * An image is a rectangular storage area, with each cell being of some
 * type. In this case, the types are unsigned char, float, RGB, double,
 * binary, long, void * and short. Float is large enough for most purposes for
 * image processing; for those for which it is not, DoubleImage is provided;
 * the space costs are much higher for this, so it should be avoided unless
 * necessary. Binary images can store 0 or 1 in each location. By convention,
 * 0 is white and 1 is black in binary images.
 * PtrImages can store a void * pointer at each location. These images may
 * not be saved or loaded.
 * COLRNG is the number of discrete gray levels in a GrayImage, and the number
 * of levels in each component of an RGBImage. COLRNG-1 is the brightest
 * (most white) GrayImage value.
 *
 * N.B. Older versions of this datatype had a different definition for RGB,
 * namely:
 *	typedef unsigned char RGB[3];
 * Due to C's weirdness with arrays, this proved troublesome, so the new
 * definition was installed. However, since some code relies on the old
 * definition, a backwards compatibility mode is available. To get it,
 * #define IMAGE_OLD_RGB
 * before including image.h.
 *
 * imNew creates an Image of the given size, of the appropriate type,
 *	returning (void *)NULL on failure. The Image returned
 *	contains all zeros. The return value should be cast to
 *	the appropriate type.
 *
 * imNewOffset creates an image of the given size, of the appropriate type,
 *	returning (void *)NULL on failure. The valid range of addresses
 *	for the new image is (xbase..xbase+width-1, ybase..ybase+height-1).
 *	Addressing an image with a shifted base may be more efficient than
 *	performing the base shift for each access.
 *
 * imFree frees the given Image.
 *
 * imSetOffset modifies the given image so that its range of addresses
 *	is (newxbase..newxbase+width-1, newybase..newybase+height-1).
 *	The image data is not changed. The pixel which was previously
 *	addressed as imRef(im, imGetXBase(im), imGetYBase(im)) will now
 *	be addressed as imRef(im, newxbase, newybase). imGetXBase(im) and
 *	imGetYBase(im) will be updated to newxbase and newybase.
 *
 * imLoad attempts to load an Image from a file. It checks to see that
 *	what is stored in the file has the appropriate type, and if not
 *	returns NULL. It then attempts to load the image from the file,
 *	and if successful returns the (.*)Image as appropriate. If it
 *	fails, it returns NULL. The base of the loaded image is (0,0),
 *	regardless of what it was when it was saved. The file is closed in
 *	all cases.
 *	If the file does not contain an image of the appropriate type, but
 *	does contain an image of a type which may be promoted to that type
 *	(for example, if it contains a BinaryImage and imLoad is attempting
 *	to load a GrayImage), the image will be read and silently promoted
 *	to the requested type (see imPromote). If such a promotion is not
 *	possible, NULL is returned.
 *	Any comments present in the original file are read in and stored
 *	as the comment associated with the image (see imGetComment).
 *	If more than one comment line was present in the file, then the
 *	contents of the lines will be separated by newlines. There is no
 *	trailing newline; thus, the image read from a file with a single
 *	comment line will contain the contents of that line with no extra
 *	newline. There is a maximum limit on the amount of comments which
 *	will be read from a file of 8192 bytes (including newlines).
 *
 * imLoadF attempts to load an Image from a stdio stream. It performs the
 *	same operations as imLoad, except that it assumes the stream is
 *	open and correctly positioned. After loading, the stream is positioned
 *	to just after the image; it is not closed. If the image cannot be
 *	loaded (i.e. if NULL is returned), the position of the stream (and
 *	what has been written to it) is undefined.
 *
 * imSave attempts to write the given Image out to a file. If successful,
 *	it returns 0. If it fails, it returns -1. Files written by imSave
 *	from IMAGE_FLOAT, IMAGE_DOUBLE, IMAGE_LONG and IMAGE_SHORT images
 *	can only be re-read on same type of machine as they were written,
 *	unless two different machines happen to share a floating point format,
 *	endianness, and	alignment restrictions. Luckily, this is often
 *	the case. It also writes out the comment associated with the image
 *	as a comment in the file. The rules for this are:
 *	- Every newline in the comment string causes a new comment line to
 *	  be begun in the file.
 *	- If any line would be longer than 70 characters, it is broken up.
 *	  If possible, it will be broken at a whitespace character.
 *	If imSave fails (returns -1), the contents of the file are
 *	undefined.
 *
 * imSaveF performs the same function as imSave, except that it writes
 *	to a previously-opened stream. After the save, the stream
 *	is positioned after the newly-written data.
 *
 * imDup makes an exact duplicate of an Image - another Image having
 *	the same size, type and offsets, the same image data, and the same
 *	comment. It returns (void *)NULL if it cannot create the duplicate.
 *
 * imPromote converts an image to a "higher" type of image. The arguments are
 *	the image to be converted and the type into which it should be
 *	converted. It returns (void *)NULL if it cannot create the new image.
 *	Only certain types of conversions are possible with this routine.
 *	They are:
 *	BinaryImages can be promoted to:
 *		GrayImage, ShortImage, LongImage, FloatImage, DoubleImage,
 *		RGBImage
 *	GrayImages can be promoted to:
 *		ShortImage, LongImage, FloatImage, DoubleImage, RGBImage
 *	ShortImages can be promoted to:
 *		LongImage, FloatImage, DoubleImage
 *	LongImages can be promoted to:
 *		DoubleImage
 *	FloatImages can be promoted to:
 *		DoubleImage
 *	The rules of the conversions are:
 *	BinaryImage->GrayImage, ShortImage, LongImage:	(1-COLRNG,COLRNG-1)
 *		0 maps to COLRNG - 1
 *		1 maps to 0
 *	BinaryImage->FloatImage, DoubleImage:		(1,0)
 *		0 maps to 0
 *		1 maps to 1
 *	BinaryImage->RGBImage:				(1-COLRNG,COLRNG-1)
 *		0 maps to {COLRNG-1, COLRNG-1, COLRNG-1}
 *		1 maps to {0, 0, 0}
 *	GrayImage->ShortImage, LongImage, FloatImage, DoubleImage: (1,0)
 *		a pixel with value v in the original image has value v
 *		in the converted image. (i.e. v maps to v)
 *	GrayImage->RGBImage:				(1,0)
 *		v maps to {v, v, v}
 *	ShortImage->LongImage, FloatImage, DoubleImage:	(1,0)
 *		v maps to v
 *	LongImage->DoubleImage:				(1,0)
 *		v maps to v
 *	FloatImage->DoubleImage:			(1,0)
 *		v maps to v
 *	The meaning of the numbers in parentheses are: if a conversion
 *	is labelled (a,b), then a pixel with input value v will be mapped
 *	to av+b. These values are the default ones; they may be changed at
 *	any time with imSetPromotion. Note that these default values are not
 *	transitive: promoting (for example) a BinaryImage to a GrayImage then
 *	to a FloatImage will give different results than promoting the
 *	BinaryImage directly to a FloatImage.
 *	When converting to an RGBImage, all three components are set to the
 *	same value.
 *	These conversions are all those which are strictly "promotions": no
 *	information is lost during the conversion.
 *
 * imSetPromotion alters the parameters of one of the promotions. The
 *	"promotefrom" and "promoteto" ImageTypes must correspond to a
 *	valid promotion. The slope and offset parameters will then be used
 *	for all future promotions from images of type "promotefrom" to images
 *	of type "promoteto". This includes the implicit promotions performed
 *	by imLoad and imLoadF.
 *
 * imDemote converts an Image to an Image of lower type. The conversions
 *	which this routine may perform are:
 *	DoubleImages can be demoted to:
 *		BinaryImage, GrayImage, ShortImage, LongImage, FloatImage,
 *		RGBImage
 *	FloatImages can be demoted to:
 *		BinaryImage, GrayImage, ShortImage, LongImage, RGBImage
 *	LongImages can be demoted to:
 *		BinaryImage, GrayImage, ShortImage, FloatImage, RGBImage
 *	ShortImages can be demoted to:
 *		BinaryImage, GrayImage, RGBImage
 *	GrayImages can be demoted to:
 *		BinaryImage
 *	RGBImages can be demoted to:
 *		BinaryImage, GrayImage, ShortImage, LongImage, FloatImage,
 *		DoubleImage
 *	All these conversions have the property that information may be lost.
 *	Note that some conversions may lose information in both directions
 *	(for example DoubleImage<->RGBImage).
 *	The rules of the conversions are:
 *	DoubleImage->FloatImage:
 *		v maps to v, if v lies in the range of floats
 *		undefined if v does not
 *	DoubleImage->LongImage, ShortImage, GrayImage:
 *		v maps to floor(v) if floor(v) is representable in the
 *		destination
 *		undefined if floor(v) is not
 *	DoubleImage->BinaryImage:
 *		0 maps to 0
 *		non-zero maps to 1
 *	DoubleImage->RGBImage
 *		v maps to {floor(v), floor(v), floor(v)} if floor(v) is
 *		representable in the destination
 *		undefined if floor(v) is not
 *	FloatImage->LongImage, ShortImage, GrayImage:
 *		v maps to floor(v) if floor(v) is representable in the
 *		destination
 *		undefined if floor(v) is not
 *	FloatImage->BinaryImage:
 *		0 maps to 0
 *		non-zero maps to 1
 *	FloatImage->RGBImage
 *		v maps to {floor(v), floor(v), floor(v)} if floor(v) is
 *		representable in the destination
 *		undefined if floor(v) is not
 *	LongImage->ShortImage, GrayImage
 *		v maps to v if v is representable in the destination
 *		undefined if v is not
 *	LongImage->FloatImage
 *		v maps to v (note that accuracy may be lost)
 *	LongImage->BinaryImage
 *		0 maps to 1
 *		non-zero maps to 0
 *	LongImage->RGBImage
 *		v maps to {v, v, v} if v is representable in the destination
 *		undefined if v is not
 *	ShortImage->GrayImage
 *		v maps to v if v is representable in the destination
 *		undefined if v is not
 *	ShortImage->BinaryImage
 *		0 maps to 1
 *		non-zero maps to 0
 *	ShortImage->RGBImage
 *		v maps to {v, v, v} if v is representable in the destination
 *		undefined if v is not
 *	GrayImage->BinaryImage
 *		0 maps to 1
 *		non-zero maps to 0
 *	RGBImage->BinaryImage
 *		If all components are zero, then 1
 *		0 otherwise
 *	RGBImage->anything else
 *		The R, G and B components are first combined by
 *		v = .299R + .587G + .114B
 *		The resulting value is then assigned to the new image pixel.
 *	These conversions are as closely as possibly the inverses of the
 *	conversion routines used by imPromote (i.e. a promotion followed by a
 *	demotion will give the original image back). Note that this makes them
 *	non-transitive, and that there are some conversions which are
 *	demotions in both directions. None of these conversions may be
 *	altered; if another type of conversion is desired, a special routine
 *	may be written.
 *
 * imConvert converts (promotes or demotes) an image to an image of another
 *	type. Any two types of image may be converted between, except for
 *	PtrImages. This routine is a convenience routine which calls
 *	imPromote or imDemote as necessary. As a special case,
 *	converting an image to its original type has the same effect as
 *	imDup EXCEPT that the comment is not copied (no other conversion copies
 *	the comment, so this behaviour is consistent). Note that this form of
 *	conversion is neither a promotion nor a demotion, and so will be
 *	rejected by both imPromote and imDemote.
 *
 * imGetWidth returns the width of the given Image.
 *
 * imGetHeight returns the height of the given Image.
 *
 * imGetXBase returns the X base of the given Image. This is the lowest
 *	valid X value.
 *
 * imGetYBase returns the Y base of the given Image. This is the lowest
 *	valid Y value.
 *
 * imGetXMax returns the highest valid X value of an Image. This is equal to
 *	imGetXBase(im)+imGetWidth(im)-1.
 *
 * imGetYMax returns the highest valid Y value of an Image. This is equal to
 *	imGetYBase(im)+imGetHeight(im)-1.
 *
 * imGetType returns the ImageType of the given Image.
 *
 * imGetComment returns the comment associated with the Image, or NULL if
 *	there is none. This is the comment which was present in the file
 *	from which the image was loaded, if any, or a comment associated
 *	with the image later using imSetComment. Note that the memory
 *	pointed at by this pointer is owned by the Image module, and should
 *	not be freed; it will remain valid until the Image is freed, or the
 *	comment is changed.
 *
 * imSetComment assigns the given string as the comment of the given Image.
 *	It returns TRUE if it is successful, FALSE if it is not. It makes
 *	a copy of the string; the original string is not referred to after
 *	this, and may be freed or altered without affecting the Image's
 *	comment. You can pass in NULL as the new comment. If the comment
 *	could not be changed (i.e. FALSE is returned), then the old comment
 *	remains.
 *
 * imRef gives access to an entry in an Image of some form (it works
 *	for all defined forms of Image). It gives an lvalue: it is valid
 *	to say imRef(i, x, y) = foo;
 *
 * The PixPtr types allow efficient access to contiguous regions of an Image.
 * You get a PixPtr by calling imGetPixPtr, giving the location in the image
 * you would like a handle on. You can then dereference this using
 * imPtrRef. imRef(im, x, y) gives the same value as
 * imPtrRef(imGetPixPtr(im, x, y)); note that this is an lvalue and may be
 * assigned to.
 * The advantage of PixPtrs is that they can be efficiently moved through
 * the image array. For example, if im is a GrayImage:
 * for (x = 0; x < w; x++) {
 *     imRef(im, x, y) = 123;
 *     }
 * can be replaced by:
 * GrayPixPtr pp;
 * pp = imGetPixPtr(im, 0, y);
 * for (x = 0; x < w; x++) {
 *     imPtrRef(im, pp) = 123;
 *     imPtrRight(im, pp);
 *     }
 * which may run more efficiently.
 *
 * imGetPixPtr gets a handle on the (x,y) pixel in the image im, giving a
 *	PixPtr of the appropriate type.
 *
 * imPtrRef dereferences a PixPtr of some type, giving an lvalue of the
 *	appropriate type.
 *
 * imPtrDup duplicates a PixPtr of some type, returning a PixPtr of the same
 *	type, initially referring to the same location in the image.
 *
 * imPtrUp, imPtrDown, imPtrLeft and imPtrRight move a PixPtr through an
 *	Image. imPtrUp effectively decrements the y component of the pixel
 *	to which the pointer refers; imPtrDown increments it. imPtrLeft and
 *	imPtrRight decrement and increment respectively the x component.
 *
 * imPtrEq compares two pointers for equality, returning TRUE if they are,
 *	FALSE if they are not. The pointers must refer to pixels in the
 *	same Image. The use of this allows more efficient image operations.
 *
 * Some of these are implemented partially as macros for strange reasons.
 * As a result, they may not be entirely safety-checked. If you want to
 * have safety-checking in place, at a cost in speed and code size, put
 * -DIMAGE_CHECKALL on your gcc line, or add
 * #define IMAGE_CHECKALL
 * to your code somewhere before you include image.h.
 * This only works if you're compiling with gcc.
 *
 */

#include "misc.h"
#include "image.h"
#include <ctype.h>
#include <stdio.h>

/* File tags */
/* Standard pbmplus tags */
#define	TAG_BINARY_COOKED	"P1"
#define	TAG_GRAY_COOKED		"P2"
#define	TAG_RGB_COOKED		"P3"
#define	TAG_BINARY		"P4"
#define	TAG_GRAY		"P5"
#define	TAG_RGB			"P6"
/* Nonstandard wjr tags */
#define	TAG_FLOAT	"Q1"
#define	TAG_DOUBLE	"Q2"
#define	TAG_LONG	"Q3"
#define	TAG_SHORT	"Q4"

/* Maximum line length in a non-raw P?M file */
#define	PBM_MAX_LINELEN	70

/* How big a comment buffer we'll have for reading files */
#define	LOAD_COMMENT_MAX	8192

static char incomment[LOAD_COMMENT_MAX];
static int incommentsize;

static void *readBinaryCooked(FILE *inFile, ImageHeader **readimHeader);
static void *readGrayCooked(FILE *inFile, ImageHeader **readimHeader);
static void *readRGBCooked(FILE *inFile, ImageHeader **readimHeader);
static void *readBinary(FILE *inFile, ImageHeader **readimHeader);
static void *readGray(FILE *inFile, ImageHeader **readimHeader);
static void *readRGB(FILE *inFile, ImageHeader **readimHeader);
static void *readFloat(FILE *inFile, ImageHeader **readimHeader);
static void *readDouble(FILE *inFile, ImageHeader **readimHeader);
static void *readLong(FILE *inFile, ImageHeader **readimHeader);
static void *readShort(FILE *inFile, ImageHeader **readimHeader);

/*
 * Here begins file magic: list of tags with associated file types and
 * reading functions.
 */
static struct {
    char *filetag;
    ImageType filetype;
    void * (*readfunc)(FILE *inFile, ImageHeader **readimHeader);
    } fileReaders[] = {
	{ TAG_BINARY_COOKED, IMAGE_BINARY, readBinaryCooked },
	{ TAG_GRAY_COOKED, IMAGE_GRAY, readGrayCooked },
	{ TAG_RGB_COOKED, IMAGE_RGB, readRGBCooked },
	{ TAG_BINARY, IMAGE_BINARY, readBinary },
	{ TAG_GRAY, IMAGE_GRAY, readGray },
	{ TAG_RGB, IMAGE_RGB, readRGB },
	{ TAG_FLOAT, IMAGE_FLOAT, readFloat },
	{ TAG_DOUBLE, IMAGE_DOUBLE, readDouble },
	{ TAG_LONG, IMAGE_LONG, readLong },
	{ TAG_SHORT, IMAGE_SHORT, readShort }
	};
#define	NREADERS	(sizeof(fileReaders) / sizeof(fileReaders[0]))

/* Here are all the conversion routines */
static void                  *pG2F(void *im), *pG2R(void *im), *pG2D(void *im);
static void *dG2B(void *im), *pG2L(void *im), *pG2S(void *im);
static void *dF2G(void *im),                  *dF2R(void *im), *pF2D(void *im);
static void *dF2B(void *im), *dF2L(void *im), *dF2S(void *im);
static void *dR2G(void *im), *dR2F(void *im),                  *dR2D(void *im);
static void *dR2B(void *im), *dR2L(void *im), *dR2S(void *im);
static void *dD2G(void *im), *dD2F(void *im), *dD2R(void *im)                 ;
static void *dD2B(void *im), *dD2L(void *im), *dD2S(void *im);
static void *pB2G(void *im), *pB2F(void *im), *pB2R(void *im), *pB2D(void *im);
static void                  *pB2L(void *im), *pB2S(void *im);
static void *dL2G(void *im), *dL2F(void *im), *dL2R(void *im), *pL2D(void *im);
static void *dL2B(void *im),                  *dL2S(void *im);
static void *dS2G(void *im), *pS2F(void *im), *dS2R(void *im), *pS2D(void *im);
static void *dS2B(void *im), *pS2L(void *im)                 ;

/*
 * Here's the table of conversions.
 * convertTable[type1][type2] tells how we convert from an image of type1
 * to an image of type2.
 */
static struct {
    boolean isPromotion;
    double slope;
    double offset;
    void * (*convertFunc)(void *im);
    } convertTable[IMAGE_SHORT + 1][IMAGE_SHORT + 1] = {
	{	/* Invalid image type */
	{ FALSE, 0, 0, NULL}, { FALSE, 0, 0, NULL}, { FALSE, 0, 0, NULL},
	{ FALSE, 0, 0, NULL}, { FALSE, 0, 0, NULL}, { FALSE, 0, 0, NULL},
	{ FALSE, 0, 0, NULL}, { FALSE, 0, 0, NULL}, { FALSE, 0, 0, NULL} },

	{	/* GrayImage */
	{ FALSE, 0, 0, NULL}, { FALSE, 0, 0, NULL}, { TRUE , 1, 0, pG2F},
	{ TRUE , 1, 0, pG2R}, { TRUE , 1, 0, pG2D}, { FALSE, 0, 0, dG2B},
	{ TRUE , 1, 0, pG2L}, { FALSE, 0, 0, NULL}, { TRUE , 1, 0, pG2S} },

	{	/* FloatImage */
	{ FALSE, 0, 0, NULL}, { FALSE, 0, 0, dF2G}, { FALSE, 0, 0, NULL},
	{ FALSE, 0, 0, dF2R}, { TRUE , 1, 0, pF2D}, { FALSE, 0, 0, dF2B},
	{ FALSE, 0, 0, dF2L}, { FALSE, 0, 0, NULL}, { FALSE, 0, 0, dF2S} },

	{	/* RGBImage */
	{ FALSE, 0, 0, NULL}, { FALSE, 0, 0, dR2G}, { FALSE, 0, 0, dR2F},
	{ FALSE, 0, 0, NULL}, { FALSE, 0, 0, dR2D}, { FALSE, 0, 0, dR2B},
	{ FALSE, 0, 0, dR2L}, { FALSE, 0, 0, NULL}, { FALSE, 0, 0, dR2S} },

	{	/* DoubleImage */
	{ FALSE, 0, 0, NULL}, { FALSE, 0, 0, dD2G}, { FALSE, 0, 0, dD2F},
	{ FALSE, 0, 0, dD2R}, { FALSE, 0, 0, NULL}, { FALSE, 0, 0, dD2B},
	{ FALSE, 0, 0, dD2L}, { FALSE, 0, 0, NULL}, { FALSE, 0, 0, dD2S} },

	{	/* BinaryImage */
	{ FALSE, 0, 0, NULL}, { TRUE , 1-COLRNG, COLRNG-1, pB2G}, { TRUE , 1, 0, pB2F},
	{ TRUE , 1-COLRNG, COLRNG-1, pB2R}, { TRUE , 1, 0, pB2D}, { FALSE, 0, 0, NULL},
	{ TRUE , 1-COLRNG, COLRNG-1, pB2L}, { FALSE, 0, 0, NULL}, { TRUE , 1-COLRNG, COLRNG-1, pB2S} },
	
	{	/* LongImage */
	{ FALSE, 0, 0, NULL}, { FALSE, 0, 0, dL2G}, { FALSE, 0, 0, dL2F},
	{ FALSE, 0, 0, dL2R}, { TRUE , 1, 0, pL2D}, { FALSE, 0, 0, dL2B},
	{ FALSE, 0, 0, NULL}, { FALSE, 0, 0, NULL}, { FALSE, 0, 0, dL2S} },

	{	/* PtrImage */
	{ FALSE, 0, 0, NULL}, { FALSE, 0, 0, NULL}, { FALSE, 0, 0, NULL},
	{ FALSE, 0, 0, NULL}, { FALSE, 0, 0, NULL}, { FALSE, 0, 0, NULL},
	{ FALSE, 0, 0, NULL}, { FALSE, 0, 0, NULL}, { FALSE, 0, 0, NULL} },

	{	/* ShortImage */
	{ FALSE, 0, 0, NULL}, { FALSE, 0, 0, dS2G}, { TRUE , 1, 0, pS2F},
	{ FALSE, 0, 0, dS2R}, { TRUE , 1, 0, pS2D}, { FALSE, 0, 0, dS2B},
	{ TRUE , 1, 0, pS2L}, { FALSE, 0, 0, NULL}, { FALSE, 0, 0, NULL} }
	};

/*
 * 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(unsigned width, unsigned height, int xbase, int ybase,
	   unsigned el_size)
{
    register int i;
    register void *mainArea;
    register void **ptrArea;
    register char *p;
    register void **q;

    /* Allocate the actual image data area */
    mainArea = calloc(height, width*el_size);
    if (mainArea == (void *)NULL) {
	return((void **)NULL);
	}

    /* Allocate a pointer for each row */
    ptrArea = (void **)malloc(height * sizeof(void *));
    if (ptrArea == (void **)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)
{
    /* Free the memory block. This should give the first element. */
    free((void *)((char *)(*(ptr + ybase)) + xbase * (int)el_size));
    /* and the pointer block */
    free((void *)(ptr + ybase));
    }

/*
 * Root through the image's header to find its type, and get the appropriate
 * base pointer of the data area, and the element size.
 */
static void
getAreaAndSize(void *im, ImageHeader *header,
	       void ***areaPtr, unsigned *el_size)
{
    assert(im != (void *)NULL);
    assert(header != (ImageHeader *)NULL);
    assert(areaPtr != (void ***)NULL);
    assert(el_size != (unsigned *)NULL);

    switch(header->tag) {
      case IMAGE_GRAY: {
	*areaPtr = (void **)(((GrayImage)im)->data);
	*el_size = sizeof(unsigned char);
	break;
	}
	
      case IMAGE_FLOAT: {
	*areaPtr = (void **)(((FloatImage)im)->data);
	*el_size = sizeof(float);
	break;
	}
	
      case IMAGE_RGB: {
	*areaPtr = (void **)(((RGBImage)im)->data);
	*el_size = sizeof(RGB);
	break;
	}

      case IMAGE_DOUBLE: {
	*areaPtr = (void **)(((DoubleImage)im)->data);
	*el_size = sizeof(double);
	break;
	}

      case IMAGE_BINARY: {
	*areaPtr = (void **)(((BinaryImage)im)->data);
	*el_size = sizeof(char);
	break;
	}

      case IMAGE_LONG: {
	*areaPtr = (void **)(((LongImage)im)->data);
	*el_size = sizeof(long);
	break;
	}

      case IMAGE_PTR: {
	*areaPtr = (void **)(((PtrImage)im)->data);
	*el_size = sizeof(void *);
	break;
	}

      case IMAGE_SHORT: {
	*areaPtr = (void **)(((ShortImage)im)->data);
	*el_size = sizeof(short);
	break;
	}

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

/*
 * Given an Image's type, find its header
 */
ImageHeader *
getHeader(void *im, ImageType type)
{
    ImageHeader *header;

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

    switch(type) {
	case IMAGE_GRAY: {
	    header = ((GrayImage)im)->header;
	    break;
	    }
	
	case IMAGE_FLOAT: {
	    header = ((FloatImage)im)->header;
	    break;
	    }
	
	case IMAGE_RGB: {
	    header = ((RGBImage)im)->header;
	    break;
	    }
	
	case IMAGE_DOUBLE: {
	    header = ((DoubleImage)im)->header;
	    break;
	    }
	
	case IMAGE_BINARY: {
	    header = ((DoubleImage)im)->header;
	    break;
	    }
	
	case IMAGE_LONG: {
	    header = ((LongImage)im)->header;
	    break;
	    }
	
	case IMAGE_PTR: {
	    header = ((PtrImage)im)->header;
	    break;
	    }
	
	case IMAGE_SHORT: {
	    header = ((ShortImage)im)->header;
	    break;
	    }
	
	default: {
	    panic("bad image tag");
	    }
	}

    return(header);
    }
    

/*
 * Get a single positive integer from f. Eat a single character of whitespace
 * following the integer. The integer must be terminated by whitespace or
 * EOF. Nothing else (except for a comment) is allowed. Accumulate comments
 * in incomment.
 */
static int
gettoken(FILE *f, boolean *failed)
{
    int val;
    int c;

    assert(failed != (boolean *)NULL);

    /* Skip over whitespace and comments */
    while (TRUE) {
	do {
	    c = getc(f);
	    if (c == EOF) {
		*failed = TRUE;
		return(0);
		}
	    } while (isspace(c));

	if (c == '#') {
	    /* A comment - skip to the newline */
	    do {
		c = getc(f);
		if (c == EOF) {
		    *failed = TRUE;
		    return(0);
		    }
		if (incommentsize < LOAD_COMMENT_MAX - 1) {
		    incomment[incommentsize++] = c;
		    }
		} while (c != '\n');
	    }
	else {
	    break;
	    }
	}

    assert(!isspace(c));
    /* Accumulate the digits */
    val = 0;
    while (isdigit(c)) {
	val = 10 * val + c - '0';
	c = getc(f);
	if (c == EOF) {
	    /* Number terminated by EOF */
	    *failed = FALSE;
	    return(val);
	    }
	}

    if (!isspace(c)) {
	/* Number terminated by something which isn't whitespace */
	*failed = TRUE;
	return(0);
	}

    /* Number terminated by whitespace */
    *failed = FALSE;
    return(val);
    }

/*
 * Get a 1 or 0 from f. Eat whitespace before the character, if any.
 * Nothing else (except for a comment) is allowed. Accumulate comments
 * in incomment.
 */
static int
getpbmtoken(FILE *f, boolean *failed)
{
    int c;

    assert(failed != (boolean *)NULL);

    /* Skip over whitespace and comments */
    while (TRUE) {
	do {
	    c = getc(f);
	    if (c == EOF) {
		*failed = TRUE;
		return(0);
		}
	    } while (isspace(c));

	if (c == '#') {
	    /* A comment - skip to the newline */
	    do {
		c = getc(f);
		if (c == EOF) {
		    *failed = TRUE;
		    return(0);
		    }
		if (incommentsize < LOAD_COMMENT_MAX - 1) {
		    incomment[incommentsize++] = c;
		    }
		} while (c != '\n');
	    }
	else {
	    break;
	    }
	}

    assert(!isspace(c));

    *failed = FALSE;
    if (c == '0') {
	return(0);
	}
    else if (c == '1') {
	return(1);
	}
    else {
	*failed = TRUE;
	return(0);
	}
    }

/*
 * Take the comment which was accumulated from the input file, copy it
 * and tack it into this header.
 */
static boolean
setComment(ImageHeader *header)
{
    assert(header != (ImageHeader *)NULL);
		      
    if (incommentsize == 0) {
	/* There was no comment in the input file */
	header->comment = (char *)NULL;
	return(TRUE);
	}

    /* The last thing in the comment should be a newline - clobber it */
    assert(incomment[incommentsize - 1] == '\n');
    incomment[incommentsize] = '\0';
    header->comment = strdup(incomment);
    if (header->comment == (char *)NULL) {
	return(FALSE);
	}
    return(TRUE);
    }

void *
imNew(ImageType type, unsigned width, unsigned height)
{
    return(imNewOffset(type, width, height, 0, 0));
    }

void *
imNewOffset(ImageType type, unsigned width, unsigned height,
	    int xbase, int ybase)
{
    void **areaPtr;
    ImageHeader *newHeader;

    /* 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->comment = (char *)NULL;

    /*
     * 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.
     */
    switch(type) {
      case IMAGE_GRAY: {
	GrayImage newGray;

	newGray = (GrayImage)malloc(sizeof(* newGray));
	if (newGray == (GrayImage)NULL) {
	    free((void *)newHeader);
	    return((void *)NULL);
	    }
	areaPtr = allocImage(width, height, xbase, ybase, sizeof(unsigned char));
	if (areaPtr == (void **)NULL) {
	    free((void *)newHeader);
	    free((void *)newGray);
	    return((void *)NULL);
	    }
	newHeader->body.grayptr = newGray;
	newGray->header = newHeader;
	newGray->data = (unsigned char **)areaPtr;
	return((void *)newGray);
	break;			/* Paranoia strikes */
	}

      case IMAGE_FLOAT: {
	FloatImage newFloat;

	newFloat = (FloatImage)malloc(sizeof(* newFloat));
	if (newFloat == (FloatImage)NULL) {
	    free((void *)newHeader);
	    return((void *)NULL);
	    }
	areaPtr = allocImage(width, height, xbase, ybase, sizeof(float));
	if (areaPtr == (void **)NULL) {
	    free((void *)newHeader);
	    free((void *)newFloat);
	    return((void *)NULL);
	    }
	newHeader->body.floatptr = newFloat;
	newFloat->header = newHeader;
	newFloat->data = (float **)areaPtr;
	return((void *)newFloat);
	break;
	}

      case IMAGE_RGB: {
	RGBImage newRGB;

	newRGB = (RGBImage)malloc(sizeof(* newRGB));
	if (newRGB == (RGBImage)NULL) {
	    free((void *)newHeader);
	    return((void *)NULL);
	    }
	areaPtr = allocImage(width, height, xbase, ybase, sizeof(RGB));
	if (areaPtr == (void **)NULL) {
	    free((void *)newHeader);
	    free((void *)newRGB);
	    return((void *)NULL);
	    }
	newHeader->body.RGBptr = newRGB;
	newRGB->header = newHeader;
	newRGB->data = (RGB **)areaPtr;
	return((void *)newRGB);
	break;
	}

      case IMAGE_DOUBLE: {
	DoubleImage newDouble;

	newDouble = (DoubleImage)malloc(sizeof(* newDouble));
	if (newDouble == (DoubleImage)NULL) {
	    free((void *)newHeader);
	    return((void *)NULL);
	    }
	areaPtr = allocImage(width, height, xbase, ybase, sizeof(double));
	if (areaPtr == (void **)NULL) {
	    free((void *)newHeader);
	    free((void *)newDouble);
	    return((void *)NULL);
	    }
	newHeader->body.doubleptr = newDouble;
	newDouble->header = newHeader;
	newDouble->data = (double **)areaPtr;
	return((void *)newDouble);
	break;
	}

      case IMAGE_BINARY: {
	BinaryImage newBinary;

	newBinary = (BinaryImage)malloc(sizeof(* newBinary));
	if (newBinary == (BinaryImage)NULL) {
	    free((void *)newHeader);
	    return((void *)NULL);
	    }
	areaPtr = allocImage(width, height, xbase, ybase, sizeof(char));
	if (areaPtr == (void **)NULL) {
	    free((void *)newHeader);
	    free((void *)newBinary);
	    return((void *)NULL);
	    }
	newHeader->body.binaryptr = newBinary;
	newBinary->header = newHeader;
	newBinary->data = (char **)areaPtr;
	return((void *)newBinary);
	break;
	}

      case IMAGE_LONG: {
	LongImage newLong;

	newLong = (LongImage)malloc(sizeof(* newLong));
	if (newLong == (LongImage)NULL) {
	    free((void *)newHeader);
	    return((void *)NULL);
	    }
	areaPtr = allocImage(width, height, xbase, ybase, sizeof(long));
	if (areaPtr == (void **)NULL) {
	    free((void *)newHeader);
	    free((void *)newLong);
	    return((void *)NULL);
	    }
	newHeader->body.longptr = newLong;
	newLong->header = newHeader;
	newLong->data = (long **)areaPtr;
	return((void *)newLong);
	break;
	}

      case IMAGE_PTR: {
	PtrImage newPtr;

	newPtr = (PtrImage)malloc(sizeof(* newPtr));
	if (newPtr == (PtrImage)NULL) {
	    free((void *)newHeader);
	    return((void *)NULL);
	    }
	areaPtr = allocImage(width, height, xbase, ybase, sizeof(void *));
	if (areaPtr == (void **)NULL) {
	    free((void *)newHeader);
	    free((void *)newPtr);
	    return((void *)NULL);
	    }
	newHeader->body.ptrptr = newPtr;
	newPtr->header = newHeader;
	newPtr->data = (void ***)areaPtr;
	return((void *)newPtr);
	break;
	}

      case IMAGE_SHORT: {
	ShortImage newShort;

	newShort = (ShortImage)malloc(sizeof(* newShort));
	if (newShort == (ShortImage)NULL) {
	    free((void *)newHeader);
	    return((void *)NULL);
	    }
	areaPtr = allocImage(width, height, xbase, ybase, sizeof(short));
	if (areaPtr == (void **)NULL) {
	    free((void *)newHeader);
	    free((void *)newShort);
	    return((void *)NULL);
	    }
	newHeader->body.shortptr = newShort;
	newShort->header = newHeader;
	newShort->data = (short **)areaPtr;
	return((void *)newShort);
	break;
	}

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

    /*NOTREACHED*/
    }

void
im_Free_(void *im, ImageHeader *header)
{
    void **areaPtr;
    unsigned el_size;
    int xbase, ybase;

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

    /* Pull out the data area and the size of the element */
    getAreaAndSize(im, header, &areaPtr, &el_size);

    /* and get rid of all of them */
    xbase = header->xbase;
    ybase = header->ybase;

    free((void *)im);
    if (header->comment != (char *)NULL) {
	free((void *)header->comment);
	}
    free((void *)header);
    freeImage(areaPtr, xbase, ybase, el_size);
    }

void
im_SetOffset_(void *im, ImageHeader *header, int newxbase, int newybase)
{
    int y;
    int oldxbase;
    int oldybase;

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

    oldxbase = header->xbase;
    oldybase = header->ybase;

#define	FIX_PTRS(im, imtype) \
    { \
	imtype vim = (imtype)(im); \
	for (y = imGetYBase(vim); y <= imGetYMax(vim); y++) { \
	    vim->data[y] += (oldxbase - newxbase); \
	    } \
	vim->data += (oldybase - newybase); \
    }

    switch(header->tag) {
      case IMAGE_GRAY:		FIX_PTRS(im, GrayImage);	break;
      case IMAGE_FLOAT:		FIX_PTRS(im, FloatImage);	break;
      case IMAGE_RGB:		FIX_PTRS(im, RGBImage);		break;
      case IMAGE_DOUBLE:	FIX_PTRS(im, DoubleImage);	break;
      case IMAGE_BINARY:	FIX_PTRS(im, BinaryImage);	break;
      case IMAGE_LONG:		FIX_PTRS(im, LongImage);	break;
      case IMAGE_PTR:		FIX_PTRS(im, PtrImage);		break;
      case IMAGE_SHORT:		FIX_PTRS(im, ShortImage);	break;
      default: {
	panic("bad image tag");
	}
      }

#undef	FIX_PTRS

    header->xbase = newxbase;
    header->ybase = newybase;
    }

void *
imLoad(ImageType type, char *filename)
{
    FILE *inFile;
    void *im;

    /* Open the file, imLoadF on it, then close it */
    assert(filename != (char *)NULL);
    inFile = fopen(filename, "r");
    if (inFile == (FILE *)NULL) {
	return((void *)NULL);
	}

    im = imLoadF(type, inFile);

    if (fclose(inFile) == EOF) {
	/*
	 * It would be a strange day that fclose on a read-only file
	 * gave an error, but...
	 *
	 * Sigh - have to track down the image's header so we can free it.
	 */
	if (im) {
	    ImageHeader *header = getHeader(im, type);
	    im_Free_(im, header);
	    }
	return((void *)NULL);
	}

    return(im);
    }

void *
imLoadF(ImageType type, FILE *inFile)
{
    char fileHeader[PBM_MAX_LINELEN];
    void *im;
    int c;
    int i;
    ImageType filetype;
    void * (*readfunc)(FILE *inFile, ImageHeader **readimHeader);
    void *readim;
    ImageHeader *readimHeader;

    assert(inFile != (FILE *)NULL);

    /*
     * Read up to the first whitespace in order to find the file's header.
     */
    i = 0;
    do {
	c = getc(inFile);
	if (c == EOF) {
	    return((void *)NULL);
	    }
	fileHeader[i++] = c;
	} while (!isspace(c));
    fileHeader[--i] = '\0';		/* Clobber the whitespace */

    /* Determine who is supposed to handle this sort of file */
    for (i = 0; i < NREADERS; i++) {
	if (strcmp(fileHeader, fileReaders[i].filetag) == 0) {
	    /* Found it */
	    filetype = fileReaders[i].filetype;
	    readfunc = fileReaders[i].readfunc;
	    break;
	    }
	}
    if (i == NREADERS) {
	/* Didn't match any of the ones in the table - unknown type. */
	return((void *)NULL);
	}

    /* Clear the comment buffer */
    incommentsize = 0;

    /* Read in the file */
    readim = (*readfunc)(inFile, &readimHeader);
    if (readim == (void *)NULL) {
	/* Didn't load for whatever reason */
	return((void *)NULL);
	}

    if (filetype == type) {
	/* The file was of the expected type - no conversion required */
	if (!setComment(readimHeader)) {
	    /* Sigh - stumbled over the comment */
	    im_Free_(readim, readimHeader);
	    return((void *)NULL);
	    }
	return(readim);
	}

    /*
     * OK. We read in a file, and it wasn't what we expected. We get to
     * promote it to the expected image type, if that's defined.
     *
     * If that conversion isn't a promotion, kick out.
     */
    if (!convertTable[filetype][type].isPromotion) {
	im_Free_(readim, readimHeader);
	return((void *)NULL);
	}
    
    /* Convert... */
    im = im_Promote_(readim, readimHeader, type);
    if (im == (void *)NULL) {
	/* Didn't work. */
	im_Free_(readim, readimHeader);
	return((void *)NULL);
	}

    /* Kill the image we read in, and return the promoted one. */
    im_Free_(readim, readimHeader);

    /* Stick the comment from the file into the header */
    readimHeader = getHeader(im, type);
    if (!setComment(readimHeader)) {
	/* Died in the comment copy */
	im_Free_(im, readimHeader);
	return((void *)NULL);
	}

    return(im);
    }

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

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

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

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

    return(0);
    }

int
im_SaveF_(void *im, ImageHeader *header, FILE *outFile)
{
    void *rect;
    unsigned el_size;
    char *startString;
    char *startTag;
    boolean maxvalPresent;
    int i;
    char *p;
    char commentBuf[PBM_MAX_LINELEN];

    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).
     */
    switch (header->tag) {
      case IMAGE_GRAY: {
	rect = (void *)
	    &(((((GrayImage)im)->data)[header->ybase])[header->xbase]);
	el_size = sizeof(unsigned char);
	/* This is the start of an appropriate PGM file header */
	startTag = TAG_GRAY;
	startString = "%d %d %d\n";
	maxvalPresent = TRUE;
	break;
	}
	    
      case IMAGE_FLOAT: {
	rect = (void *)
	    &(((((FloatImage)im)->data)[header->ybase])[header->xbase]);
	el_size = sizeof(float);
	/*
	 * Not a PGM or PBM or PanythingM header - make it look strange.
	 */
	startTag = TAG_FLOAT;
	startString = "%d %d\n";
	maxvalPresent = FALSE;
	break;
	}
	    
      case IMAGE_RGB: {
	rect = (void *)
	    &(((((RGBImage)im)->data)[header->ybase])[header->xbase]);
	el_size = sizeof(RGB);
	/* A PPM header */
	startTag = TAG_RGB;
	startString = "%d %d %d\n";
	maxvalPresent = TRUE;
	break;
	}

      case IMAGE_DOUBLE: {
	rect = (void *)
	    &(((((DoubleImage)im)->data)[header->ybase])[header->xbase]);
	el_size = sizeof(double);
	/*
	 * Not a PGM or PBM or PanythingM header - make it look strange.
	 */
	startTag = TAG_DOUBLE;
	startString = "%d %d\n";
	maxvalPresent = FALSE;
	break;
	}
	    
      case IMAGE_BINARY: {
	rect = (void *)
	    &(((((BinaryImage)im)->data)[header->ybase])[header->xbase]);
	el_size = 0;
	startTag = TAG_BINARY;
	startString = "%d %d\n";
	maxvalPresent = FALSE;
	break;
	}
	    
      case IMAGE_LONG: {
	rect = (void *)
	    &(((((LongImage)im)->data)[header->ybase])[header->xbase]);
	el_size = sizeof(long);
	/*
	 * Not a PGM or PBM or PanythingM header - make it look strange.
	 */
	startTag = TAG_LONG;
	startString = "%d %d\n";
	maxvalPresent = FALSE;
	break;
	}

      case IMAGE_PTR: {
	panic("attempt to save pointer image");
	break;
	}

      case IMAGE_SHORT: {
	rect = (void *)
	    &(((((ShortImage)im)->data)[header->ybase])[header->xbase]);
	el_size = sizeof(short);
	/*
	 * Not a PGM or PBM or PanythingM header - make it look strange.
	 */
	startTag = TAG_SHORT;
	startString = "%d %d\n";
	maxvalPresent = FALSE;
	break;
	}

      default: {
	panic("bad image tag");
	}
      }
    
    /* Write out the tag, and comments if any */
    if (fprintf(outFile, "%s\n", startTag) == EOF) {
	return(-1);
	}

    if (header->comment != (char *)NULL) {
	p = header->comment;
	while (*p != '\0') {
	    /* Copy a line into commentBuf */
	    for (i = 0;
		 (i < PBM_MAX_LINELEN) && (p[i] != '\n') && (p[i] != '\0');
		 i++) {
		commentBuf[i] = p[i];
		}
	    if (i == PBM_MAX_LINELEN) {
		/* The line was too long - search back for whitespace */
		for (i = PBM_MAX_LINELEN - 1;
		     (i >= 0) && (!isspace(commentBuf[i]));
		     i--) {
		    }
		if (i < 0) {
		    /* There was no whitespace. Break it how we like it... */
		    commentBuf[PBM_MAX_LINELEN - 1] = '\0';
		    i = PBM_MAX_LINELEN - 1;
		    }
		else {
		    /* Break at the whitespace */
		    commentBuf[i] = '\0';
		    i++;
		    }
		}
	    else {
		commentBuf[i] = '\0';
		}
	    /*
	     * At this point, commentBuf contains i characters to be
	     * printed.
	     */
	    if (fprintf(outFile, "#%s\n", commentBuf) == EOF) {
		return(-1);
		}
	    /* Advance the pointer */
	    p += i;
	    if (*p == '\n') {
		p++;
		}
	    }

	}

    /* Write out the header info */
    if (maxvalPresent) {
	if (fprintf(outFile, startString, header->width, header->height,
		    COLRNG - 1) == EOF) {
	    return(-1);
	    }
	}
    else {
	if (fprintf(outFile, startString, header->width,
		    header->height) == EOF) {
	    return(-1);
	    }
	}
	

    /* Write out the data block */
    if (header->tag != IMAGE_BINARY) {
	if (fwrite((char *)rect, el_size,
		   (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, boolean copycomment)
{
    void *dup;
    unsigned el_size;
    char *oldbase;
    char *newbase;
    ImageHeader *newheader;

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

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

    switch(header->tag) {
      case IMAGE_GRAY: {
	oldbase = (void *)
	    &(((GrayImage)im)->data[header->ybase][header->xbase]);
	newbase = (void *)
	    &(((GrayImage)dup)->data[header->ybase][header->xbase]);
	el_size = sizeof(unsigned char);
	newheader = ((GrayImage)dup)->header;
	
	break;
	}

      case IMAGE_FLOAT: {
	oldbase = (void *)
	    &(((FloatImage)im)->data[header->ybase][header->xbase]);
	newbase = (void *)
	    &(((FloatImage)dup)->data[header->ybase][header->xbase]);
	el_size = sizeof(float);
	newheader = ((FloatImage)dup)->header;

	break;
	}

      case IMAGE_RGB: {
	oldbase = (void *)
	    &(((RGBImage)im)->data[header->ybase][header->xbase]);
	newbase = (void *)
	    &(((RGBImage)dup)->data[header->ybase][header->xbase]);
	el_size = sizeof(RGB);
	newheader = ((RGBImage)dup)->header;

 	break;
	}

      case IMAGE_DOUBLE: {
	oldbase = (void *)
	    &(((DoubleImage)im)->data[header->ybase][header->xbase]);
	newbase = (void *)
	    &(((DoubleImage)dup)->data[header->ybase][header->xbase]);
	el_size = sizeof(double);
	newheader = ((DoubleImage)dup)->header;

	break;
	}

      case IMAGE_BINARY: {
	oldbase = (void *)
	    &(((BinaryImage)im)->data[header->ybase][header->xbase]);
	newbase = (void *)
	    &(((BinaryImage)dup)->data[header->ybase][header->xbase]);
	el_size = sizeof(char);
	newheader = ((BinaryImage)dup)->header;

	break;
	}

      case IMAGE_LONG: {
	oldbase = (void *)
	    &(((LongImage)im)->data[header->ybase][header->xbase]);
	newbase = (void *)
	    &(((LongImage)dup)->data[header->ybase][header->xbase]);
	el_size = sizeof(long);
	newheader = ((LongImage)dup)->header;

	break;
	}

      case IMAGE_PTR: {
	oldbase = (void *)
	    &(((PtrImage)im)->data[header->ybase][header->xbase]);
	newbase = (void *)
	    &(((PtrImage)dup)->data[header->ybase][header->xbase]);
	el_size = sizeof(void *);
	newheader = ((PtrImage)dup)->header;

	break;
	}

      case IMAGE_SHORT: {
	oldbase = (void *)
	    &(((ShortImage)im)->data[header->ybase][header->xbase]);
	newbase = (void *)
	    &(((ShortImage)dup)->data[header->ybase][header->xbase]);
	el_size = sizeof(short);
	newheader = ((ShortImage)dup)->header;

	break;
	}

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

    /* Copy the comment, if any, and if we're supposed to */
    if (copycomment && (header->comment != (char *)NULL)) {
	newheader->comment = strdup(header->comment);
	if (newheader->comment == (char *)NULL) {
	    /* Failed to copy */
	    im_Free_(dup, newheader);
	    return((void *)NULL);
	    }
	}

    /* Do the copy */
    (void)memcpy(newbase, oldbase,
		 (el_size * header->width * header->height));

    return(dup);
    }

void *
im_Promote_(void *im, ImageHeader *header, ImageType promoteto)
{
    ImageType origtype;
    void *newim;

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

    origtype = header->tag;
    if (!convertTable[origtype][promoteto].isPromotion) {
	/* They just requested a non-promotion. Bad... */
	panic("non-promotion request made to imPromote()");
	}
    assert(convertTable[origtype][promoteto].convertFunc !=
	   (void * (*)(void *))NULL);

    newim = (*convertTable[origtype][promoteto].convertFunc)(im);

    return(newim);
    }

void
imSetPromotion(ImageType promotefrom, ImageType promoteto,
	       double slope, double offset)
{
    if (!convertTable[promotefrom][promoteto].isPromotion) {
	panic("attempt made to imSetPromotion a non-promotion conversion");
	}
    convertTable[promotefrom][promoteto].slope = slope;
    convertTable[promotefrom][promoteto].offset = offset;
    }

void *
im_Demote_(void *im, ImageHeader *header, ImageType demoteto)
{
    ImageType origtype;
    void *newim;

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

    origtype = header->tag;
    if (convertTable[origtype][demoteto].isPromotion) {
	/* They just requested a promotion. Bad... */
	panic("promotion request made to imDemote()");
	}
    else if (convertTable[origtype][demoteto].convertFunc == (void * (*)(void *))NULL) {
	panic("impossible request made to imDemote()");
	}

    newim = (*convertTable[origtype][demoteto].convertFunc)(im);

    return(newim);
    }

void *
im_Convert_(void *im, ImageHeader *header, ImageType convertto)
{
    ImageType origtype;

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

    origtype = header->tag;
    if (origtype == convertto) {
	/* They really just want a copy */
	return(im_Dup_(im, header, FALSE));
	}
    else if (convertTable[origtype][convertto].isPromotion) {
	/* It's a promotion */
	return(im_Promote_(im, header, convertto));
	}
    else {
	/* It's a demotion, or illegal - let imDemote handle it */
	return(im_Demote_(im, header, convertto));
	}
    }

boolean
im_SetComment_(void *im, ImageHeader *header, char *newcomment)
{
    char *newcopy;

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

    if (newcomment != (char *)NULL) {
	newcopy = strdup(newcomment);
	if (newcopy == (char *)NULL) {
	    return(FALSE);
	    }
	}
    else {
	newcopy = (char *)NULL;
	}

    if (header->comment != (char *)NULL) {
	/* Free the old comment */
	free((void *)header->comment);
	}

    header->comment = newcopy;
    return(TRUE);
    }

/*
 * Here are the actual file-reading routines.
 *
 * Each one is passed a FILE * and an ImageHeader **. It knows that the file
 * stream is positioned immediately after a P?M (or similar) magic number,
 * and implicitly what that magic number was (i.e. what form the file will
 * take). It then reads the file, creates an Image of the appropriate type,
 * and returns it as its return value, putting a pointer to its ImageHeader
 * into *readimHeader.
 */

static void *
readBinaryCooked(FILE *inFile, ImageHeader **readimHeader)
{
    unsigned width, height;
    boolean failed;
    BinaryImage im;
    int x, y;

    /*
     * File format:
     * P1 <width> <height>
     * bunches of 0<ws> and 1<ws>, suitable for reading with gettoken.
     */
    width = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    height = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    
    im = (BinaryImage)imNew(IMAGE_BINARY, width, height);
    if (im == (BinaryImage)NULL) {
	return((void *)NULL);
	}
	
    for (y = 0; y < height; y++) {
	for (x = 0; x < width; x++) {
	    /* Can't use gettoken - may not be separating whitespace */
	    imRef(im, x, y) = getpbmtoken(inFile, &failed);
	    if (failed) {
		imFree(im);
		return((void *)NULL);
		}
	    }
	}

    *readimHeader = im->header;
    return(im);
    }

static void *
readGrayCooked(FILE *inFile, ImageHeader **readimHeader)
{
    unsigned width, height;
    unsigned maxval;
    boolean failed;
    GrayImage im;
    int x, y;

    /*
     * File format:
     * P2 <width> <height> <maxval>
     * bunches of <value><ws> where 0 <= <value> <= maxval, suitable for
     * reading with gettoken.
     * If maxval is not 255, we'll have to rescale the values; this will
     * be slow.
     */
    width = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    height = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    maxval = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    
    im = (GrayImage)imNew(IMAGE_GRAY, width, height);
    if (im == (GrayImage)NULL) {
	return((void *)NULL);
	}
	
    if (maxval == COLRNG - 1) {
	/* Special case the nice case */
	for (y = 0; y < height; y++) {
	    for (x = 0; x < width; x++) {
		imRef(im, x, y) = gettoken(inFile, &failed);
		if (failed) {
		    imFree(im);
		    return((void *)NULL);
		    }
		}
	    }
	}
    else {
	/* Have to rescale. */
	for (y = 0; y < height; y++) {
	    for (x = 0; x < width; x++) {
		imRef(im, x, y) =
		    (gettoken(inFile, &failed) * (COLRNG - 1)) / maxval;
		if (failed) {
		    imFree(im);
		    return((void *)NULL);
		    }
		}
	    }
	}

    *readimHeader = im->header;
    return(im);
    }

static void *
readRGBCooked(FILE *inFile, ImageHeader **readimHeader)
{
    unsigned width, height;
    unsigned maxval;
    boolean failed;
    RGBImage im;
    int x, y;

    /*
     * File format:
     * P3 <width> <height> <maxval>
     * bunches of <rvalue><ws><gvalue><ws><bvalue><ws> where
     * 0 <= <rvalue>, <gvalue>, <bvalue> <= maxval, suitable for
     * reading with gettoken.
     * If maxval is not 255, we'll have to rescale the values; this will
     * be slow.
     */
    width = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    height = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    maxval = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    
    im = (RGBImage)imNew(IMAGE_RGB, width, height);
    if (im == (RGBImage)NULL) {
	return((void *)NULL);
	}
	
    if (maxval == COLRNG - 1) {
	/* Special case the nice case */
	for (y = 0; y < height; y++) {
	    for (x = 0; x < width; x++) {
		imRef(im, x, y).r = gettoken(inFile, &failed);
		if (failed) {
		    imFree(im);
		    return((void *)NULL);
		    }

		imRef(im, x, y).g = gettoken(inFile, &failed);
		if (failed) {
		    imFree(im);
		    return((void *)NULL);
		    }

		imRef(im, x, y).b = gettoken(inFile, &failed);
		if (failed) {
		    imFree(im);
		    return((void *)NULL);
		    }
		}
	    }
	}
    else {
	/* Have to rescale. */
	for (y = 0; y < height; y++) {
	    for (x = 0; x < width; x++) {
		imRef(im, x, y).r =
		    (gettoken(inFile, &failed) * (COLRNG - 1)) / maxval;
		if (failed) {
		    imFree(im);
		    return((void *)NULL);
		    }

		imRef(im, x, y).g =
		    (gettoken(inFile, &failed) * (COLRNG - 1)) / maxval;
		if (failed) {
		    imFree(im);
		    return((void *)NULL);
		    }

		imRef(im, x, y).b =
		    (gettoken(inFile, &failed) * (COLRNG - 1)) / maxval;
		if (failed) {
		    imFree(im);
		    return((void *)NULL);
		    }
		}
	    }
	}

    *readimHeader = im->header;
    return(im);
    }

static void *
readBinary(FILE *inFile, ImageHeader **readimHeader)
{
    unsigned width, height;
    boolean failed;
    BinaryImage im;
    int x, y;
    int c, bitsleft;

    /*
     * File format:
     * P4 <width> <height>
     * binary image, packed, high bit is leftmost; rows begin on byte
     * boundaries.
     */
    width = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    height = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    
    im = (BinaryImage)imNew(IMAGE_BINARY, width, height);
    if (im == (BinaryImage)NULL) {
	return((void *)NULL);
	}
	
    c = EOF;
    for (y = 0; y < height; y++) {
	bitsleft = 0;
	for (x = 0; x < width; x++) {
	    if (bitsleft == 0) {
		c = getc(inFile);
		if (c == EOF) {
		    imFree(im);
		    return((void *)NULL);
		    }
		bitsleft = 8;
		}
	    /* High bit first */
	    if (c & (1 << (--bitsleft))) {
		imRef(im, x, y) = 1;
		}
	    /* It's already zero by default */
	    }
	}

    *readimHeader = im->header;
    return(im);
    }

static void *
readGray(FILE *inFile, ImageHeader **readimHeader)
{
    unsigned width, height;
    unsigned maxval;
    boolean failed;
    GrayImage im;
    int x, y;

    /*
     * File format:
     * P5 <width> <height> <maxval>
     * image data in reading order, one byte per pixel.
     * If maxval is not 255, we'll have to rescale the values; this will
     * be slow.
     */
    width = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    height = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    maxval = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    
    im = (GrayImage)imNew(IMAGE_GRAY, width, height);
    if (im == (GrayImage)NULL) {
	return((void *)NULL);
	}
	
    /* Inhale everything. */
    if (fread((char *)(im->data[0]), sizeof(unsigned char),
	      (width * height), inFile) != (width * height)) {
	imFree(im);
	return((void *)NULL);
	}
    if (maxval != COLRNG - 1) {
	/* Have to rescale. */
	for (y = 0; y < height; y++) {
	    for (x = 0; x < width; x++) {
		imRef(im, x, y) =
		    (imRef(im, x, y) * (COLRNG - 1)) / maxval;
		}
	    }
	}

    *readimHeader = im->header;
    return(im);
    }

static void *
readRGB(FILE *inFile, ImageHeader **readimHeader)
{
    unsigned width, height;
    unsigned maxval;
    boolean failed;
    RGBImage im;
    int x, y;

    /*
     * File format:
     * P3 <width> <height> <maxval>
     * image data, three bytes per pixel, in RGB order.
     * If maxval is not 255, we'll have to rescale the values; this will
     * be slow.
     */
    width = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    height = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    maxval = gettoken(inFile, &failed);
    if (failed) {
	return((void *)NULL);
	}
    
    im = (RGBImage)imNew(IMAGE_RGB, width, height);
    if (im == (RGBImage)NULL) {
	return((void *)NULL);
	}
	
    /* Inhale everything. */
    if (fread((char *)(im->data[0]), sizeof(RGB),
	      (width * height), inFile) != (width * height)) {
	imFree(im);
	return((void *)NULL);
	}
    if (maxval != COLRNG - 1) {
	/* Have to rescale. */
	for (y = 0; y < height; y++) {
	    for (x = 0; x < width; x++) {
		imRef(im, x, y).r =
		    (imRef(im, x, y).r * (COLRNG - 1)) / maxval;
		imRef(im, x, y).g =
		    (imRef(im, x, y).g * (COLRNG - 1)) / maxval;
		imRef(im, x, y).b =
		    (imRef(im, x, y).b * (COLRNG - 1)) / maxval;
		}
	    }
	}

    *readimHeader = im->header;
    return(im);
    }

/* Here's a macro that expands out to a function to read a simple file */

#define READFUNC(funcname, imtype, imtag, eltype) \
static void * \
funcname(FILE *inFile, ImageHeader **readimHeader) \
{ \
    unsigned width, height; \
    boolean failed; \
    imtype im; \
    width = gettoken(inFile, &failed); \
    if (failed) { \
	return((void *)NULL); \
	} \
    height = gettoken(inFile, &failed); \
    if (failed) { \
	return((void *)NULL); \
	} \
    im = (imtype)imNew(imtag, width, height); \
    if (im == (imtype)NULL) { \
	return((void *)NULL); \
	} \
    if (fread((char *)(im->data[0]), sizeof(eltype), \
	      (width * height), inFile) != (width * height)) { \
	imFree(im); \
	return((void *)NULL); \
	} \
    *readimHeader = im->header; \
    return(im); \
    }

READFUNC(readFloat, FloatImage, IMAGE_FLOAT, float)

READFUNC(readDouble, DoubleImage, IMAGE_DOUBLE, double)

READFUNC(readLong, LongImage, IMAGE_LONG, long)

READFUNC(readShort, ShortImage, IMAGE_SHORT, short)

#undef	READFUNC

/*
 * Here be dragons.
 *
 * Well, more like an enormous number of conversion routines... which are
 * cooked up out of some fairly horrendous macros.
 */

#define CONVFUNC(funcname, fromimtype, fromimtag, fromptrtype, toimtype, toimtag, toptrtype, assignment) \
static void * \
funcname(void *im) \
{ \
    fromimtype fromim; \
    toimtype toim; \
    fromptrtype fromptr; \
    toptrtype toptr; \
    int x, y; \
    assert(im != (void *)NULL); \
    fromim = (fromimtype)im; \
    toim = (toimtype)imNewOffset(toimtag, \
				 imGetWidth(fromim), imGetHeight(fromim), \
				 imGetXBase(fromim), imGetYBase(fromim)); \
    if (toim == (toimtype)NULL) { \
	return((void *)NULL); \
	} \
    for (y = imGetYBase(fromim); y <= imGetYMax(fromim); y++) { \
	fromptr = imGetPixPtr(fromim, imGetXBase(fromim), y); \
	toptr = imGetPixPtr(toim, imGetXBase(toim), y); \
	for (x = imGetXBase(fromim); x <= imGetXMax(fromim); x++) { \
	    assignment; \
	    imPtrRight(fromim, fromptr); \
	    imPtrRight(toim, toptr); \
	    } \
	} \
    return((void *)toim); \
    }

#define PROMFUNC(funcname, fromimtype, fromimtag, fromptrtype, toimtype, toimtag, toptrtype, assignment1, assignment2) \
static void * \
funcname(void *im) \
{ \
    fromimtype fromim; \
    toimtype toim; \
    fromptrtype fromptr; \
    toptrtype toptr; \
    double slope; \
    double offset; \
    int x, y; \
    assert(im != (void *)NULL); \
    fromim = (fromimtype)im; \
    toim = (toimtype)imNewOffset(toimtag, \
				 imGetWidth(fromim), imGetHeight(fromim), \
				 imGetXBase(fromim), imGetYBase(fromim)); \
    if (toim == (toimtype)NULL) { \
	return((void *)NULL); \
	} \
    slope = convertTable[fromimtag][toimtag].slope; \
    offset = convertTable[fromimtag][toimtag].offset; \
    if ((slope == 1.) && (offset == 0.)) { \
	/* Special case - straight assignment */ \
	for (y = imGetYBase(fromim); y <= imGetYMax(fromim); y++) { \
	    fromptr = imGetPixPtr(fromim, imGetXBase(fromim), y); \
	    toptr = imGetPixPtr(toim, imGetXBase(toim), y); \
	    for (x = imGetXBase(fromim); x <= imGetXMax(fromim); x++) { \
		assignment1; \
		imPtrRight(fromim, fromptr); \
		imPtrRight(toim, toptr); \
		} \
	    } \
	} \
    else { \
	for (y = imGetYBase(fromim); y <= imGetYMax(fromim); y++) { \
	    fromptr = imGetPixPtr(fromim, imGetXBase(fromim), y); \
	    toptr = imGetPixPtr(toim, imGetXBase(toim), y); \
	    for (x = imGetXBase(fromim); x <= imGetXMax(fromim); x++) { \
		assignment2; \
		imPtrRight(fromim, fromptr); \
		imPtrRight(toim, toptr); \
		} \
	    } \
	} \
    return((void *)toim); \
    }

/* A standard promotion function */
#define	STDPROM(cfrom, lfrom, ufrom, cto, lto, uto) \
    PROMFUNC(p##cfrom##2##cto, lfrom##Image, IMAGE_##ufrom, lfrom##PixPtr, \
	     lto##Image, IMAGE_##uto, lto##PixPtr, \
	     imPtrRef(toim, toptr) = imPtrRef(fromim, fromptr), \
	     imPtrRef(toim, toptr) = imPtrRef(fromim, fromptr) * slope + offset)
	     
/* A standard promote-to-RGB function */
#define	STDPROMRGB(cfrom, lfrom, ufrom) \
    PROMFUNC(p##cfrom##2R, lfrom##Image, IMAGE_##ufrom, lfrom##PixPtr, \
	     RGBImage, IMAGE_RGB, RGBPixPtr, \
	     imPtrRef(toim, toptr).r = imPtrRef(toim, toptr).g = imPtrRef(toim, toptr).b = imPtrRef(fromim, fromptr), \
	     imPtrRef(toim, toptr).r = imPtrRef(toim, toptr).g = imPtrRef(toim, toptr).b = imPtrRef(fromim, fromptr) * slope + offset)
	     
/* A standard promote-from-binary function */
#define	STDPROMBIN(cto, lto, uto) \
    PROMFUNC(pB2##cto, BinaryImage, IMAGE_BINARY, BinaryPixPtr, \
	     lto##Image, IMAGE_##uto, lto##PixPtr, \
	     imPtrRef(toim, toptr) = imPtrRef(fromim, fromptr), \
	     imPtrRef(toim, toptr) = (imPtrRef(fromim, fromptr) ? slope : 0) + offset)
	     
/* A standard demotion function: straight assignment */
#define	STDDEM(cfrom, lfrom, ufrom, cto, lto, uto) \
    CONVFUNC(d##cfrom##2##cto, lfrom##Image, IMAGE_##ufrom, lfrom##PixPtr, \
	     lto##Image, IMAGE_##uto, lto##PixPtr, \
	     imPtrRef(toim, toptr) = imPtrRef(fromim, fromptr))

/* A standard demote-to-RGB function: straight assignment */
#define	STDDEMTORGB(cfrom, lfrom, ufrom) \
    CONVFUNC(d##cfrom##2R, lfrom##Image, IMAGE_##ufrom, lfrom##PixPtr, \
	     RGBImage, IMAGE_RGB, RGBPixPtr, \
	     imPtrRef(toim, toptr).r = imPtrRef(toim, toptr).g = imPtrRef(toim, toptr).b = imPtrRef(fromim, fromptr))

/* A standard demote-from-RGB function: combine then assign */
#define	STDDEMFROMRGB(cto, lto, uto) \
    CONVFUNC(dR2##cto, RGBImage, IMAGE_RGB, RGBPixPtr, \
	     lto##Image, IMAGE_##uto, lto##PixPtr, \
	     imPtrRef(toim, toptr) = .299 * imPtrRef(fromim, fromptr).r + .587 * imPtrRef(fromim, fromptr).g + .114 * imPtrRef(fromim, fromptr).b)

/* A demote-to-binary function: result 1 iff original value is 0 */
#define	DEMBINEQ(cfrom, lfrom, ufrom) \
    CONVFUNC(d##cfrom##2B, lfrom##Image, IMAGE_##ufrom, lfrom##PixPtr, \
	     BinaryImage, IMAGE_BINARY, BinaryPixPtr, \
	     imPtrRef(toim, toptr) = (imPtrRef(fromim, fromptr) == 0))

/* A demote-to-binary function: result 1 iff original value is != 0 */
#define	DEMBINNE(cfrom, lfrom, ufrom) \
    CONVFUNC(d##cfrom##2B, lfrom##Image, IMAGE_##ufrom, lfrom##PixPtr, \
	     BinaryImage, IMAGE_BINARY, BinaryPixPtr, \
	     imPtrRef(toim, toptr) = (imPtrRef(fromim, fromptr) != 0))

STDPROM(G, Gray, GRAY, F, Float, FLOAT)
STDPROMRGB(G, Gray, GRAY)
STDPROM(G, Gray, GRAY, D, Double, DOUBLE)
DEMBINEQ(G, Gray, GRAY)
STDPROM(G, Gray, GRAY, L, Long, LONG)
STDPROM(G, Gray, GRAY, S, Short, SHORT)

STDDEM(F, Float, FLOAT, G, Gray, GRAY)
STDDEMTORGB(F, Float, FLOAT)
STDPROM(F, Float, FLOAT, D, Double, DOUBLE)
DEMBINNE(F, Float, FLOAT)
STDDEM(F, Float, FLOAT, L, Long, LONG)
STDDEM(F, Float, FLOAT, S, Short, SHORT)

STDDEMFROMRGB(G, Gray, GRAY)
STDDEMFROMRGB(F, Float, FLOAT)
STDDEMFROMRGB(D, Double, DOUBLE)
CONVFUNC(dR2B, RGBImage, IMAGE_RGB, RGBPixPtr,
	 BinaryImage, IMAGE_BINARY, BinaryPixPtr,
	 imPtrRef(toim, toptr) = ((imPtrRef(fromim, fromptr).r == 0) && (imPtrRef(fromim, fromptr).g == 0) && (imPtrRef(fromim, fromptr).b == 0)))
STDDEMFROMRGB(L, Long, LONG)
STDDEMFROMRGB(S, Short, SHORT)

STDDEM(D, Double, DOUBLE, G, Gray, GRAY)
STDDEM(D, Double, DOUBLE, F, Float, FLOAT)
STDDEMTORGB(D, Double, DOUBLE)
DEMBINNE(D, Double, DOUBLE)
STDDEM(D, Double, DOUBLE, L, Long, LONG)
STDDEM(D, Double, DOUBLE, S, Short, SHORT)

STDPROMBIN(G, Gray, GRAY)
STDPROMBIN(F, Float, FLOAT)
PROMFUNC(pB2R, BinaryImage, IMAGE_BINARY, BinaryPixPtr,
	 RGBImage, IMAGE_RGB, RGBPixPtr,
	 imPtrRef(toim, toptr).r = imPtrRef(toim, toptr).g = imPtrRef(toim, toptr).b = imPtrRef(fromim, fromptr),
	 imPtrRef(toim, toptr).r = imPtrRef(toim, toptr).g = imPtrRef(toim, toptr).b = (imPtrRef(fromim, fromptr) ? slope : 0) + offset)
STDPROMBIN(D, Double, DOUBLE)
STDPROMBIN(L, Long, LONG)
STDPROMBIN(S, Short, SHORT)

STDDEM(L, Long, LONG, G, Gray, GRAY)
STDDEM(L, Long, LONG, F, Float, FLOAT)
STDDEMTORGB(L, Long, LONG)
STDPROM(L, Long, LONG, D, Double, DOUBLE)
DEMBINEQ(L, Long, LONG)
STDDEM(L, Long, LONG, S, Short, SHORT)

STDDEM(S, Short, SHORT, G, Gray, GRAY)
STDPROM(S, Short, SHORT, F, Float, FLOAT)
STDDEMTORGB(S, Short, SHORT)
STDPROM(S, Short, SHORT, D, Double, DOUBLE)
DEMBINEQ(S, Short, SHORT)
STDPROM(S, Short, SHORT, L, Long, LONG)

