#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <ctype.h>
#include <time.h>

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <Obm.h>
#include <ObmW/Gterm.h>
#include "ximtool.h"
#include "iis.h"

/*  Slackware/RedHat4.2 compatibility hack. */
#if defined(linux) && defined(isalnum)
#undef isalnum
#define isalnum(c) (isalpha(c)||isdigit(c))
#endif


#define	DBG_RASTER	0


/*
 * RASTER.C -- Raster pixel (frame buffer) routines.  These are the routines
 * which create and manipulate the frame buffers and the graphics pipeline
 * to the screen.
 *
 *	     xim_initialize (xim, config, nframes, hardreset)
 *		  xim_reset (xim, w)
 *	         xim_resize (xim, w)
 *	        xim_refresh (xim)
 *
 *	       xim_setFrame (xim, frame)
 *    xim_setReferenceFrame (chan, frame)
 *	xim_setDisplayFrame (xim, frame)
 *	      xim_initFrame (xim, frame, nframes, config, memModel)
 *	       xim_delFrame (xim, frame)
 *	    xim_matchFrames (xim, frames, reference_frame)
 *	 xim_registerFrames (xim, frames, reference_frame)
 *	       xim_fitFrame (xim)
 *	     xim_tileFrames (xim, frame_list)
 *
 *	     xim_cursorMode (xim, state)
 *
 *	     xim_setMapping (xim, fb, frame, mapping, src, dst, fill_mode)
 *		xim_setZoom (xim, fb, frame, mapping, src, dst,
 *			     xcen,ycen, xmag,ymag, xoff,yoff, absolute)
 *		xim_setFlip (xim, fb, flip_x, flip_y)
 *	    xim_setColormap (name, dirs, red, green, blue, ncolors)
 *		 xim_setRop (xim, fb, rop)
 *   rop = xim_getAntialias (xim, s)
 *
 *	      xim_getScreen (xim, frame, sx, sy, width, height, depth)
 *      bool = xim_onScreen (xim, frame)
 *
 *         xim_setCursorPos (xim, sx, sy)
 *         xim_getCursorPos (xim, sx, sy, raster, frame)
 *
 * pixels = xim_readDisplay (xim, x0,y0,nx,ny, w,h, r,g,b, ncolors)
 *  stat = xim_writeDisplay (xim, frame, mapname, pixels, w,h, r,g,b, ncolors)
 *
 *		xim_message (xim, object, message)
 *		   xim_msgi (xim, object, intval)
 *		  xim_alert (xim, text, ok_action, cancel_action)
 */

/* Define some builtin colormaps. */
static Lut aips0 = {
#include "data/aips0.lut"
};
static Lut blue = {
#include "data/blue.lut"
};
static Lut color = {
#include "data/color.lut"
};
static Lut green = {
#include "data/green.lut"
};
static Lut halley = {
#include "data/halley.lut"
};
static Lut heat = {
#include "data/heat.lut"
};
static Lut rainbow = {
#include "data/rainbow.lut"
};
static Lut red = {
#include "data/red.lut"
};
static Lut staircase = {
#include "data/staircase.lut"
};
static Lut standard = {
#include "data/standard.lut"
};

static void get_fbconfig();
static void set_colorbar();
static int get_dirfile();
static void load_testpattern();
static void set_nframes();
static void xim_frameRegion();
static void xim_colortables();
static int xim_onScreen();
static void xim_highlightFrame();
static void hsv_to_rgb();

void xim_labelTiles();
void xim_getScreen();

#define	TOL	0.0001


/* XIM_INITIALIZE -- Initialize the imaging subsystem.  Read the config file
 * and create the frame buffers, mappings, and colormaps.
 */
void
xim_initialize (xim, config, nframes, hardreset)
register XimDataPtr xim;
int config;
int nframes;
int hardreset;
{
	register Widget gt = xim->gt;
	register FrameBufPtr fb;
	register int i;

	unsigned short m_red[MAX_COLORS];
	unsigned short m_green[MAX_COLORS];
	unsigned short m_blue[MAX_COLORS];
	char *fname, *ip, *op, sbuf[8192];
	String maps[MAX_COLORMAPS], dirs[4];
	int max_cmaps, first, ngray, rgb_len, nfiles, n;
	static int nbuiltin_cmaps = 0;
	int display_frame = 1;
	char cmapname[SZ_NAME];
	char buf[SZ_LINE];
	FbConfigPtr cf;
	ColorMapPtr cm;
	int startup;
	DIR *dir;

	/* The gterm widget handle should already have been set by the
	 * GUI during startup.
	 */
	if (!gt) {
	    fprintf (stderr, "xim_initialize:  no gterm-image widget!!\n");
	    exit (1);
	}

	/* Inform the GUI that the frame buffers are being initialized.
	 * The initialize parameter is set to "startup" during program
	 * startup, "restart" at the start of an initialization during
	 * execution, and "done" when initialization is completed.
	 */
	startup = (xim->nframes == 0);
	xim_message (xim, "initialize", startup ? "startup" : "restart");

	if (hardreset || startup || config != xim->fb_configno) {
	    xim_msgi (xim, "frame", 0);
	    xim_message (xim, "frameTitle", "");
	    set_nframes (xim, 0);
	    cm = &colormaps[DEF_COLORMAP-1];
	    strcpy (cmapname, cm->name);
	    hardreset = 1;

	    /* Get frame buffer configurations. */
	    get_fbconfig (xim);

	    /* Remove any existing border highlight marker. */
	    if (xim->gm_border) {
		GmDestroy (xim->gm_border);
		xim->gm_border = NULL;
	    }

	    /* Initialize the frame buffers. */
	    if (DBG_RASTER)
		fprintf (stderr, "xim_initialize: Raster init.....\n");
	    GtRasterInit (gt);
	    if (DBG_RASTER)
		fprintf (stderr, "xim_initialize: Raster init.....DONE\n");
	    for (i=1;  i <= MAX_FRAMES;  i++)
		xim->frames[i].frameno = 0;

	    cf = &xim->fb_config[config-1];
	    nframes = (nframes <= 0) ? cf->nframes : nframes;

	    if (startup) {
		/* Set initial tile frames list to be all frames if starting
		 * up in tile frames mode.
		 */
		if (xim->tileFrames && nframes > 1) {
		    xim->tileFramesList = 0;
		    xim->nTileFrames = 0;
		    for (i=0;  i < nframes;  i++) {
			xim->tileFramesList |= (1 << i);
			xim->nTileFrames++;
		    }
		}
	    } else {
		xim->display_frame = 0;
		xim->df_p = NULL;
		for (i=0;  i < XtNumber(xim->chan);  i++) {
		    IoChanPtr chan = &xim->chan[i];
		    if (chan->type)
			xim_setReferenceFrame (chan, 1);
		}
		if (xim->cursor_chan)
		    xim->cursor_chan = &xim->chan[0];
	    }

	    xim->rop = xim->antialias ?
		xim_getAntialias (xim, xim->antialiasType) : 0x00;
	    xim->fb_configno = config = max(1, min(MAX_FBCONFIG, config));
	    if (DBG_RASTER)
		fprintf (stderr,
		    "xim_initialize: ........................SETNFRAME\n");
	    set_nframes (xim, nframes);
	    if (DBG_RASTER)
		fprintf (stderr,
		    "xim_initialize: ........................SETNFRAME DONE\n");

	    /* Initialize the tile framing options. */
	    xim->tileByRows = 1;
	    xim->tileTopDown = 1;
	    xim->tileLabels = 0;

	    /* Set the new frame size. */
	    sprintf (buf, "%d %d %d", cf->width, cf->height, 8);
	    xim_message (xim, "frameSize", buf);

	    /* Create the frames. */
	    for (i=1;  i <= nframes;  i++) {
		if (DBG_RASTER)
		    fprintf (stderr,
			"xim_initialize: ...............INITFRAME %d\n",i);
		xim_initFrame (xim, i, nframes, cf, xim->memModel);
		if (DBG_RASTER)
		    fprintf (stderr,
			"xim_initialize: ...............INITFRAME %d DONE\n",i);
	    }

            /* Reinitialize the tile framing. */
            if (xim->tileFrames) {
                xim->tileFramesList = 0;
                xim->nTileFrames = 0;
                for (i=0;  i < nframes;  i++) {
                    xim->tileFramesList |= (1 << i);
                    xim->nTileFrames++;
                }
            }

	    xim->width = cf->width;
	    xim->height = cf->height;
	    GtSetLogRes (gt, cf->width, cf->height);

	} else {
	    /* Soft reset. */
	    display_frame = xim->display_frame;
	    fb = &xim->frames[display_frame-1];
	    cm = &colormaps[fb->colormap-1];
	    strcpy (cmapname, cm->name);

	    for (i=0;  i < ncolormaps;  i++) {
		cm = &colormaps[i];
		GtFreeColormap (gt, cm->mapno);
	    }
	}

	if (DBG_RASTER)
	    fprintf (stderr,
		"xim_initialize: ........................CMAP SETUP\n");
	/* Set up the colormap to emulate the IIS/imtool colormap.  Set
	 * xim->ncolors to the length of the grayscale region of the colormap
	 * returned by iiscolormap so that our other colormaps will have the
	 * same size.
	 */
	if (DBG_RASTER)
	    fprintf (stderr,
		"xim_initialize: xim->ncolors = %d\n", xim->ncolors);
	xim_iiscolormap (gt, m_red,m_green,m_blue, &first, &ngray, &rgb_len);
	if (DBG_RASTER)
	    fprintf (stderr,
		"xim_initialize: writeColormap:  first=%d ngray=%d len=%d\n",
		first, ngray, rgb_len);
	GtWriteColormap (gt, 0, first, rgb_len, m_red, m_green, m_blue);
	if ((xim->ncolors = ngray) < 0) {
	    fprintf (stderr, "ERROR: No colormap cells available.\n");
	    exit (1);
	}


	/* Initialize the color tables.
	*/
	max_cmaps = nbuiltin_cmaps ? nbuiltin_cmaps : MAX_COLORMAPS;
	if (DBG_RASTER)
	    fprintf (stderr,
		"xim_initialize: init colortables:  xim->ncolors = %d\n",
		xim->ncolors);
	for (ncolormaps=0;  ncolormaps < max_cmaps;  ncolormaps++) {
	    cm = &colormaps[ncolormaps];
	    if (!cm->name[0])
		break;
	    cm->mapno = GtNextColormap (gt);
	    xim_setColormap (cm->name, NULL, m_red, m_green, m_blue,
		xim->ncolors);
	    GtWriteColormap (gt, cm->mapno, first, xim->ncolors,
		m_red, m_green, m_blue);
	}
	if (DBG_RASTER)
	    fprintf (stderr, "xim_initialize: init colortables:  DONE\n");

	/* The first time this is called count the number of builtin cmaps. */
	if (nbuiltin_cmaps == 0)
	    nbuiltin_cmaps = ncolormaps;

	/* Get the names of the two user defined colormaps, if any. */
	nfiles = 0;
	maps[nfiles++] = xim->userCMap1;
	maps[nfiles++] = xim->userCMap2;

	/* If any user colormap directories are specified get the names
	 * of the colormap files in these directories.
	 */
	dirs[0] = "./";
	dirs[1] = xim->userCMapDir1;
	dirs[2] = xim->userCMapDir2;
	dirs[3] = NULL;

	for (i=1, op=sbuf;  dirs[i];  i++) {
	    if (strcmp (dirs[i], "none") != 0) {
		if ((dir = opendir (dirs[i]))) {
		    while ((n = get_dirfile (dir, op, SZ_FNAME)) > 0) {
			maps[nfiles++] = op;
			op += n + 1;
			if (nfiles >= MAX_COLORMAPS)
			    break;
		    }
		    closedir (dir);
		}
	    }
	}

	/* Add all the user specified colormaps to the colormap table.
	 */
	for (i=0;  i < nfiles && ncolormaps < MAX_COLORMAPS;  i++) {
	    fname = maps[i];
	    if (strcmp (fname, "none") != 0) {
		if (xim_setColormap (fname, dirs,
		    m_red, m_green, m_blue, xim->ncolors) == OK) {

		    cm = &colormaps[ncolormaps++];
		    cm->mapno = GtNextColormap (gt);

		    /* Use root portion of filename as the colormap name. */
		    for (ip=fname;  *ip;  ip++)
			if (*ip == '/')
			    fname = ip + 1;
		    for (ip=fname, op=cm->name;  *ip && *ip != '.';  ip++) {
			*op++ = *ip;
			if (op - cm->name >= SZ_CMAPNAME)
			    break;
		    }
		    *op = '\0';

		    GtWriteColormap (gt, cm->mapno, first, xim->ncolors,
			m_red, m_green, m_blue);
		}
	    }
	}

	/* Pass the list of colortables on to the user interface. */
	xim_colortables (xim);

	/* Set initial display frame. */
	xim_setDisplayFrame (xim, display_frame);

	/* Force the initial default colormap to be loaded. */
	fb = &xim->frames[display_frame-1];
	for (i=0;  i < ncolormaps;  i++) {
	    cm = &colormaps[i];
	    if (strcmp (cm->name, cmapname) == 0)
		break;
	}
	if (i >= ncolormaps)
	    cm = &colormaps[DEF_COLORMAP-1];

	GtLoadColormap (gt, cm->mapno, fb->offset, fb->scale);
	xim_enhancement (xim, fb);

	/* Set options. */
/*	xim_message (xim, "cmfocus", xim->cm_focus ? "True" : "False");*/
	xim_message (xim, "autoscale", xim->autoscale ? "True" : "False");
	xim_message (xim, "antialias", xim->antialias ? "True" : "False");
	xim_message (xim, "tileFrames", xim->tileFrames ? "True" : "False");

	xim_message (xim, "initialize", "done");

	if (DBG_RASTER)
	    fprintf (stderr,
		"xim_initialize: ................................RETURNING\n");
}


/* XIM_RESET -- Called when the active gterm widget is set or changed.
 */
void
xim_reset (xim, w)
XimDataPtr xim;
Widget w;
{
	unsigned short iomap[MAX_COLORS];
	int iomap_len;

	if (!w)
	    return;

	/* The following assumes that the GUI does a setGterm first for the
	 * colorbar and second for the main image window.  The first thing we
	 * do is set the iomap so that all ximtool color and pixel i/o will
	 * see the emulated IIS color model.
	 */
	if (xim->cb == NULL) {
	    xim->cb = w;
	    xim_iisiomap (w, iomap, &iomap_len);
	    GtPostResizeProc (w, set_colorbar, xim);
	    GtWriteIomap (w, iomap, 0, iomap_len);
	} else {
	    xim->gt = w;
	    xim_iisiomap (w, iomap, &iomap_len);
	    GtPostResizeProc (w, xim_resize, xim);
	    GtWriteIomap (w, iomap, 0, iomap_len);
	}
}


/* XIM_RESIZE -- Called when the active gterm widget is resized.  We need to
 * resize the zoom raster and modify the zoom mapping for each frame to
 * reflect the new window size.
 */
void
xim_resize (xim, w)
XimDataPtr xim;
Widget w;
{
	register FrameBufPtr fb;
	int junk, sx, sy, width, height, depth;
	int i, active, frame, mapping, zoomtype;
	float xscale, yscale, scale;
	char buf[SZ_LINE];


	/* This case is true during startup. */
	if (xim->nframes <= 0)
	    return;

	/* Get new screen size.  In tile frame mode select the first frame
	 * in the list for the size, which may not always be frame 1.
	 */
	if (xim->tileFrames && !(xim->tileFramesList & 1)) {
	    if (xim->nframes == 0)
		return;
	    for (i=1;  i <= xim->nframes;  i++)
                if (xim->tileFramesList & (1 << (i-1))) {
	    	    xim_getScreen (xim, i, &sx, &sy, &width, &height, &depth);
		    break;
		}
	} else
	    xim_getScreen (xim, 1, &sx, &sy, &width, &height, &depth);

	/* Compute the new scale factor required to scale the source to the
	 * destination at magnification 1.0.
	 */
	if (xim->autoscale) {
	    xscale = (float)width / (float)xim->width;
	    yscale = (float)height / (float)xim->height;
	    scale = min (xscale, yscale);
	} else {
	    xscale = 1.0;
	    yscale = 1.0;
	    scale = 1.0;
	}

	/* Adjust the mapping for each frame. */
	for (frame=1;  frame <= xim->nframes;  frame++) {
	    fb = &xim->frames[frame-1];
	    mapping = fb->zoommap;

	    /* For rapid frame blink, split screen, etc. the zoom raster
	     * must be the same size as the screen so we must resize this
	     * raster when the display window is resized.  The CreateRaster
	     * call will delete the old raster and all its mappings and
	     * create a new one.  We must then restore the mappings, and
	     * if this is an active display frame, refresh the mapping.
	     */
	    active = xim_onScreen (xim, frame);

	    if (fb->zoomras) {
		GtQueryRaster (w, fb->zoomras, &zoomtype, &junk, &junk, &junk);
		GtCreateRaster (w, fb->zoomras, zoomtype, width, height, depth);
		xim_setMapping (xim, NULL, frame, fb->dispmap,
		    fb->zoomras, 0, M_FILL);
		if (!active) {
		    GtDisableMapping (w, fb->dispmap, 0);
		    xim_setMapping (xim, NULL, frame, fb->zoommap,
			fb->raster, fb->zoomras, M_FILL);
		    GtDisableMapping (w, fb->zoommap, 0);
		}
	    } else {
		if (active) {
		    GtEnableMapping (w, fb->dispmap, 0);
		    GtSetDisplayRaster (w, xim->display_frame);
		} else
		    GtDisableMapping (w, fb->dispmap, 0);
	    }

	    /* Set the new mapping. */
	    fb->xscale = fb->yscale = scale;
	    xim_setZoom (xim, fb, frame, mapping, fb->raster, fb->zoomras,
		fb->xcen, fb->ycen, fb->xmag, fb->ymag, 
		fb->xoff, fb->yoff, False);
	}

	/* Refresh the screen for any active mappings.  Any mappings
	 * defined on any visible frame should be refreshed, since a
	 * redraw clears the display window.
	 */
	for (frame=1;  frame <= xim->nframes;  frame++) {
	    if (xim_onScreen (xim, frame)) {
		int junk, width, height, depth;
		fb = &xim->frames[frame-1];
		GtQueryRaster (w, fb->raster, &junk, &width, &height, &depth);
		GtRefreshPixels (w, fb->raster, GtPixel, 0, 0, width, height);
	    }
	}

	/* Highlight the current frame. */
	xim_highlightFrame (xim, xim->display_frame);

	sprintf (buf, "%d %d %d %d", sx, sy, width, height);
	xim_message (xim, "resize", buf);
}


/* XIM_REFRESH -- Refresh the current display frame.
 */
void
xim_refresh (xim)
XimDataPtr xim;
{
	register FrameBufPtr fb = xim->df_p;
	int junk, width, height, depth;

	GtQueryRaster (xim->gt, fb->raster, &junk, &width, &height, &depth);
	GtRefreshPixels (xim->gt, fb->raster, GtPixel, 0, 0, width, height);
}


/* XIM_CLOSE -- Free any raster specific resources.
 */
void
xim_close (xim)
register XimDataPtr xim;
{
}


/* XIM_SETFRAME -- Configure a frame to be both the display and reference
 * frame.
 */
void
xim_setFrame (xim, frame)
register XimDataPtr xim;
int frame;
{
	xim_setDisplayFrame (xim, frame);
}


/* XIM_SETREFERENCEFRAME -- Set the frame used for frame buffer i/o.
 */
void
xim_setReferenceFrame (chan, frame)
register IoChanPtr chan;
int frame;
{
	register XimDataPtr xim = (XimDataPtr) chan->xim;
	register FrameBufPtr fb;
	int frameno;

	/* Ignore request if channel not active. */
	if (!chan->type)
	    return;

	frameno = max(1, min(MAX_FRAMES, frame));
	fb = &xim->frames[frameno-1];

	/* Ignore request if not a valid frame. */
	if (fb->frameno > 0) {
	    chan->reference_frame = frameno;
	    chan->rf_p = fb;
	}
}


/* XIM_SETDISPLAYFRAME -- Set the frame which is displayed.
 */
void
xim_setDisplayFrame (xim, frame)
register XimDataPtr xim;
int frame;
{
	register FrameBufPtr fb;
	register Widget gt = xim->gt;
	FrameBufPtr old_fb = xim->df_p;
	int frameno, old_frameno;
	char buf[256];

	old_frameno = old_fb ? old_fb->frameno : 0;
	frameno = max(1, min(MAX_FRAMES, frame));
	fb = &xim->frames[frameno-1];

	/* Ignore request if not a valid frame. */
	if (fb->frameno > 0 && (!old_fb || frameno != old_frameno)) {
	    /* Special case: if tile frame mode is in effect and we are
	     * displaying a frame not in the tile list, clear the screen
	     * before displaying the frame.  Likewise, if displaying an
	     * untiled frame while in tile frame mode and then reverting
	     * to display of a tiled frame, clear the screen and display
	     * all the tiled frames.
	     */
	    if (xim->tileFrames && old_fb) {
		int tile_old = (xim->tileFramesList & (1 << old_frameno-1));
		int tile_new = (xim->tileFramesList & (1 <<     frameno-1));

		if (tile_old && !tile_new)
		    GtClearScreen (gt);
		else if (!tile_old && tile_new) {
		    GtClearScreen (gt);
/*		    xim_resize (xim, gt);*/
		}
	    }

	    /* Map the new frame. */
	    xim->df_p = fb;
	    xim->display_frame = frameno;
	    GtEnableMapping (gt, fb->dispmap, 0);

	    /* Unmap the old frame. */
	    if (old_fb && !xim_onScreen (xim, old_frameno)) {
		FrameBufPtr old_fb = &xim->frames[old_frameno-1];
		GtDisableMapping (gt, old_fb->dispmap, 0);
	    }

	    /* Display the new frame. */
	    if (GtActiveMapping (gt, fb->zoommap)) {
		GtRefreshMapping (gt, fb->dispmap);
	    } else {
		GtEnableMapping (gt, fb->zoommap, 0);
		GtRefreshMapping (gt, fb->zoommap);
	    }
	    GtSetDisplayRaster (gt, frameno);


	    /* Highlight the new frame if in tileFrames mode. */
	    xim_highlightFrame (xim, frameno);

	    /* Load the colormap only if it differs from what we already
	     * have.  This avoids the waste of repeatedly reloading the
	     * colormap when blinking frames that have matched colormaps.
	     */
	    if (!old_fb || fb->colormap != old_fb->colormap ||
		abs (fb->offset - old_fb->offset) > 0.001 ||
		abs (fb->scale - old_fb->scale) > 0.001) {

	    	GtSetColormapFocus (-1);	/* force full update	*/
		GtLoadColormap (gt, fb->colormap, fb->offset, fb->scale);
	    	GtSetColormapFocus (xim->cm_focus);
	    }

	    xim_msgi (xim, "frame", frameno);
	    xim_message (xim, "frameTitle", fb->ctran.imtitle);
	    xim_message (xim, "xflip", fb->xflip ? "true" : "false");
	    xim_message (xim, "yflip", fb->yflip ? "true" : "false");
	    xim_frameRegion (xim, fb);

	    sprintf (buf, "%g %g %g %g %g %g %g %g",
		fb->xmag, fb->ymag, fb->xcen, fb->ycen, 
		fb->xscale, fb->yscale, fb->xoff, fb->yoff);
	    xim_message (xim, "frameView", buf);
	}
}


/* XIM_INITFRAME -- Initialize a frame buffer.
 */
void
xim_initFrame (xim, frame, nframes, config, memModel)
register XimDataPtr xim;
int frame, nframes;
FbConfigPtr config;
char *memModel;
{
	register FrameBufPtr fb = &xim->frames[frame-1];
	register Widget gt = xim->gt;
	int sx, sy, width, height, depth;

	if (frame < 1 || frame > MAX_FRAMES)
	    return;

	xim_delFrame (xim, frame);
	memset ((void *)fb, 0, sizeof(FrameBuf));

	/* Create the frame buffer. */
	fb->frameno = frame;
	fb->raster = GtNextRaster (gt);

	if (DBG_RASTER) {
	    fprintf (stderr, 
  		"xim_initFrame: Creating CLIENT raster for frame=%d raster=%d (%dx%dx%d)\n",
  		frame, fb->raster, 
  		config->width, config->height, DEF_FRAME_DEPTH);
	    fprintf (stderr, 
  		"xim_initFrame: Creating memModel=%s frame=%d raster=%d  (%d x %d)\n", 
  		memModel, frame, fb->raster, config->width, config->height);
	}

	if (GtCreateRaster (gt, fb->raster, GtClient,
		config->width, config->height, DEF_FRAME_DEPTH) < 0) {

	    	    fprintf (stderr, "cannot create %dx%d frame buffer #%d\n",
			config->width, config->height, frame);
	    return;
	}

	/* Get region of screen to be written into. */
	xim_getScreen (xim, frame, &sx, &sy, &width, &height, &depth);

	if (DBG_RASTER)
	    fprintf (stderr,
		"xim_initFrame: [%d of %d] screen size -- %d x %d x %d\n",
		frame, nframes, width, height, depth);

	/* Set up the graphics pipeline to the screen.  The "memory model"
	 * determines memory is used, trading off memory usage against speed
	 * for operations like zoom/pan and blink.
	 */

	if (strcmp (memModel, "fast") == 0) {
	    /* Use both client and server memory to achieve the best
	     * possible performance.  The frame buffer raster (type ximage)
	     * is mapped to a server pixmap with the zoom/pan mapping, and
	     * the server pixmap is mapped to the screen with the one-to-one
	     * frame display mapping.
	     */
fast:	    fb->zoomras = GtNextRaster (gt);
	    if (GtCreateRaster (gt,
		    fb->zoomras, GtServer, width, height, depth) < 0)
		goto nice;

	    xim_setMapping (xim, fb, frame, fb->zoommap = GtNextMapping(gt),
		fb->raster, fb->zoomras, xim->autoscale ? M_ASPECT : M_UNITARY);
	    xim_setMapping (xim, fb, frame, fb->dispmap = GtNextMapping(gt),
		fb->zoomras, 0, M_FILL);

	} else if (strcmp (memModel, "beNiceToServer") == 0) {
	    /* Minimize use of X server memory, but keep zoom raster to speed
	     * up blinks.  Everything is the same as with the "fast" memory
	     * model except that the zoom raster is an ximage rather than
	     * server pixmap raster.
	     */
nice: 	    fb->zoomras = GtNextRaster (gt);
	    if (DBG_RASTER)
		fprintf (stderr,
		    "xim_initFrame: creating 'nice' model (0x%x)\n", 
		    fb->zoomras);
	    if (GtCreateRaster (gt,
		    fb->zoomras, GtClient, width, height, depth) < 0)
		goto small;

	    xim_setMapping (xim, fb, frame, fb->zoommap = GtNextMapping(gt),
		fb->raster, fb->zoomras, xim->autoscale ? M_ASPECT : M_UNITARY);
	    xim_setMapping (xim, fb, frame, fb->dispmap = GtNextMapping(gt),
		fb->zoomras, 0, M_FILL);

	} else if (strcmp (memModel, "small") == 0) {
	    /* Minimize both client and server memory as far as possible.
	     * Only the frame buffer raster is used, the zoom/pan mapping
	     * maps directly to the screen and is also used as the screen
	     * mapping.
	     *
	     * FIXME:  On TrueColor visuals we can have an issue here where
	     * the display pixmap is 24-bits and various things (e.g. pixel
	     * readback) are expecting 8-bits.
	     */
small:	    fb->zoomras = 0;
	    if (DBG_RASTER)
		fprintf (stderr,
		    "xim_initFrame: creating 'small' model (0x%x)\n", 
		    fb->zoomras);

	    xim_setMapping (xim, fb, frame, fb->zoommap = GtNextMapping(gt),
		fb->raster, fb->zoomras, xim->autoscale ? M_ASPECT : M_UNITARY);
	    fb->dispmap = fb->zoommap;

	} else {
	    fprintf (stderr,
		"unrecognized memModel `%s', default to `fast'\n", memModel);
	    goto fast;
	}

	/* Set up the default colormap. */
	fb->colormap = DEF_COLORMAP;
	fb->offset = 0.5;
	fb->scale = (xim->invert ? -1.0 : 1.0);
	xim_enhancement (xim, fb);

	/* Disable frame display initially if not mapped to screen. */
	if (xim_onScreen (xim, frame)) {
	    GtEnableMapping (gt, fb->dispmap, 0);
	    GtSetDisplayRaster (gt, frame);
	} else
	    GtDisableMapping (gt, fb->dispmap, 0);

	set_nframes (xim, max (xim->nframes, frame));
}


/* XIM_DELFRAME -- Delete a frame.
 */
void
xim_delFrame (xim, frame)
register XimDataPtr xim;
int frame;
{
	register FrameBufPtr fb = &xim->frames[frame-1];
	register Widget gt = xim->gt;

	if (frame < 1 || frame > MAX_FRAMES)
	    return;
	if (fb->frameno <= 0)
	    return;

	GtFreeMapping (gt, fb->dispmap);
	if (fb->zoommap != fb->dispmap)
	    GtFreeMapping (gt, fb->zoommap);
	if (fb->zoomras)
	    GtDestroyRaster (gt, fb->zoomras);
	if (fb->raster)
	    GtDestroyRaster (gt, fb->raster);

	fb->frameno = 0;
	if (xim->nframes == frame)
	    set_nframes (xim, frame - 1);
}


/* XIM_ERASEFRAME -- Erase a frame.
 */
void
xim_eraseFrame (xim, frame)
register XimDataPtr xim;
int frame;
{
	FrameBufPtr fb = &xim->frames[frame-1];
	Widget gt = xim->gt;
	int    Z = 0;

	GtSetPixels (gt, fb->raster, GtPixel, Z,Z,Z,Z, CMS_BACKGROUND, 0);
}


/* XIM_FITFRAME -- Attempt to resize the display window to be the same
 * size as the frame buffer.
 */
void
xim_fitFrame (xim)
register XimDataPtr xim;
{
	register Widget gt = xim->gt;

	GtCreateRaster (gt, 0, GtServer, xim->width, xim->height, 8);
}


/* XIM_TILEFRAMES -- Set or clear tile frame mode.
 */
void
xim_tileFrames (xim, frame_list)
register XimDataPtr xim;
int frame_list;
{
	register int i;
	register Widget w = xim->gt;
	register FrameBufPtr fb;
	char buf[SZ_LINE];
	int mapping;


	/* Get list of frames to be tiled. */
	xim->tileFramesList = frame_list;
	for (i=0, xim->nTileFrames = 0;  i < xim->nframes;  i++)
	    if (frame_list & (1 << i))
		xim->nTileFrames++;

	/* Remove any existing border highlight marker. */
	if (xim->gm_border) {
	    GmDestroy (xim->gm_border);
	    xim->gm_border = NULL;
	}

	xim->tileFrames = (frame_list != 0);
	GtClearScreen (w);
	initialize_shadow_pixmap (w, 0);
	xim_resize (xim, w);

	/* Entering tile frame mode.
	 */
	if (xim->tileFrames) {
			
	    /* Ensure that the display frame is set to one of the
	     * tiled frames.
	     */
	    if (!(xim->tileFramesList & (1 << xim->display_frame-1))) {
		for (i=1;  i <= xim->nframes;  i++)
		    if (xim->tileFramesList & (1 << (i-1))) {
			xim->df_p = NULL;
			xim->display_frame = 0;
			xim_setFrame (xim, i);
			break;
		    }
	    }


	    /* Change the stacking order of the frame mappings if
	     * necessary so that any frames not in the tile list are 
	     * displayed above the tiled frames.  If this is not
	     * done the untiled frames may be obscured by the tiled
	     * frames.
	     */
	    for (i=0, mapping=0;  i < xim->nframes;  i++)
		if (xim->tileFramesList & (1 << i)) {
		    fb = &xim->frames[i];
		    if (!mapping)
			mapping = fb->dispmap;
		    else if (GtCompareMappings (w, mapping, fb->dispmap) < 0)
			mapping = fb->dispmap;
		}
	    for (i=0;  i < xim->nframes;  i++)
		if (!(xim->tileFramesList & (1 << i))) {
		    fb = &xim->frames[i];
		    GtRaiseMapping (w, fb->dispmap, mapping);
		}
	}

	/* Highlight the current frame. */
	xim_highlightFrame (xim, xim->display_frame);

	sprintf (buf, "%s", xim->tileFrames ? "True" : "False");
	xim_message (xim, "tileFrames", buf);
}


/* XIM_HIGHLIGHTFRAME -- If in tile frames mode, highlight the current frame
 * by coloring the border of the frame.
 */
static void
xim_highlightFrame (xim, frame)
register XimDataPtr xim;
int frame;
{
	/* If we are tiling frames highlight the new display frame. */
	if (xim->gm_border) {
	    GmDestroy (xim->gm_border);
	    xim->gm_border = NULL;
	}

	if (xim->tileFrames && xim->highlightFrames) {
	    int sx, sy, width, height, depth;
	    XtPointer gm;
	    Arg args[20];
	    int nargs = 0;

	    xim_getScreen (xim, frame, &sx, &sy, &width, &height, &depth);
	    if (sx > 0 && sy > 0) {
		gm = GmCreate (xim->gt, Gm_Box, False);

		XtSetArg (args[nargs], GmX, sx + (width-1)/2);        nargs++;
		XtSetArg (args[nargs], GmY, sy + (height-1)/2);       nargs++;
		XtSetArg (args[nargs], GmWidth, (width-1)/2+1);       nargs++;
		XtSetArg (args[nargs], GmHeight, (height-1)/2+1);     nargs++;
		XtSetArg (args[nargs], GmLineWidth, xim->tileBorder); nargs++;
		XtSetArg (args[nargs], GmSensitive, False);           nargs++;
		XtSetArg (args[nargs], GmVisible, True);              nargs++;
		XtSetArg (args[nargs], GmActivated, True);            nargs++;

		GmLower (gm, NULL);
		GmSetAttribute (gm, GmLineColor, xim->borderColor, XtRString);
		GmSetAttributes (gm, args, nargs, XtRInt);

		xim->gm_border = gm;
	    }
	    xim_labelTiles (xim);
	}
}


/* XIM_LABELTILES -- Label the tile with the frame number.
 */
void
xim_labelTiles (xim)
register XimDataPtr xim;
{
	FrameBufPtr  fb;
	MappingPtr   mp;
	XtPointer    gm;
	Arg 	     args[10];
	char	     text[256], tw[16];
	register int i, j, len;
	int 	     sx, sy, width, height, depth, nargs=0;

	static XtPointer labels[MAX_FRAMES];
	static int label_init = 1;
	    
	if (!(xim->tileFrames && xim->tileLabels > 0))
	    return;

	/* Initialize the label markers. */
	if (label_init) {
	    /* First time through make sure we're all NULL.  */
	    for (i=0;  i < MAX_FRAMES; i++)  
		labels[i] = (XtPointer) NULL;
	    label_init = 0;
	} else {
	    /* Free up the existing labels.  */
	    for (i=0;  labels[i] && i < MAX_FRAMES; i++) {
	    	GmDestroy (labels[i]);
	    	labels[i] = (XtPointer) NULL;
	    }
	}


	for (i=0;  i < xim->nframes;  i++) {
	    if (xim->tileFramesList & (1 << i)) {
		xim_getScreen (xim, i+1, &sx, &sy, &width, &height, &depth);
		fb = &xim->frames[i];
		switch (xim->tileLabels) {
		case 1: 				/* frame number */
		    sprintf (text, " %d ", i+1);
		    break;
		case 2: 				/* image name 	*/
		    if (fb->nmaps == 0) {
			strcpy (text, " Blank ");
		    } else {
		        mp = &fb->mapping[0];
		        len = strlen (mp->ref);
		        for (j=len-1; mp->ref[j] != '/' && j > 0; j--) ;
		        sprintf (text, " %s ", &mp->ref[++j]);
		    }
		    break;
		case 3: 				/* image title	*/
		    if (fb->nmaps == 0) {
			strcpy (text, " Blank ");
		    } else {
		        len = strlen (fb->ctran.imtitle);
		        for (j=0; fb->ctran.imtitle[j] != ' ' && j < len; j++) ;
		        j += 3;
		        sprintf (text, " %s ", &fb->ctran.imtitle[j]);
		    }
		    break;
		}


		/* Now draw the label as a text marker on the tile.  We use
		 * markers since they can be placed as needed in the frame
		 * and provide a background which lets them be read despite
		 * whatever image scaling is in place.
		 */
		gm = GmCreate (xim->gt, Gm_Text, False);

		nargs = 0;			/* initialize		*/
		len = strlen (text);
		sprintf (tw, "%dch", len);

		XtSetArg (args[nargs], GmX, sx + 10);            nargs++;
		XtSetArg (args[nargs], GmY, sy + height - 20);   nargs++;
		XtSetArg (args[nargs], GmLineWidth, 0);          nargs++;
		XtSetArg (args[nargs], GmSensitive, True);       nargs++;
		XtSetArg (args[nargs], GmVisible, True);         nargs++;
		XtSetArg (args[nargs], GmActivated, True);       nargs++;
		XtSetArg (args[nargs], GmImageText, True);       nargs++;

		GmSetAttribute (gm, GmWidth, tw, XtRString);
		GmSetAttribute (gm, GmHeight, "1ch", XtRString);
		GmSetAttribute (gm, GmTextBgColor, "black", XtRString);
		GmSetAttribute (gm, GmTextColor, "yellow", XtRString);
		GmSetAttribute (gm, GmText, text, XtRString);
		GmSetAttributes (gm, args, nargs, XtRInt);
        	GmMarkpos (gm);
        	GmRedraw (gm, GXcopy, True);

		labels[i] = gm;			/* save the marker ptr 	*/
	    }
	}
}


/* XIM_MATCHFRAMES -- Make the color enhancement of the listed frames match
 * that of the indicated frame.
 */
void
xim_matchFrames (xim, frames, reference_frame)
XimDataPtr xim;
int *frames;
int reference_frame;
{
	register FrameBufPtr fr, fb = &xim->frames[reference_frame-1];
	register int *ip, i;
	int bits;

	/* If frames is NULL match all frames.  Set one bit in BITS for
	 * each frame to be modified.  This ensures that each frame is
	 * only modified once, even if the same frame is listed more than
	 * once.
	 */
	if (frames) {
	    for (ip=frames, bits=0;  *ip;  ip++)
		bits |= (1 << (*ip - 1));
	} else {
	    for (i=1, bits=0;  i <= xim->nframes;  i++)
		bits |= (1 << (i - 1));
	}

	for (i=0;  i < xim->nframes;  i++) {
	    fr = &xim->frames[i];
	    if (fr != fb && (bits & (1 << (fr->frameno - 1)))) {
		fr->colormap = fb->colormap;
		fr->offset = fb->offset;
		fr->scale = fb->scale;
		xim_enhancement (xim, fr);
	    }
	}
}


/* XIM_REGISTERFRAMES -- Register the listed frames with the given reference
 * frame, i.e.  set the field center and scale of each frame to match the
 * reference frame.
 */
void
xim_registerFrames (xim, frames, reference_frame, offsets)
XimDataPtr xim;
int *frames;
int reference_frame;
int offsets;
{
	register int *ip, i;
	register FrameBufPtr fr, fb = &xim->frames[reference_frame-1];
	int src, st, sx, sy, snx, sny;
	int dst, dt, dx, dy, dnx, dny;
	Widget gt = xim->gt;
	int bits, rop;

	/* If frames is NULL register all frames.  Set one bit in BITS for
	 * each frame to be modified.  This ensures that each frame is
	 * only modified once, even if the same frame is listed more than
	 * once.
	 */
	if (frames) {
	    for (ip=frames, bits=0;  *ip;  ip++)
		bits |= (1 << (*ip - 1));
	} else {
	    for (i=1, bits=0;  i <= xim->nframes;  i++)
		bits |= (1 << (i - 1));
	}

	GtGetMapping (gt, fb->zoommap,
	    &rop, &src,&st,&sx,&sy,&snx,&sny, &dst,&dt,&dx,&dy,&dnx,&dny);

	for (i=0;  i < xim->nframes;  i++) {
	    fr = &xim->frames[i];

/*	    if (fr != fb && (bits & (1 << (fr->frameno - 1)))) {*/
	    if ((bits & (1 << (fr->frameno - 1)))) {
		fr->xcen = fb->xcen;  fr->ycen = fb->ycen;
		fr->xmag = fb->xmag;  fr->ymag = fb->ymag;
		fr->xflip = fb->xflip;  fr->yflip = fb->yflip;

		if (!xim_onScreen (xim, fb->frameno))
		    GtDisableMapping (gt, fr->zoommap, 0);

		if (offsets) {
		    /* fb is the current display buffer, fr is some other
		     * frame in the list.  To do the offsets we must
		     * first subtract the offset of the current display
		     * and then add the offset peculiar to the frame.
		     */

		    int nsx, nsy;

		    nsx = (int)(sx - fb->xoff + fr->xoff);
		    nsy = (int)(sy - fb->yoff + fr->yoff);

		    GtSetMapping (gt, fr->zoommap, xim->rop,
		        fr->raster, st,nsx,nsy, snx,sny, 
		        fr->zoomras,dt,dx,dy,dnx,dny);
		    GtRefreshMapping (gt, fr->zoommap);

		} else {
		    GtSetMapping (gt, fr->zoommap, xim->rop,
		        fr->raster, st,sx,sy, snx,sny, 
		        fr->zoomras,dt,dx,dy,dnx,dny);
		}
	    }
	}
	    
	/* A normal registration zeroes the offsets for each frame.  */
	if (!offsets) {
	    for (i=0;  i < xim->nframes;  i++) {
	        fr = &xim->frames[i];
	        fr->xoff = fr->yoff = 0;
	    }
	}
	xim_labelTiles (xim);
}


/* XIM_CURSORMODE -- Enter or leave cursor mode.  The remote client issues
 * a cursor read request via the datastream, then blocks until a cursor
 * value is returned.  The GUI is responsible for signalling to the user
 * that the application is waiting for cursor input, and for returning the
 * cursor value when the cursor read finishes.
 */
void
xim_cursorMode (xim, state)
register XimDataPtr xim;
int state;
{
	/* The GUI is responsible for implementing cursor reads. */
	xim_message (xim, "cursorMode", state ? "on" : "off");
}


/* XIM_SETMAPPING -- Set up a mapping between two rasters.
 */
void
xim_setMapping (xim, fb, frame, mapping, src, dst, fill_mode)
register FrameBufPtr fb;
register XimDataPtr xim;
int frame;
int mapping;
int src, dst;
int fill_mode;
{
	register Widget gt = xim->gt;
	int src_type, src_width, src_height, src_depth;
	int dst_type, dst_width, dst_height, dst_depth, dst_x, dst_y;
	float xscale, yscale, scale;
	int dx, dy, dnx, dny;
	int sx, sy, snx, sny;

	/* Get dimensions of source and destination rasters. */
	if (GtQueryRaster (gt, src,
		&src_type, &src_width, &src_height, &src_depth) == 0)
	    return;

	/* Get region of destination to be written into. */
	if (dst == 0) {
	    xim_getScreen (xim, frame,
		&dst_x, &dst_y, &dst_width, &dst_height, &dst_depth);
	} else {
	    if (GtQueryRaster (gt, dst,
		    &dst_type, &dst_width, &dst_height, &dst_depth) == 0)
		return;
	    dst_x = dst_y = 0;
	}

	xscale = (float)dst_width / (float)src_width;
	yscale = (float)dst_height / (float)src_height;

	switch (fill_mode) {
	case M_UNITARY:
	    /* Set up a unitary (one-to-one) mapping, i.e. one pixel in the
	     * source is mapped to one pixel in the destination.  The mapped
	     * region is the center of both rasters.
	     */
	    xscale = yscale = 1.0;
	    /* fall through */

	case M_ASPECT:
	    /* Scale the source to fit the destination, preserving the
	     * aspect ratio and displaying all source pixels.
	     */
	    scale = min (xscale, yscale);
	    if (abs(scale - (int)(scale+0.5)) < TOL)
		scale = (int)(scale+0.5);

	    dnx = src_width * scale;
	    if (dnx > dst_width) {
		/* Clip source rect. */
		dnx = dst_width;
		snx = dnx;
		sx = max (0, (src_width + 1) / 2 - snx / 2);
		dx = 0;
	    } else {
		/* Clip destination rect. */
		snx = src_width;
		sx = 0;
		dx = max (0, (dst_width + 1) / 2 - dnx / 2);
	    }

	    dny = src_height * scale;
	    if (dny > dst_height) {
		/* Clip source rect. */
		dny = dst_height;
		sny = dny;
		sy = max (0, (src_height + 1) / 2 - sny / 2);
		dy = 0;
	    } else {
		/* Clip destination rect. */
		sny = src_height;
		sy = 0;
		dy = max (0, (dst_height + 1) / 2 - dny / 2);
	    }

	    GtSetMapping (gt, mapping, xim->rop,
		src, GtPixel, sx, sy, snx, sny,
		dst, GtPixel, dst_x + dx, dst_y + dy, dnx, dny);
	    break;

	case M_FILL:
	    /* Scale the source to fill the destination window.
	     */
	    GtSetMapping (gt, mapping, xim->rop,
		src, GtPixel, 0, 0, src_width, src_height,
		dst, GtPixel, dst_x, dst_y, dst_width, dst_height);
	    break;
	}

	if (fb && mapping == fb->zoommap) {
	    fb->xmag = 1.0;
	    fb->ymag = 1.0;
	    fb->xscale = xscale;
	    fb->yscale = yscale;
	    fb->xcen = (src_width + 1) / 2.0;
	    fb->ycen = (src_height + 1) / 2.0;
	    fb->xflip = fb->yflip = 0;
	}
}


/* XIM_SETZOOM -- Modify a mapping between two rasters given the specified
 * view center and zoom factors.
 */
void
xim_setZoom (xim, fb, frame, mapping, src, dst, xcen,ycen,xmag,ymag, xoff,yoff, absolute)
register XimDataPtr xim;
register FrameBufPtr fb;
int frame;
int mapping;
int src, dst;
float xcen, ycen;	/* center of source raster region to be mapped */
float xmag, ymag;	/* magnification in each axis */
float xoff, yoff;	/* offset in each axis */
Boolean absolute;	/* ignore xscale/yscale */
{
	register Widget gt = xim->gt;
	int src_type, src_width, src_height, src_depth;
	int dst_type, dst_width, dst_height, dst_depth, dst_x, dst_y;
	int sx1, sx2, sy1, sy2, snx, sny;
	int dx1, dx2, dy1, dy2, dnx, dny;
	int fx1, fx2, fy1, fy2, shift;
	int src_recenter_allowed = 1;
	int dst_recenter_allowed = 1;
	int xreplicate, yreplicate;
	int scale, max_x, max_y, bias;
	float xscale, yscale;
	char buf[256];


	/* Get dimensions of source and destination rasters. */
	if (GtQueryRaster (gt, src,
		&src_type, &src_width, &src_height, &src_depth) == 0)
	    return;

	/* Get region of destination to be written into. */
	if (dst == 0) {
	    xim_getScreen (xim, frame,
		&dst_x, &dst_y, &dst_width, &dst_height, &dst_depth);
	} else {
	    if (GtQueryRaster (gt, dst,
		    &dst_type, &dst_width, &dst_height, &dst_depth) == 0)
		return;
	    dst_x = dst_y = 0;
	}

	/* Use old center if none given. */
	if (xcen <= 0)
	    xcen = fb->xcen;
	if (ycen <= 0)
	    ycen = fb->ycen;

	/* Get scaling ratios. */
	if (absolute) {
	    xscale = xmag;
	    yscale = ymag;
	} else {
	    xscale = xmag * fb->xscale;
	    yscale = ymag * fb->yscale;
	}

	/* Avoid roundoff if integer scaling. */
	if ((xreplicate = (abs(xscale - (int)(xscale+0.5)) < TOL)))
	    xscale = (int)(xscale+0.5);
	if ((yreplicate = (abs(yscale - (int)(yscale+0.5)) < TOL)))
	    yscale = (int)(yscale+0.5);

	/* Sanity check, return if we're getting too small. */
	if (xscale < TOL || yscale < TOL)
	    return;


	/* Map the destination rect back into the source with the given
	 * zoom factor and center, clipping the source rect at the source
	 * raster boundary.
	 */
src_recenter:
	max_x = src_width - 1;
	max_y = src_height - 1;

	snx = (int) (dst_width / xscale) + 1;
	sny = (int) (dst_height / yscale) + 1;

	sx1 = (int) (xcen - snx / 2.0 + 0.5 + xoff);		/*********/
	sx2 = (int) (xcen + snx / 2.0 + 0.5);
	sy1 = (int) (ycen - sny / 2.0 + 0.5 + yoff);		/*********/
	sy2 = (int) (ycen + sny / 2.0 + 0.5);

	/* Fiddle the source rect slightly if necessary to achieve the
	 * requested snx,sny.  This corrects for any pixel selection errors
	 * introduced in the float to integer truncation.  This is a small
	 * effect but it can be apparent at high pixel magnifications.
	 */
	sx2 -= ((sx2 - sx1 + 1) - snx);
	sy2 -= ((sy2 - sy1 + 1) - sny);

	if (src_recenter_allowed) {
	    /* Adjust the center of the source rect to make it fit on the
	     * source raster, but only if only one edge extends beyond the
	     * raster boundary.  If both edges are out of bounds we just
	     * clip below.
	     *
	     * MJF 9/29  - The changes to fx2/fy2 below are to fix an off-by-one
	     * error in the center pixel computed by an integer zoom.  For
	     * example, display a 256sq image, invert the cmap and zoom by
	     * two, the center is different than a straight centering op done
	     * by the pan function.  The effect is that hardcopy has an extra
	     * border on two sides.
	     */
	    fx1 = (sx1 < 0) ? -sx1 : 0;
	    fx2 = (sx2 > max_x) ? (max_x - sx2 + 1) : 0;
	    if (fx1 && !fx2 || !fx1 && fx2)
		xcen += (fx1 + fx2);

	    fy1 = (sy1 < 0) ? -sy1 : 0;
	    fy2 = (sy2 > max_y) ? (max_y - sy2 + 1) : 0;
	    if (fy1 && !fy2 || !fy1 && fy2)
		ycen += (fy1 + fy2);

	    src_recenter_allowed = 0;
	    goto src_recenter;
	}

	/* Clip the source rect to the raster boundary. */
	sx1 = max(0, min(max_x, sx1));
	sx2 = max(0, min(max_x, sx2));
	sy1 = max(0, min(max_y, sy1));
	sy2 = max(0, min(max_y, sy2));

	snx = sx2 - sx1 + 1;
	sny = sy2 - sy1 + 1;


	/* Map the zoomed, possibly centered, and clipped source rect back
	 * to the destination.
	 */
	max_x = dst_width - 1;
	max_y = dst_height - 1;

	dx1 = (int) ((sx1-0.5 - xcen) * xscale + dst_width / 2.0 + 0.5);
	dx2 = (int) ((sx2+0.5 - xcen) * xscale + dst_width / 2.0 + 0.5);
	dy1 = (int) ((sy1-0.5 - ycen) * yscale + dst_height / 2.0 + 0.5);
	dy2 = (int) ((sy2+0.5 - ycen) * yscale + dst_height / 2.0 + 0.5);

	if (dst_recenter_allowed) {
	    /* If the unclipped destination rect extends out of bounds on
	     * one side or the other (but not both), shift it slightly to 
	     * try to recenter it on the destination raster.  This tends
	     * to cancel out rounding errors occurring in the calculation
	     * of the destination rect.
	     */
	    fx1 = (dx1 < 0) ? -dx1 : 0;
	    fx2 = (dx2 > max_x) ? (max_x - dx2) : 0;
	    if (fx1 && !fx2 || !fx1 && fx2) {
		shift = (fx1 + fx2);
		dx1 += shift;
		dx2 += shift;
	    }

	    fy1 = (dy1 < 0) ? -dy1 : 0;
	    fy2 = (dy2 > max_y) ? (max_y - dy2) : 0;
	    if (fy1 && !fy2 || !fy1 && fy2) {
		shift = (fy1 + fy2);
		dy1 += shift;
		dy2 += shift;
	    }

	    /* If the destination rect does not fill the destination raster,
	     * center it in the raster.
	     */
	    if ((dx2 - dx1 + 1) < dst_width) {
		shift = (dx1 + (max_x - dx2)) / 2 - dx1;
		dx1 += shift;
		dx2 += shift;
	    }

	    if ((dy2 - dy1 + 1) < dst_height) {
		shift = (dy1 + (max_y - dy2)) / 2 - dy1;
		dy1 += shift;
		dy2 += shift;
	    }
	}

	/* Clip to the edge of the destination raster. */
	dx1 = max(0, min(max_x, dx1));
	dx2 = max(0, min(max_x, dx2));
	dy1 = max(0, min(max_y, dy1));
	dy2 = max(0, min(max_y, dy2));

	dnx = dx2 - dx1 + 1;
	dny = dy2 - dy1 + 1;

	/* Attempt to integerize things if integer scaling is selected.  This
	 * can leave a blank pixel or two at the edge depending upon the
	 * window size and scaling.
	 */
	scale = (int)xscale;
	if (xreplicate && dnx != snx * scale) {
	    if (snx < src_width) {
		snx = max(1, min(src_width, dnx / scale));
		dnx = max(1, min(dst_width, snx * scale));
	    } else if (dnx < dst_width) {
		dnx = max(1, min(dst_width, snx * scale));
		snx = max(1, min(src_width, dnx / scale));
	    }
	    if ((bias = ((dx2 - dx1 + 1) - dnx) / 2) > 0) {
		dx1 += bias;
		dx2 += bias;
	    }
	}
	scale = (int)yscale;
	if (yreplicate && dny != sny * scale) {
	    if (sny < src_height) {
		sny = max(1, min(src_height, dny / scale));
		dny = max(1, min(dst_height, sny * scale));
	    } else if (dny < dst_height) {
		dny = max(1, min(dst_height, sny * scale));
		sny = max(1, min(src_height, dny / scale));
	    }
	    if ((bias = ((dy2 - dy1 + 1) - dny) / 2) > 0) {
		dy1 += bias;
		dy2 += bias;
	    }
	}



	/* Set the mapping. */
        initialize_shadow_pixmap (gt, dst);
	GtSetMapping (gt, mapping, xim->rop,
	    src, GtPixel, sx1, sy1, snx, sny,
	    dst, GtPixel, dst_x + dx1, dst_y + dy1,
		fb->xflip ? -dnx : dnx,
		fb->yflip ? -dny : dny);

	/* Update the frame buffer zoom/pan/flip parameters. */
	fb->xcen = xcen;     fb->ycen = ycen;
	fb->xoff = xoff;     fb->yoff = yoff;

	if (absolute) {
	    fb->xmag = xmag / fb->xscale;
	    fb->ymag = ymag / fb->yscale;
	} else {
	    fb->xmag = xmag;
	    fb->ymag = ymag;
	}

	xim_frameRegion (xim, fb);
	sprintf (buf, "%g %g %g %g %g %g %g %g",
	    fb->xmag, fb->ymag, fb->xcen, fb->ycen, 
	    fb->xscale, fb->yscale, fb->xoff, fb->yoff);
	xim_message (xim, "frameView", buf);
}


/* XIM_GETSCREEN -- Determine the region of the display window to which
 * the given frame is mapped.
 */
void
xim_getScreen (xim, frame, sx, sy, width, height, depth)
register XimDataPtr xim;
int frame;
int *sx, *sy;
int *width, *height, *depth;
{
	register int i;
	int border = xim->tileBorder;
	int rtype, scr_width, scr_height;
	int twidth, theight, tileno, frameno;
	int tilex, tiley;
	int nrows = xim->tileRows, ncols = xim->tileCols;

	if (GtQueryRaster (xim->gt, 0,
		&rtype, &scr_width, &scr_height, depth) == 0)
	    return;

	/* Use the entire display window if we are not tiling frames, if
	 * there aren't enough frames to tile, or if the given frame is
	 * not in the list of tile frames.
	 */
	if (!xim->tileFrames || xim->nframes <= 1 ||
		!(xim->tileFramesList & (1 << frame-1))) {
	    *sx = *sy = 0;
	    *width = scr_width;
	    *height = scr_height;
	    return;
	}

	/* Get index of frame in tile Frames list. */
	for (i=1, frameno=0;  i <= xim->nframes;  i++) {
	    if (xim->tileFramesList & (1 << (i-1))) {
		frameno++;
		if (frame == i)
		    break;
	    }
	}

	/* Now compute the size of each tile. */
	tileno  = frameno - 1;
	twidth  = (scr_width  - (ncols * 2 * border)) / ncols;
	theight = (scr_height - (nrows * 2 * border)) / nrows;

	/* Get the "coordinates" of the tile in the mosaic. */
	if (xim->tileByRows) {
	    tilex = tileno % ncols;
	    tiley = tileno / ncols;
	} else {
	    tilex = tileno / nrows;
	    tiley = tileno % nrows;
	}
	if (!xim->tileTopDown)
	    tiley = (nrows - tiley - 1);

	/* Finally, get the placement of the tile on the screen. */
	*sx = tilex * twidth  + ((tilex*2+1) * border);
	*sy = tiley * theight + ((tiley*2+1) * border);

	*width = twidth;
	*height = theight;
}


/* XIM_ONSCREEN -- Test whether the given frame is visible onscreen.
 */
static int
xim_onScreen (xim, frame)
register XimDataPtr xim;
int frame;
{
	if (xim->tileFrames)
	    return ((xim->tileFramesList & (1 << (frame-1))) != 0);
	else
	    return (frame == xim->display_frame);
}


/* XIM_SETFLIP -- Modify a mapping to flip the frame in X and/or Y.
 */
void
xim_setFlip (xim, fb, flip_x, flip_y)
register XimDataPtr xim;
register FrameBufPtr fb;
int flip_x, flip_y;
{
	register Widget gt = xim->gt;
	int src, st, sx, sy, snx, sny;
	int dst, dt, dx, dy, dnx, dny;
	int rop;

	if (!flip_x && !flip_y)
	    return;

	if (flip_x)
	    fb->xflip = !fb->xflip;
	if (flip_y)
	    fb->yflip = !fb->yflip;

	GtGetMapping (gt, fb->zoommap,
	    &rop, &src,&st,&sx,&sy,&snx,&sny, &dst,&dt,&dx,&dy,&dnx,&dny);

	dnx = abs (dnx);
	dnx = fb->xflip ? -dnx : dnx;
	dny = abs (dny);
	dny = fb->yflip ? -dny : dny;

	GtSetMapping (gt, fb->zoommap,
	    rop, src,st,sx,sy,snx,sny, dst,dt,dx,dy,dnx,dny);

	xim_message (xim, "xflip", fb->xflip ? "true" : "false");
	xim_message (xim, "yflip", fb->yflip ? "true" : "false");
}


/* XIM_SETROP -- Modify the rasterop portion of a mapping.
 */
void
xim_setRop (xim, fb, rop)
register XimDataPtr xim;
register FrameBufPtr fb;
int rop;
{
	register Widget gt = xim->gt;
	int src, st, sx, sy, snx, sny;
	int dst, dt, dx, dy, dnx, dny;
	int oldrop;

	GtGetMapping (gt, fb->zoommap,
	    &oldrop, &src,&st,&sx,&sy,&snx,&sny, &dst,&dt,&dx,&dy,&dnx,&dny);

	GtSetMapping (gt, fb->zoommap,
	    rop, src,st,sx,sy,snx,sny, dst,dt,dx,dy,dnx,dny);
}


/* XIM_SETCURSORPOS -- Set the cursor position.
 */
void
xim_setCursorPos (xim, sx, sy)
register XimDataPtr xim;
float sx, sy;			/* raster coordinates */
{
	GtSetRaster (xim->gt, xim->df_p->frameno);
	GtSetCursorPos (xim->gt, (int)sx, (int)sy);
	GtSetRaster (xim->gt, 0);
}


/* XIM_GETCURSORPOS -- Get the cursor position.  This is done to a fractional
 * pixel precision if the image is zoomed.
 */
void
xim_getCursorPos (xim, sx, sy, raster, frame)
register XimDataPtr xim;
float *sx, *sy;
int *raster, *frame;
{
	register FrameBufPtr fb;
	DPoint pv1, pv2;
	int rx, ry, rmap;
	int src, x, y, i;

	GtGetCursorPos (xim->gt, &x, &y);
	src = GtSelectRaster (xim->gt, 0, GtPixel, x,y, GtNDC, &rx, &ry, &rmap);

	pv1.x = rx;  pv1.y = ry;
	GtNDCToPixel (xim->gt, src, &pv1, &pv2, 1);
	*sx = pv2.x;
	*sy = pv2.y;

	if (raster)
	    *raster = src;

	if (frame) {
	    *frame = xim->display_frame;
	    for (i=1;  i < xim->nframes;  i++) {
		fb = &xim->frames[i-1];
		if (fb->raster == src || fb->zoomras == src) {
		    *frame = fb->frameno;
		    break;
		}
	    }
	}
}


/* XIM_READDISPLAY -- Read a region of the current display frame and return
 * the pixels and corresponding RGB colormap.  We allocate a pointer to the
 * pixels that needs to be freed later on.  Note this routine returns an
 * 8 bit RGB colormap.
 */
unsigned char *
xim_readDisplay (xim, x0,y0,nx,ny, w,h, r,g,b, ncolors) 
register XimDataPtr xim;
int x0,y0,nx,ny;			/* region to extract (input) */
int *w, *h;				/* size of output region (output). */
unsigned char *r, *g, *b;		/* colortable (output) */
int *ncolors;				/* size of colortable (output) */
{
	register Widget gt = xim->gt;
	register int i, j;

	int raster, x1, y1, nc;
	unsigned short *rs, *gs, *bs;
	int junk, width, height, depth;
	unsigned short *cmap=NULL, *iomap=NULL;
	unsigned char *pixels=NULL;
	static int debug = 0;

	/* The display is raster zero. */
	raster = 0;

	/* Get the size of the raster we're going to extract.  If nx or ny
	 * is zero use the entire raster, else use the indicated region.
	 * If a region is given clip it to the raster boundary.
	 */
	if (GtQueryRaster (gt, raster, &junk, &width, &height, &depth) == 0)
	    goto error;


	if (nx <= 0 || ny <= 0) {
	    x0 = y0 = 0;
	    nx = width;
	    ny = height;
	} else {
	    x1 = min (width-1, x0 + nx - 1);
	    y1 = min (height-1, y0 + ny - 1);
	    x0 = max (0, x0);
	    y0 = max (0, y0);
	    nx = x1 - x0 + 1;
	    ny = y1 - y0 + 1;
	}

	/* Get a pixel buffer and read the pixels. */
	if (nx*ny <= 0 || !(pixels = (unsigned char *) malloc (nx * ny)))
	    goto error;
	if (GtReadPixels (gt, raster, pixels, 8, x0, y0, nx, ny) < 0)
	    goto error;


	/* Get Gterm widget colormap. */
	if (!(cmap = (ushort *) malloc (3 * MAX_COLORS * sizeof(ushort))))
	    goto error;

	rs = cmap;
	gs = rs + MAX_COLORS;
	bs = gs + MAX_COLORS;

	if (depth == 8) {
	    nc = GtReadColormap (gt, raster, 0, MAX_COLORS, rs,gs,bs);
	    if (nc <= 0)
	        goto error;


	    /* Get the RGB color associated with each pixel.  To determine
	    ** this we must consider both the Gterm widget colormap and iomap.
	    ** The iomap maps client pixels to Gterm widget pixels, and the 
	    ** colormap maps Gterm widget pixels to RGB values.  GtReadPixels
	    ** returns client pixels, so to get the RGB value associated with
	    ** each client pixel we must run the client pixel through the iomap
	    ** to get the Gterm widget pixel, and then use this to look up the
	    ** RGB value in the widget's colormap.  When reading the display
	    ** (raster=0) we get the colormap corresponding to the image as
	    ** currently windowed, hence the output image will be rendered as
	    ** in the current display. Note: the Gterm widget maps are 16 bits,
	    ** while we return an 8 bit RGB colormap to our caller.
	    */
	    /* Get iomap. */
	    if (!(iomap = (ushort *) malloc (MAX_COLORS * sizeof(ushort))))
	        goto error;
	    GtReadIomap (gt, iomap, 0, MAX_COLORS);

	    if (debug) {
	        register short pmin = MAX_COLORS, pmax = 0, i, j;
                fprintf (stderr, "iomap\n");
	        for (i=0; i < MAX_COLORS; ) {
                    for (j=0; j < 8 && i < MAX_COLORS; j++) {
		        pmin = (iomap[i] < pmin ? iomap[i] : pmin);
		        pmax = (iomap[i] > pmax ? iomap[i] : pmax);
                        fprintf (stderr, "%3d(%3d) ", i, iomap[i++]);
		    } 
                    fprintf (stderr, "\n");

	        }
	        fprintf (stderr, "iomap min = %d max = %d\n", pmin, pmax);
                fprintf (stderr, "Gterm Colormap\n");
                for (i=0; i < MAX_COLORS; ) {
                    for (j=1; j < 4 && i < MAX_COLORS; j++)
                        fprintf (stderr, "    %3d(%3d,%3d,%3d)",i,
			    (rs[i]>>8),(gs[i]>>8),(bs[i++]>>8));
                    fprintf (stderr, "\n");
                }
	        fflush (stderr);
	    }

	    /* Output the colormap. */
	    for (i=0;  i < nc;  i++) {
	        j = iomap[i];
	        r[i] = (rs[j] >> 8);
	        g[i] = (gs[j] >> 8);
	        b[i] = (bs[j] >> 8);
	    }
	    for (i=nc;  i < MAX_COLORS;  i++) {
	        j = iomap[i];
	        r[i] = (rs[j] >> 8);
	        g[i] = (gs[j] >> 8);
	        b[i] = (bs[j] >> 8);
	    }

	} else {
	    unsigned long lut[MAX_COLORS];

	    GtReadLUT (gt, lut, 0, MAX_COLORS);

	    nc = 216;
	    for (i=0; i < nc; i++) {
	        r[i] = (lut[i] >> 16) & 0xff;
	        g[i] = (lut[i] >>  8) & 0xff;
	        b[i] = (lut[i]      ) & 0xff;
	    }
	}

	*w = nx;
	*h = ny;
	*ncolors = nc;			/* includes static colors */

	if (debug) {
	    fprintf (stderr,
		"xim_readDisplay:  w=%d  h=%d  d=%d\n", width, height, depth);
	    for (i=0; i < nc; i++)
  	        fprintf (stderr, "xim_readDisplay[%3d]:  %3d %3d %3d\n",
		i, r[i], g[i], b[i]);
	}

	free ((char *) cmap);
	free ((char *) iomap);

	return (pixels);

error:
	if (cmap)
	    free ((char *) cmap);
	if (iomap)
	    free ((char *) iomap);
	if (pixels)
	    free ((char *) pixels);
	return (NULL);
}


/* XIM_WRITEDISPLAY -- Load the current display frame from the pixel array
 * and RGB colormap passed in the input arguments.  The data is assumed to
 * be 8 bit pseudocolor.
 */
int
xim_writeDisplay (xim, frame, mapname, pixels, w,h, r,g,b, ncolors)
register XimDataPtr xim;
int frame;				/* display frame to be written */
char *mapname;				/* colormap name to be written */
unsigned char *pixels;
int w, h;
unsigned char *r, *g, *b;
int ncolors;
{
	register int i, j;
	register FrameBufPtr fb;
	register Widget gt = xim->gt;
	unsigned short rs[MAX_COLORS], gs[MAX_COLORS], bs[MAX_COLORS];
	unsigned short iomap[MAX_COLORS], sv_iomap[MAX_COLORS];
	int nx, ny, sx0, sy0, dx0, dy0;
	int sx, sy, width, height, depth;
	float xzoom, yzoom, zoom;
	int debug=0, autoscale=xim->autoscale;
	ColorMapPtr cm;


	if (DBG_RASTER)
	    fprintf (stderr, "raster writeDisplay: .......................");

	/* If frame=0 use the current display frame. */
	if (frame < 1) frame = xim->display_frame;

	/* Create additional frames if needed. */
	if (frame > xim->nframes) {
            for (i=1;  i <= frame;  i++) {
                fb = &xim->frames[i-1];
                if (fb->frameno != i) {
                    xim_initFrame (xim, i, frame,
                        &xim->fb_config[xim->fb_configno-1], xim->memModel);

                    /* If we're in tile mode, add the frame to the tile list
                     * and if needed resize the tile frames.
                     */
                    if (xim->tileFrames) {
                        xim->tileFramesList |= (1 << (i-1));
                        xim->nTileFrames++;
                        xim_tileFrames (xim, xim->tileFramesList);
                    }
                }
            }
	}


	/* Erase the existing frame before disabling the mapping, otherwise
	 * we get a white background when the new image is loaded.
	 */
	xim_eraseFrame (xim, frame);

	fb = &xim->frames[frame-1];
	GtDisableMapping (gt, fb->dispmap, 0);

	/* Substitute a one-to-one iomap so that we don't confuse colors for
	 * a non-continuous tone (grayscale) image.
	 */
	GtReadIomap (gt, sv_iomap, 0, MAX_COLORS);
	for (i=0;  i < MAX_COLORS;  i++)
	    iomap[i] = FIRST_COLOR + min (xim->ncolors, i);
	GtWriteIomap (gt, iomap, 0, MAX_COLORS);

	/* If debug mode is enabled compute and print the minimum and maximum
	 * pixel values and a histogram or count of the number of pixels at
	 * each value.
	 */
	if (debug) {
	    int count[MAX_COLORS];
	    int v, lo, hi;

	    memset ((void *)count, 0, sizeof(count));
	    for (i=0, lo=MAX_COLORS, hi=0;  i < w*h;  i++) {
		v = pixels[i];
		count[v]++;
		if (v < lo)
		    lo = v;
		else if (v > hi)
		    hi = v;
	    }
	    fprintf (stderr, "%s: w=%d h=%d min=%d max=%d\n",
		mapname, w, h, lo, hi);

	    for (i=0, j=0;  i < MAX_COLORS;  i++)
		if (count[i]) {
		    fprintf (stderr, "  %3d(%4d)", i, min(9999,count[i]));
		    if (++j >= 7) {
			fprintf (stderr, "\n");
			j = 0;
		    }
		}
	    if (j)
		fprintf (stderr, "\n");

	    fprintf (stderr, "zeros: ");
	    for (i=0;  i < MAX_COLORS;  i++)
		if (!count[i])
		    fprintf (stderr, " %d", i);
	    fprintf (stderr, "\n");
	}

	/* Invert the iomap.  We want to map standard pixels 0-N to the range
	 * FC-FC+N in Gterm color space (FC is shorthand for FIRST_COLOR).
	 * GtWritePixels will process pixels through the ximtool iomap so we
	 * need to compute invmap, which tells us the input value needed to
	 * get the desired value of out the iomap.
	GtReadIomap (gt, iomap, 0, MAX_COLORS);
	memset ((void *)invmap, 0, sizeof(invmap));
	for (i=0;  i < MAX_COLORS;  i++)
	    invmap[iomap[i]] = i;
	if (!(gtpix = (unsigned char *) malloc (npix = w * h)))
	    return (-1);
	for (i=0;  i < npix;  i++) {
	    want = FIRST_COLOR + pixels[i];
	    gtpix[i] = invmap[want];
	}
	free ((char *) gtpix);
	 */

	/* If the image is too large to fit in the existing frame clip it
	 * to fit (we need to add an auto-resize option here later).
	 */
	if (w > xim->width) {
	    nx = xim->width;
	    sx0 = (w - nx) / 2;
	    dx0 = 0;
	} else {
	    nx = w;
	    sx0 = 0;
	    dx0 = (xim->width - w) / 2;
	}

	if (h > xim->height) {
	    ny = xim->height;
	    sy0 = (h - ny) / 2;
	    dy0 = 0;
	} else {
	    ny = h;
	    sy0 = 0;
	    dy0 = (xim->height - h) / 2;
	}

	/* Load the frame.  The GtWritePixels call loads to the raster 
	 * associated with the frame, not the frame number directly.
	 */
	if (nx <= xim->width && ny <= xim->height) {
	    GtWritePixels (gt, fb->raster, pixels, 8, dx0, dy0, nx, ny);
	} else {
	    unsigned char *ip = pixels + sy0 * w + sx0;
	    for (i=0;  i < ny;  i++, ip += w)
		GtWritePixels (gt, fb->raster, ip, 8, dx0, dy0+i, nx, 1);
	}

	/* Get screen size. */
	xim_getScreen (xim, 1, &sx, &sy, &width, &height, &depth);

	/* Compute the new scale factor required to scale the source to the
	 * destination at magnification 1.0.
	 */
	if (autoscale) {
	    xzoom = (float)width / (float)nx;
	    yzoom = (float)height / (float)ny;
	    zoom = min (xzoom, yzoom);
	} else {
	    xzoom = 1.0;
	    yzoom = 1.0;
	    zoom = 1.0;
	}

	/* Set the new mapping. */
	fb->xmag = fb->ymag = zoom;
	fb->xcen = xim->width / 2;
	fb->ycen = xim->height / 2;

	/* Zoom it to fill the display window. */
	xim_setZoom (xim, fb, frame, fb->zoommap, fb->raster, fb->zoomras,
	    fb->xcen, fb->ycen, fb->xmag, fb->ymag, 
	    fb->xoff, fb->yoff, True);

	/* Check if the named colortable is already defined.  If not, add
	 * another one.  If ncolors=0 omit the colortable handling, e.g.
	 * when loading a raster that doesn't have a colormap.
	 */
	if (ncolors > 0) {
	    for (i=0, cm=NULL;  i < ncolormaps;  i++)
		if (strcmp (colormaps[i].name, mapname) == 0) {
		    cm = &colormaps[i];
		    break;
		}
	    if (!cm) {
		if (ncolormaps >= MAX_COLORMAPS - 1)
		    cm = &colormaps[ncolormaps];
		else {
		    cm = &colormaps[ncolormaps++];
		    cm->mapno = GtNextColormap (gt);
		}
		strcpy (cm->name, mapname);
		ncolormaps = min (MAX_COLORMAPS-1, ncolormaps);

		/* Pass the list of colortables on to the user interface. */
		xim_colortables (xim);
	    }

	    for (i=0;  i < ncolors;  i++) {
		rs[i] = (r[i] << 8);
		gs[i] = (g[i] << 8);
		bs[i] = (b[i] << 8);
	    }

	    if (DBG_RASTER)
		fprintf (stderr,"raster writeDisplay: writing colormap.......");
	    GtWriteColormap (gt, cm->mapno,
		FIRST_COLOR, min(ncolors,xim->ncolors), rs, gs, bs);
	    if (DBG_RASTER)
		fprintf (stderr,
		    "raster writeDisplay: writing colormap.......DONE");

	    fb->offset = 0.5;
	    fb->scale = (xim->invert ? -1.0 : 1.0);
	    fb->colormap = cm->mapno;
	} else 
	    cm = &colormaps[fb->colormap-1];

	/* Display the frame. */
	GtEnableMapping (gt, fb->dispmap, 1);
	GtSetDisplayRaster (gt, xim->display_frame);
	xim_setDisplayFrame (xim, frame);

	GtSetColormapFocus (-1);	/* force full update	*/
	GtLoadColormap (gt, cm->mapno, fb->offset, fb->scale);
	GtSetColormapFocus (xim->cm_focus);
	xim_enhancement (xim, fb);

	GtWriteIomap (gt, sv_iomap, 0, MAX_COLORS);

	if (DBG_RASTER)
	    fprintf (stderr, "raster writeDisplay: ......................DONE");
	return (0);
}


/* XIM_MESSAGE -- Send a message to the user interface.
 */
void
xim_message (xim, object, message)
register XimDataPtr xim;
char *object;
char *message;
{
	char msgbuf[SZ_MSGBUF];

	sprintf (msgbuf, "setValue {%s}", message);
	ObmDeliverMsg (xim->obm, object, msgbuf);
}


/* XIM_MSGI -- Like xim_message, but the message is an integer value.
 */
void
xim_msgi (xim, object, value)
register XimDataPtr xim;
char *object;
int value;
{
	char msgbuf[SZ_LINE];
	sprintf (msgbuf, "setValue {%d}", value);
	ObmDeliverMsg (xim->obm, object, msgbuf);
}


/* XIM_ALERT -- Issue an alert to the server.  The message text input will
 * be displayed and either the ok (proceed) or cancel action will be taken,
 * causing the action text input to be sent back to the client to be
 * executed as a command.  This is used to alert the server (i.e. user) of
 * unusual circumstances and determine whether or not the server wants to
 * proceed.  An alert with no actions is a warning.
 */
void
xim_alert (xim, text, ok_action, cancel_action)
register XimDataPtr xim;
char *text;			/* message text */
char *ok_action;		/* command sent back to client for "ok" */
char *cancel_action;		/* command sent back to client for "cancel" */
{
	char msgbuf[SZ_LINE];
	sprintf (msgbuf, "setValue {{%s} {%s} {%s}}", text,
	    ok_action ? ok_action : "", cancel_action ? cancel_action : "");
	ObmDeliverMsg (xim->obm, "alert", msgbuf);
}


/*
 * Internal Routines.
 * -------------------
 */

/* XIM_FRAMEREGION -- Called when the value of the frameRegion UI parameter
 * needs to be updated.
 */
static void
xim_frameRegion (xim, fb)
register XimDataPtr xim;
register FrameBufPtr fb;
{
	int rop, src, dst;
	int st, sx, sy, snx, sny;
	int dt, dx, dy, dnx, dny;
	char buf[SZ_LINE];

	if (GtGetMapping (xim->gt, fb->zoommap, &rop,
	    &src, &st, &sx, &sy, &snx, &sny,
	    &dst, &dt, &dx, &dy, &dnx, &dny) == -1)
		return;

	/* args: frame x y width height */
	sprintf (buf, "%d %d %d %d %d", fb->frameno, sx, sy, snx, sny);
	xim_message (xim, "frameRegion", buf);
}


/* XIM_COLORTABLES -- Called when the "colortables" UI parameter needs to be
 * updated, i.e., when some change to the list of colormaps has occurred.
 */
static void
xim_colortables (xim)
XimDataPtr xim;
{
	register char *ip, *op;
	char buf[MAX_COLORMAPS*40];
	int i;

	for (i=0, op=buf;  i < ncolormaps;  i++) {
	    *op++ = '"';
	    for (ip = colormaps[i].name;  (*op = *ip++);  op++)
		;
	    *op++ = '"';
	    *op++ = '\n';
	}
	*op++ = '\0';

	xim_message (xim, "colortables", buf);
}


/* XIM_ENHANCEMENT -- Called when the "enhancement" UI parameter needs to
 * be updated for a frame.
 */
void
xim_enhancement (xim, fb)
register XimDataPtr xim;
register FrameBufPtr fb;
{
	char buf[SZ_LINE];

	sprintf (buf, "%d \"%s\" %0.3f %0.3f", fb->frameno,
	    colormaps[fb->colormap-1].name, fb->offset, fb->scale);
	xim_message (xim, "enhancement", buf);


	/* Force an update of the colorbar.  On TrueColor visuals this
	** is required since the colormap doesn't automatically update 
	** the display.
	*/
	set_colorbar (xim, xim->cb);
}


/* GET_FBCONFIG -- Read the XIMTOOL startup file to get the set of possible
 * frame buffer sizes.
 *
 * File format:		configno nframes width height [extra fields]
 *	e.g.,			1  2  512  512
 *				2  2  800  800
 *				3  1 1024 1024		# comment
 */
static void
get_fbconfig (xim)
register XimDataPtr xim;
{
	register char	*ip;
	register FILE	*fp = NULL;
	int	config, nframes, width, height, i;
	char	lbuf[SZ_LINE+1], *fname;
	static char *fb_paths[] = {
		"/usr/local/lib/imtoolrc",
		"/opt/local/lib/imtoolrc",
		"/iraf/iraf/dev/imtoolrc",
		"/local/lib/imtoolrc",
		"/usr/iraf/dev/imtoolrc",
		"/usr/local/iraf/dev/imtoolrc",
		NULL};

	/* Initialize the config table. */
	xim->fb_configno = 1;
	for (i=0;  i < MAX_FBCONFIG;  i++) {
	    xim->fb_config[i].nframes = 1;
	    xim->fb_config[i].width = DEF_FRAME_WIDTH;
	    xim->fb_config[i].height = DEF_FRAME_HEIGHT;
	}

	/* Now add in some defaults for commonly used sizes based on the
 	 * standard IRAF imtoolrc file, we'll avoid any instrument specific
	 * configurations.
	 */
	xim->fb_config[0].width = xim->fb_config[0].height =  512;
	xim->fb_config[1].width = xim->fb_config[1].height =  800;
	xim->fb_config[2].width = xim->fb_config[2].height = 1024;
	xim->fb_config[3].width = xim->fb_config[3].height = 1600;
	xim->fb_config[4].width = xim->fb_config[4].height = 2048;
	xim->fb_config[5].width = xim->fb_config[5].height = 4096;

	/* Attempt to open the config file. */
	if ((fname=getenv(FBCONFIG_ENV1)) || (fname=getenv(FBCONFIG_ENV2)))
	    fp = fopen (fname, "r");
	if (!fp && (fname = getenv ("HOME"))) {
	    sprintf (lbuf, "%s/%s", fname, FBCONFIG_1);
	    fp = fopen (fname = lbuf, "r");
	    if (fp) {
	        xim->imtoolrc = (char *) XtCalloc (SZ_LINE, sizeof(char));
		strncpy (xim->imtoolrc, fname, strlen(fname));
	    }
	}
	if (!fp)
	    fp = fopen (fname = xim->imtoolrc, "r");
 	for (i=0; !fp && fb_paths[i]; i++) {
	    if ((fp = fopen (fname = fb_paths[i], "r"))) {
	        xim->imtoolrc = XtCalloc(strlen(fb_paths[i]+1),sizeof(char));
		strncpy (xim->imtoolrc, fb_paths[i],strlen(fb_paths[i]));
		break;
	    }
	}
	if (!fp) {
	    fprintf (stderr, 
		"Warning: No frame buffer configuration file found.\n");
	    return;
	}


	/* Scan the frame buffer configuration file.
	 */
	while (fgets (lbuf, SZ_LINE, fp) != NULL) {
	    /* Skip comment lines and blank lines. */
	    for (ip=lbuf;  *ip == ' ' || *ip == '\t';  ip++)
		;
	    if (*ip == '\n' || *ip == '#')
		continue;
	    if (!isdigit (*ip))
		continue;
	    switch (sscanf (ip, "%d%d%d%d", &config,&nframes,&width,&height)) {
	    case 4:
		break;			/* normal case */
	    case 3:
		height = width;		/* default to square format */
		break;
	    default:
		fprintf (stderr, "ximtool: bad config `%s'\n", ip);
		continue;
	    }

	    nframes = max (1, nframes);
	    width   = max (1, width);
	    height  = max (1, height);

	    /* Since the frame buffer is stored in a memory pixrect
	     * (effectively), the line length should be an integral number
	     * of 16 bit words.
	     */
	    if (width & 1) {
		fprintf (stderr, "imtool warning: fb config %d [%d-%dx%d] - ",
		    config, nframes, width, height);
		fprintf (stderr, "frame width should be even, reset to %d\n",
		    --width);
	    }

	    config = max(1, min(MAX_FBCONFIG, config)) - 1;
	    xim->fb_config[config].nframes = nframes;
	    xim->fb_config[config].width   = width;
	    xim->fb_config[config].height  = height;
	}

	if (fp) fclose (fp);
}


/* XIM_GETANTIALIAS -- Convert a antialias algorithm expressed as a string
 * into a Gterm rasterop code.
 */
int
xim_getAntialias (xim, s)
XimDataPtr xim;
char *s;
{
    register char *ip, *op;
    char word[SZ_NAME];
    int rop = 0;

    for (ip=s;  *ip && isspace(*ip);  ip++)
	;

    while (*ip) {
	for (op=word;  *ip && isalnum(*ip);  ip++)
	    *op++ = isupper(*ip) ? tolower(*ip) : *ip;
	*op++ = '\0';

	if (strcmp (word, "nearest") == 0)
	    rop |= MF_NEAREST;
	else if (strcmp (word, "bilinear") == 0)
	    rop |= MF_BILINEAR;
	else if (strcmp (word, "area") == 0)
	    rop |= MF_AREA;
	else if (strcmp (word, "blkavg") == 0)
	    rop |= MF_BLKAVG;
	else if (strcmp (word, "boxcar") == 0)
	    rop |= MF_BOXCAR;
	else if (strcmp (word, "lowpass") == 0)
	    rop |= MF_LOWPASS;
	else if (strcmp (word, "gaussian") == 0)
	    rop |= MF_GAUSSIAN;

	while (*ip && !isalnum(*ip))
	    ip++;
    }

    return (rop);
}


/* XIM_SETCOLORMAP -- Set up the RGB lookup tables used to map the windowed
 * monochrome output of a frame buffer into the hardware colormap.
 */
int
xim_setColormap (function, dirs, m_red, m_green, m_blue, nelem)
char *function;			/* type of colormap */
String *dirs;
unsigned short *m_red;
unsigned short *m_green;
unsigned short *m_blue;
int nelem;
{
	register int i, j;
	register char *ip, *op;
	static int seed = 0;
	int v, vsat, step;
	int knot[7];
	float frac;

	
	if (DBG_RASTER)
	    fprintf (stderr,
		"xim_setColormap: name='%s'  nelem=%d\n", function, nelem);

	vsat = MAX_COLORS - 1;
	step = MAX_COLORS / 6;
	for (i=0;  i < 7;  i++)
	    knot[i] = i * step;
	knot[6] = vsat;

	if (dirs) {
	    /* Load the colormap from a file. */
	    char fname[SZ_FNAME];
	    char lbuf[SZ_LINE];
	    TripletPtr p;
	    Lut user;
	    FILE *fp;

	    /* Get full file pathname.  If the same file is found in multiple
	     * directories the first version found is used.
	     */
	    strcpy (fname, function);
	    for (i=0;  function[0] != '/' && dirs[i];  i++) {
		for (ip=dirs[i], op=fname;  *ip;  )
		    *op++ = *ip++;
		if (op > fname && *(op-1) != '/')
		    *op++ = '/';
		for (ip=function;  *ip;  )
		    *op++ = *ip++;
		*op = '\0';
		if (access (fname, 0) == 0)
		    break;
	    }

	    /* Try to open the file. */
	    if ((fp = fopen (fname, "r")) == NULL) {
		fprintf (stderr, "cannot open %s\n", fname);
		return (ERR);
	    }

	    /* Scan the file.  The user colormap may have any number of
	     * RGB pairs up to a maximum of MAX_COLORS (256).
	     */
	    user.lutlen = 0;
	    while (fgets (lbuf, SZ_LINE, fp)) {
		char *np, *next;
		float v[3];

		/* Ignore blank lines and comment lines. */
		for (ip=lbuf;  isspace(*ip);  ip++)
		    ;
		if (*ip == '\0' || *ip == '#')
		    continue;
		if (user.lutlen+1 >= MAX_COLORS)
		    break;

		/* Get an RGB entry. */
		p = &user.hue[user.lutlen++];
		for (i=0, np=ip;  i < 3;  i++, np=next) {
		    v[i] = strtod (np, &next);
		    if (next == np) {
			fclose (fp);
			return (ERR);
		    }
		}

		p->red = v[0];
		p->green = v[1];
		p->blue = v[2];
	    }

	    fclose (fp);
	    if (user.lutlen <= 0)
		return (ERR);

	    /* Scale the user colormap to fit the NELEMS colors required
	     * for the output colormap.  nelem and lutlen must be the same
	     * to disable scaling and preserve the exact colortable values.
	     */
	    for (i=0;  i < nelem;  i++) {
		float x, w1, w2;
		float r, g, b;

		x = (float)i / (float)(nelem - 1) * (user.lutlen - 1);
		j = max(0, min(user.lutlen-1, (int)x));
		w1 = 1.0 - (x - j);
		w2 = 1.0 - w1;

		r = user.hue[j].red;
		g = user.hue[j].green;
		b = user.hue[j].blue;

		if (w2 > 0.0001 && j+1 < user.lutlen) {
		    r = r * w1 + user.hue[j+1].red * w2;
		    g = g * w1 + user.hue[j+1].green * w2;
		    b = b * w1 + user.hue[j+1].blue * w2;
		}

		m_red[i]   = r * vsat;
		m_green[i] = g * vsat;
		m_blue[i]  = b * vsat;
	    }

	} else if (strncmp (function, "Grayscale", 9) == 0) {
	    for (i=0;  i < nelem;  i++)
		m_red[i] = m_green[i] = m_blue[i] =
		    (float)i / (float)(nelem - 1) * vsat;

	} else if (strncmp (function, "HSV", 3) == 0) {
	    /* HSV: hue varies uniformly from 270 to 360 and back to 270.
	     * Value varies from zero to one using a cube root relation
	     * which causes the value to approach 1.0 rapidly away from zero.
	     * Saturation is zero near the endpoints, causing the curve
	     * to range from black to white at the endpoints, but ranges
	     * to 1.0 at the halfway point, causing nearly saturated colors
	     * in the middle of the range.
	     */
	    for (i=0;  i < nelem;  i++) {
		float h, s, v, r, g, b;
		double pow(), sin();

		frac = 1.0 - ((float)i / (float)(nelem - 1));
		h = frac * 360.0 + 270.0;
		s = abs (sin (frac * 3.1416));
		v = pow ((1.0 - frac), (1.0 / 3.0));

		hsv_to_rgb (h, s, v, &r, &g, &b);
		m_red[i]   = r * vsat;
		m_green[i] = g * vsat;
		m_blue[i]  = b * vsat;
	    }

	} else if (strncmp (function, "Heat", 4) == 0) {
	    for (i=0;  i < nelem;  i++) {
		frac = (float)i / (float)(nelem - 1);
		j = frac * (heat.lutlen - 1);
		m_red[i]   = heat.hue[j].red   * vsat;
		m_green[i] = heat.hue[j].green * vsat;
		m_blue[i]  = heat.hue[j].blue  * vsat;
	    }

	} else if (strncmp (function, "Ramp", 4) == 0) {
	    for (i=0;  i < nelem;  i++) {
		frac = (float)i / (float)(nelem - 1);
		j = frac * (heat.lutlen - 1);
		m_red[i]   = heat.hue[j].red   * (frac * (vsat * 2));
		m_green[i] = heat.hue[j].green * (frac * (vsat * 2));
		m_blue[i]  = heat.hue[j].blue  * (frac * (vsat * 2));
	    }

	} else if (strncmp (function, "AIPS0", 5) == 0) {
	    for (i=0;  i < nelem;  i++) {
		frac = (float)i / (float)(nelem - 1);
		j = frac * (heat.lutlen - 1);
		m_red[i]   = aips0.hue[j].red   * vsat;
		m_green[i] = aips0.hue[j].green * vsat;
		m_blue[i]  = aips0.hue[j].blue  * vsat;
	    }

	} else if (strncmp (function, "Color", 5) == 0) {
	    for (i=0;  i < nelem;  i++) {
		frac = (float)i / (float)(nelem - 1);
		j = frac * (heat.lutlen - 1);
		m_red[i]   = color.hue[j].red   * vsat;
		m_green[i] = color.hue[j].green * vsat;
		m_blue[i]  = color.hue[j].blue  * vsat;
	    }

	} else if (strncmp (function, "Staircase", 9) == 0) {
	    for (i=0;  i < nelem;  i++) {
		frac = (float)i / (float)(nelem - 1);
		j = frac * (heat.lutlen - 1);
		m_red[i]   = staircase.hue[j].red   * vsat;
		m_green[i] = staircase.hue[j].green * vsat;
		m_blue[i]  = staircase.hue[j].blue  * vsat;
	    }

	} else if (strncmp (function, "Standard", 8) == 0) {
	    for (i=0;  i < nelem;  i++) {
		frac = (float)i / (float)(nelem - 1);
		j = frac * (heat.lutlen - 1);
		m_red[i]   = standard.hue[j].red   * vsat;
		m_green[i] = standard.hue[j].green * vsat;
		m_blue[i]  = standard.hue[j].blue  * vsat;
	    }

	} else if (strncmp (function, "Red", 3) == 0) {
	    for (i=0;  i < nelem;  i++) {
		frac = (float)i / (float)(nelem - 1);
		j = frac * (heat.lutlen - 1);
		m_red[i]   = red.hue[j].red   * vsat;
		m_green[i] = red.hue[j].green * vsat;
		m_blue[i]  = red.hue[j].blue  * vsat;
	    }

	} else if (strncmp (function, "Green", 5) == 0) {
	    for (i=0;  i < nelem;  i++) {
		frac = (float)i / (float)(nelem - 1);
		j = frac * (heat.lutlen - 1);
		m_red[i]   = green.hue[j].red   * vsat;
		m_green[i] = green.hue[j].green * vsat;
		m_blue[i]  = green.hue[j].blue  * vsat;
	    }

	} else if (strncmp (function, "Blue", 4) == 0) {
	    for (i=0;  i < nelem;  i++) {
		frac = (float)i / (float)(nelem - 1);
		j = frac * (heat.lutlen - 1);
		m_red[i]   = blue.hue[j].red   * vsat;
		m_green[i] = blue.hue[j].green * vsat;
		m_blue[i]  = blue.hue[j].blue  * vsat;
	    }

	} else if (strncmp (function, "Halley", 6) == 0) {
	    for (i=0;  i < nelem;  i++) {
		frac = (float)i / (float)(nelem - 1);
		j = frac * (heat.lutlen - 1);
		m_red[i]   = halley.hue[j].red   * vsat;
		m_green[i] = halley.hue[j].green * vsat;
		m_blue[i]  = halley.hue[j].blue  * vsat;
	    }

	} else if (strncmp (function, "Rainbow1", 8) == 0) {
	    for (i=0;  i < nelem;  i++)
		m_red[i] = m_green[i] = m_blue[i] = 0;

	    for (i=knot[0];  i <= knot[1];  i++)
		m_blue[i] = vsat * (i - knot[0]) / step;
	    for (i=knot[1];  i <= knot[2];  i++)
		m_blue[i] = vsat;
	    for (i=knot[2];  i <= knot[3];  i++)
		m_blue[i] = vsat * (knot[3] - i) / step;

	    for (i=knot[1];  i <= knot[2];  i++)
		m_green[i] = vsat * (i - knot[1]) / step;
	    for (i=knot[2];  i <= knot[4];  i++)
		m_green[i] = vsat;
	    for (i=knot[4];  i <= knot[5];  i++)
		m_green[i] = vsat * (knot[5] - i) / step;

	    for (i=knot[3];  i <= knot[4];  i++)
		m_red[i] = vsat * (i - knot[3]) / step;
	    for (i=knot[4];  i <= knot[6];  i++)
		m_red[i] = vsat;

	    for (i=knot[5];  i <= knot[6];  i++) {
		if ((v = vsat * (i - knot[5]) / step) > vsat)
		    v = vsat;
		m_green[i] = m_blue[i] = v;
	    }

	} else if (strncmp (function, "Rainbow2", 8) == 0) {
	    for (i=0;  i < nelem;  i++) {
		frac = (float)i / (float)(nelem - 1);
		j = frac * (heat.lutlen - 1);
		m_red[i]   = rainbow.hue[j].red   * vsat;
		m_green[i] = rainbow.hue[j].green * vsat;
		m_blue[i]  = rainbow.hue[j].blue  * vsat;
	    }

	} else if (strncmp (function, "Random16", 8) == 0) {
	    int red, green, blue;
	    
	    if (!seed)
		seed = time(0);
	    srand (seed++);

	    for (i=0;  i < nelem;  ) {
		red   = ((rand() >> 4) % vsat);
		green = ((rand() >> 4) % vsat);
		blue  = ((rand() >> 4) % vsat);

		for (j=0;  i < nelem && j < nelem/16;  j++, i++) {
		    m_red[i]   = red;
		    m_green[i] = green;
		    m_blue[i]  = blue;
		}
	    }

	} else if (strncmp (function, "Random8", 7) == 0) {
	    int red, green, blue;
	    
	    if (!seed)
		seed = time(0);
	    srand (seed++);

	    for (i=0;  i < nelem;  ) {
		red   = ((rand() >> 4) % vsat);
		green = ((rand() >> 4) % vsat);
		blue  = ((rand() >> 4) % vsat);

		for (j=0;  i < nelem && j < nelem/8;  j++, i++) {
		    m_red[i]   = red;
		    m_green[i] = green;
		    m_blue[i]  = blue;
		}
	    }

	} else if (strncmp (function, "Random", 6) == 0) {
	    if (!seed)
		seed = time(0);
	    srand (seed++);
	    for (i=0;  i < nelem;  i++) {
		m_red[i]   = ((rand() >> 4) % vsat);
		m_green[i] = ((rand() >> 4) % vsat);
		m_blue[i]  = ((rand() >> 4) % vsat);
	    }
	}

	/* Scale colormap values to 16 bits. */
	for (i=0;  i < nelem;  i++) {
	    m_red[i]   <<= 8;
	    m_green[i] <<= 8;
	    m_blue[i]  <<= 8;
	}

	return (OK);
}

void
hsv_to_rgb (h, s, v, r, g, b)
float h, s, v;
float *r, *g, *b;
{
	register int i;
	float f, p, q, t;

	while (h >= 360.0)
	    h -= 360.0;

	h /= 60.0;
	i = (int) h;
	f = h - i;
	p = v * (1 - s);
	q = v * (1 - s*f);
	t = v * (1 - s * (1.0 - f));

	switch (i) {
	case 0:
	    *r = v;  *g = t;  *b = p;
	    break;
	case 1:
	    *r = q;  *g = v;  *b = p;
	    break;
	case 2:
	    *r = p;  *g = v;  *b = t;
	    break;
	case 3:
	    *r = p;  *g = q;  *b = v;
	    break;
	case 4:
	    *r = t;  *g = p;  *b = v;
	    break;
	case 5:
	    *r = v;  *g = p;  *b = q;
	    break;
	}
}


/* GET_DIRFILE -- Get the next file name from an open directory file.
 */
static int
get_dirfile (dir, outstr, maxch)
DIR     *dir;
char    *outstr;
int     maxch;
{
        register int    n;
        register struct dirent *dp;
        register char   *ip, *op;
        int     status;

        for (dp = readdir(dir);  dp != NULL;  dp = readdir(dir))
            if (dp->d_ino != 0) {
		n = strlen (dp->d_name);
                status = n;
                for (ip=dp->d_name, op=outstr;  --n >= 0;  )
                    *op++ = *ip++;
                *op = EOS;
                return (status);
            }

        return (EOF);
}


/* LOAD_TESTPATTERN -- Load a test pattern into the given frame.
 */
static void
load_testpattern (xim, frame, type)
XimDataPtr xim;
int frame;
int type;			/* not used */
{
	register FrameBufPtr fb = &xim->frames[frame];
	register int i, j, ncolors;
	int rtype, width, height, depth;
	unsigned char *data;

	if (GtQueryRaster (xim->gt, fb->raster,
		&rtype, &width, &height, &depth) == 0)
	    return;

	data = (unsigned char *) XtMalloc (width);
	if (data == (unsigned char *)NULL)
	    return;

	ncolors = xim->ncolors;
	for (j=0;  j < height;  j++) {
	    for (i=0;  i < width;  i++)
		data[i] = (((i + j) * 10) % ncolors);
	    GtWritePixels (xim->gt, fb->raster, data, 8, 0, j, width, 1);
	}

	XtFree ((char *)data);
}


/* SET_COLORBAR -- Write the colorbar pixels.
 */
static void
set_colorbar (xim, w)
XimDataPtr xim;
Widget w;
{
	register int i;
	static int initialized = 0;
	int first, ngray, rgb_len, rtype, width, height, depth;
	unsigned short m_red[MAX_COLORS];
	unsigned short m_green[MAX_COLORS];
	unsigned short m_blue[MAX_COLORS];
	unsigned char *data;


	if (!w)
	    return;

	if (DBG_RASTER)
	    fprintf (stderr, "SETTING COLORBAR PIXELS...... init = %d\n",
		initialized);

	if (GtQueryRaster (w, 0, &rtype, &width, &height, &depth) == 0)
	    return;

	data = (unsigned char *) XtMalloc (width * height);
	for (i=0;  i < width;  i++)
	    data[i] = ((float)i / (float)(width - 1) * (xim->ncolors - 1));

	for (i=1;  i < height;  i++)
	    memmove (data + i * width, data, width);

	if (!initialized) {
	    xim_iiscolormap (w, m_red,m_green,m_blue, &first, &ngray, &rgb_len);
	    GtWriteColormap (w, 0, first, rgb_len, m_red, m_green, m_blue);

	    xim_setColormap ("Grayscale", NULL, m_red, m_green, m_blue, ngray);
	    GtWriteColormap (w, 0, first, ngray, m_red, m_green, m_blue);

	    xim->ncolors = ngray;
	    initialized++;
	}

	GtWritePixels (w, 0, data, 8, 0, 0, width, height);
	XtFree ((char *)data);

	if (DBG_RASTER)
	    fprintf (stderr, "SETTING COLORBAR PIXELS...... DONE\n");
}


/* SET_NFRAMES -- Called when the number of frame buffers changes.
 */
static void
set_nframes (xim, nframes)
XimDataPtr xim;
int nframes;
{
	xim->nframes = nframes;
	xim_msgi (xim, "nframes", nframes);
}
