/*
 * tkPhoto.c --
 *	Image display widget for use with Tk.
 *
 * Copyright 1993 The Australian National University.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation is hereby granted, provided that the above copyright
 * notice appears in all copies.  This software is provided without any
 * warranty, express or implied. The Australian National University
 * makes no representations about the suitability of this software for
 * any purpose.
 *
 * Author: Paul Mackerras (paulus@cs.anu.edu.au)
 */

#ifndef lint
static char rcsid[] = "$Header: /home/paulus/CaVis/othersrc/tk3.3/RCS/tkPhoto.c,v 1.8 1993/10/20 05:42:39 paulus Exp $";
#endif

#include "tkInt.h"
#include "tkCanvas.h"
#include <math.h>
#include <stdlib.h>
#include <tcl.h>
#include <X11/Xutil.h>
#include "tkPhoto.h"
#include "ctype.h"

#define FALSE	0
#define TRUE	1

#define MIN(a, b)	((a) < (b)? (a): (b))
#define MAX(a, b)	((a) > (b)? (a): (b))

/* Number of bits in a byte */
#ifndef NBBY
#define NBBY		8
#endif

/*
 * Maximum number of bytes of temporary image storage memory to hang
 * on to (per photo widget).  Larger blocks than this are freed
 * immediately after use.
 */
#define MAX_KEEP_IMAGE	20000

/*
 * A signed 8-bit integral type.  If your chars are unsigned and you
 * don't have an ANSI C compiler, you can define schar as some larger
 * signed integer type at the expense of wasting some memory.
 */
#ifdef __STDC__
typedef signed char	schar;
#else
typedef char		schar;
#endif

/*
 * The photo widget no longer draws a 3-D border around the image.
 * Define DO_BORDER to get the photo widget to draw a border.
 * This is normally left undefined because of the problems in keeping
 * the color correct when changing colormaps.  Also, problems arise
 * when there are no colors free in the photo window's colormap for
 * the border to use.
 */

/*
 * Definition of data needed to map 24-bit colour values to
 * pixel values for a particular display and screen.
 * This information is shared among all photo widgets on the same
 * X display and screen.
 */

/*
 * Information identifying a particular palette on a particular screen.
 */
typedef struct {
    Display	*display;	/* display the window's on */
    int		screen;		/* screen number */
    Visual	*visual;	/* X visual to use in the window */
    Tk_Uid	palette;	/* # shades of each color required */
    double	gamma;		/* display gamma to correct for */
    int		flags;		/* MUST_OWN_CMAP, MUST_DFLT_CMAP */
} ColorTableId;

/*
 * Stores color -> pixel value mapping information.
 * These are entered in the `colorHash' table.
 */
typedef struct ColorTable {
    ColorTableId id;		/* display & screen this applies to */
    Colormap	cmap;		/* colormap for displaying image */
    int		flags;		/* NEED_ALLOC, OWN_CMAP */
    int		refcnt;		/* # photos using this map */
    int		ncolors;	/* # colors allocated for this map */

    /* Mapping tables for converting RGB 8-bit values to pixel values */
    unsigned long	red_values[256];
    unsigned long	green_values[256];
    unsigned long	blue_values[256];
    unsigned long	*pixel_map;	/* list of pixel values allocated */

    unsigned char	color_quant[3][256];
} ColorTable;

/*
 * Definition of the data associated with each photo widget.
 */
typedef struct {
    int		flags;		/* see below */
    Tcl_Interp	*interp;	/* interpreter associated with object */

    Tk_Window	tkwin;		/* window for the photo to appear in */
    Display	*display;	/* display the window's on */
    Tk_Uid	palette;	/* specifies color allocations */
    ColorTable	*colors;	/* points to info about mapping colours */
    int		depth;		/* bits/pixel for the window */
    Visual	*visual;	/* X visual for the display window */
    XVisualInfo	vis_info;	/* information about visual */
    Colormap	cmap;		/* current colormap for this window */
    Colormap	defaultCmap;	/* default colormap in this window */
    double	gamma;		/* correct for this display gamma */
    GC		monoGC;		/* GC for putting monochrome images */
    Cursor	cursor;		/* cursor for this window */

    int 	width;		/* actual width of image display area */
    int 	height;		/* actual height of image display area */
    int		reqWidth;	/* requested width of display area */
    int		reqHeight;	/* requested height */
    int		padx;		/* extra width between image and border */
    int		pady;		/* extra height */
    Tk_Uid	blankColorName;	/* name of color for blank areas */
    XColor	blankColor;	/* colour for blank areas of image */
    GC		blankGC;	/* GC for filling in blankColor */
    char	*xScrollCmd;	/* command for X scrolling */
    char	*yScrollCmd;	/* command for Y scrolling */
    char	*geometry;	/* string geometry spec for window */
    int		xorg;		/* X origin of image in window */
    int		yorg;		/* Y origin */
    int		xshift;		/* X image coord at window xorg */
    int		yshift;		/* Y image coord at window yorg */
    int		scanX;		/* X coord given in scan mark cmd */
    int		scanY;		/* Y coord given in scan mark cmd */
    int		scanSpeed;	/* amt to move window for unit mouse mvt */
    Region	region;		/* bits we need to redraw */

    int		xscr_pos;	/* position we last told the x scrollbar */
    int		xscr_wid;	/* width ditto */
    int		xscr_pwid;	/* pixelWidth ditto */
    int		yscr_pos;	/* position we last told the y scrollbar */
    int		yscr_ht;	/* height ditto */
    int		yscr_pht;	/* pixelHeight ditto */

    Pixmap	pixels;		/* backing pixmap */
    int		pixelWidth;	/* width of backing pixmap */
    int		pixelHeight;	/* height of backing pixmap */

    int		ditherLevel;	/* 0=don't, 1=when asked, 2=each block */
    unsigned char *pix24;	/* local storage for 24-bit image */
    schar	*error;		/* error image used in dithering */
    int		ditherX;	/* image is correctly dithered up to */
    int		ditherY;	/* (not including) pixel (ditherX, ditherY) */

    char	*imageGeom;	/* string giving widthxheight of image */
    int		imageWidth;	/* user-declared width of image */
    int		imageHeight;	/* user-declared height */

    XImage	*image;		/* image structure for converted pixels */
    int		imageSize;	/* # bytes available at image->data */

    int		undrawn;	/* # pixels not yet shown on screen */
    int		drawInterval;	/* max # before updating screen */

    char	*ownCmap;	/* string saying whether to use private cmap */

#ifdef DO_BORDER
    Tk_3DBorder	bgBorder;	/* used for drawing a frame around image */
    Tk_3DBorder	activeBorder;	/* used for frame when mouse is in window */
    int		borderWidth;	/* width of border frame */
    int		relief;		/* appearance of border */
#endif /* DO_BORDER */
} Photo;

/*
 * Values for flags
 */
#define MAP_COLORS	1	/* using a pseudo-colour display */
#define OWN_CMAP	2	/* a colormap was allocated for this window */
#define EXPOSED		4	/* seen expose event => can draw in window */
#define REDRAW_FRAME	8	/* some part of frame needs redrawing */
#define REDRAW_IMAGE	0x10	/* some part of image needs redrawing */
#define USER_GEOMETRY	0x20	/* geometry was specified by user */
#define USER_IMAGE_GEOM	0x40	/* image size was specified by user */
#define ACTIVE		0x80	/* the mouse is in the window */
#define MONOCHROME	0x100	/* we're displaying a monochrome image */
#define NEED_ALLOC	0x200	/* need to allocate colors in ColorTable */
#define STRANGE_VISUAL	0x400	/* not using the default visual */
#define REDRAW_REQUESTED 0x800	/* have asked for redraw when idle */
#define BLANK_ALLOCED	0x1000	/* blank color has been allocated */
#define BLANK_CHANGED	0x2000	/* blank color has changed; reallocate it */
#define MUST_DFLT_CMAP	0x4000	/* must use default colormap */
#define MUST_OWN_CMAP	0x8000	/* must use a private colormap */
#define IS_PHOTO_ITEM	0x10000	/* this is a canvas item, not a widget */

/*
 * Values for ditherLevel
 */
#define DITHER_NONE	0
#define DITHER_REQUEST	1
#define DITHER_BLOCKS	2

/*
 * This structure defines the record for each photo canvas item.
 */
typedef struct PhotoItem {
    Tk_Item	header;		/* Generic stuff that's the same for all
				 * types.  MUST BE FIRST IN STRUCTURE. */
    double	x1, y1;		/* upper-left corner of viewport,
				 * in canvas coordinates */
    double	x2, y2;		/* lower-right corner of viewport */
    char	*name;		/* name used to locate this photo item */
    Photo	photo;		/* photo information structure */
} PhotoItem;

/*
 * Colour values for defaults
 */
#define BLACK		"#000000"
#define GREY50		"#808080"
#define BISQUE2		"#eed5b7"

/*
 * Default configuration
 */
#define DEF_PHOTO_BLANK		BLACK
#define DEF_PHOTO_CURSOR	((char *) NULL)
#define DEF_PHOTO_DITHER_LEVEL	"-1"
#define DEF_PHOTO_DRAWINTERVAL	"5000"
#define DEF_PHOTO_GAMMA		"1"
#define DEF_PHOTO_GEOMETRY	((char *) NULL)
#define DEF_PHOTO_IMAGE_GEOM	((char *) NULL)
#define DEF_PHOTO_OWN_CMAP	"ifneeded"
#define DEF_PHOTO_PADX		"0"
#define DEF_PHOTO_PADY		"0"
#define DEF_PHOTO_PALETTE	""
#define DEF_PHOTO_SCANSPEED	"10"
#define DEF_PHOTO_VISUAL	"best"

#ifdef DO_BORDER
#define DEF_PHOTO_ACTIVE_BG	BISQUE2
#define DEF_PHOTO_BG		GREY50
#define DEF_PHOTO_BORDER_WIDTH	"0"
#define DEF_PHOTO_RELIEF	"sunken"
#endif

#define DEFAULT_WIDTH		256
#define DEFAULT_HEIGHT		256

/*
 * Information used for parsing configuration options for photo widget
 */
static Tk_ConfigSpec configSpecs[] = {
#ifdef DO_BORDER
    {TK_CONFIG_BORDER, "-activebackground", "activeBackground", "Foreground",
	 DEF_PHOTO_ACTIVE_BG, Tk_Offset(Photo, activeBorder), 0},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	 DEF_PHOTO_BG, Tk_Offset(Photo, bgBorder), 0},
    {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
	 (char *) NULL, 0, 0},
    {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
	 (char *) NULL, 0, 0},
#endif /* DO_BORDER */
    {TK_CONFIG_UID, "-blank", "blank", "Blank",
	 DEF_PHOTO_BLANK, Tk_Offset(Photo, blankColorName), 0},
#ifdef DO_BORDER
    {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
	 DEF_PHOTO_BORDER_WIDTH, Tk_Offset(Photo, borderWidth), 0},
#endif /* DO_BORDER */
    {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
	 DEF_PHOTO_CURSOR, Tk_Offset(Photo, cursor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_INT, "-ditherlevel", "ditherLevel", "DitherLevel",
	 DEF_PHOTO_DITHER_LEVEL, Tk_Offset(Photo, ditherLevel), 0},
    {TK_CONFIG_INT, "-drawinterval", "drawinterval", "DrawInterval",
	 DEF_PHOTO_DRAWINTERVAL, Tk_Offset(Photo, drawInterval), 0},
    {TK_CONFIG_DOUBLE, "-gamma", "gamma", "Gamma",
	 DEF_PHOTO_GAMMA, Tk_Offset(Photo, gamma), 0},
    {TK_CONFIG_STRING, "-geometry", "geometry", "Geometry",
	 DEF_PHOTO_GEOMETRY, Tk_Offset(Photo, geometry), TK_CONFIG_NULL_OK},
    {TK_CONFIG_STRING, "-imagesize", "imageSize", "Geometry",
	 DEF_PHOTO_IMAGE_GEOM, Tk_Offset(Photo, imageGeom), TK_CONFIG_NULL_OK},
    {TK_CONFIG_STRING, "-owncmap", "ownCmap", "OwnCmap",
	 DEF_PHOTO_OWN_CMAP, Tk_Offset(Photo, ownCmap), 0},
    {TK_CONFIG_PIXELS, "-padx", "padX", "Pad",
	 DEF_PHOTO_PADX, Tk_Offset(Photo, padx), 0},
    {TK_CONFIG_PIXELS, "-pady", "padY", "Pad",
	 DEF_PHOTO_PADY, Tk_Offset(Photo, pady), 0},
    {TK_CONFIG_UID, "-palette", "palette", "Palette",
	 DEF_PHOTO_PALETTE, Tk_Offset(Photo, palette), 0},
#ifdef DO_BORDER
    {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
	 DEF_PHOTO_RELIEF, Tk_Offset(Photo, relief), 0},
#endif /* DO_BORDER */
    {TK_CONFIG_INT, "-scanspeed", "scanSpeed", "ScanSpeed",
	 DEF_PHOTO_SCANSPEED, Tk_Offset(Photo, scanSpeed), 0},
    {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
	 (char *) NULL, Tk_Offset(Photo, xScrollCmd), 0},
    {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
	 (char *) NULL, Tk_Offset(Photo, yScrollCmd), 0},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
	 (char *) NULL, 0, 0}
};

/*
 * Information used for parsing configuration options for photo canvas items
 */
static Tk_ConfigSpec itemConfigSpecs[] = {
    {TK_CONFIG_UID, "-blank", "blank", "Blank",
	 DEF_PHOTO_BLANK, Tk_Offset(PhotoItem, photo.blankColorName), 0},
    {TK_CONFIG_INT, "-ditherlevel", "ditherLevel", "DitherLevel",
	 DEF_PHOTO_DITHER_LEVEL, Tk_Offset(PhotoItem, photo.ditherLevel), 0},
    {TK_CONFIG_INT, "-drawinterval", "drawinterval", "DrawInterval",
	 DEF_PHOTO_DRAWINTERVAL, Tk_Offset(PhotoItem, photo.drawInterval), 0},
    {TK_CONFIG_DOUBLE, "-gamma", "gamma", "Gamma",
	 DEF_PHOTO_GAMMA, Tk_Offset(PhotoItem, photo.gamma), 0},
    {TK_CONFIG_STRING, "-imagesize", "imageSize", "Geometry",
	 DEF_PHOTO_IMAGE_GEOM, Tk_Offset(PhotoItem, photo.imageGeom),
	 TK_CONFIG_NULL_OK},
    {TK_CONFIG_UID, "-palette", "palette", "Palette",
	 DEF_PHOTO_PALETTE, Tk_Offset(PhotoItem, photo.palette), 0},
    {TK_CONFIG_INT, "-xshift", "xShift", "XShift",
	 "0", Tk_Offset(PhotoItem, photo.xshift), 0},
    {TK_CONFIG_INT, "-yshift", "yShift", "YShift",
	 "0", Tk_Offset(PhotoItem, photo.yshift), 0},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
	 (char *) NULL, 0, 0}
};

/*
 * Hash table used to provide access to photos from C code.
 */
static Tcl_HashTable photoHash;
static int hash_inited;		/* set when Tcl_InitHashTable done */

/*
 * Hash table used to hash from display/screen/visual/palette/gamma
 * to ColorTable info.
 */
static Tcl_HashTable colorHash;
static int color_hash_inited;
#define N_COLOR_HASH	(sizeof(ColorTableId) / sizeof(int))

/*
 * Hash table used to hash from display/screen/visual to default
 * colormap (for visuals other than the default).
 */
static Tcl_HashTable defaultHash;
static int default_hash_inited;
#define N_DEFAULT_HASH	N_COLOR_HASH

/*
 * Forward declarations
 */
static Photo *		MakePhotoWindow _ANSI_ARGS_((Tcl_Interp *interp,
			    Tk_Window tkwin, char *path, char *vis_opt));
static int		SetupPhoto _ANSI_ARGS_((Photo *php, char *name,
			    char *vis_opt));
static int		PhotoWidgetCmd _ANSI_ARGS_((ClientData clientData,
			    Tcl_Interp *interp, int argc, char **argv));
static int		ConfigurePhoto _ANSI_ARGS_((Tcl_Interp *interp,
			    Photo *phPtr, char *widgRec, int argc, char **argv,
			    Tk_ConfigSpec *specs, int flags));
static void		ComputePhotoGeometry _ANSI_ARGS_((Photo *photoPtr));
static void		ReallocatePixmap _ANSI_ARGS_((Photo *photoPtr,
			    int width, int height));
static void		DestroyPhoto _ANSI_ARGS_((ClientData clientData));
static int		GetVisualInfo _ANSI_ARGS_((Tcl_Interp *interp,
			    Photo *php, char *visual_option));
static int		CountBits _ANSI_ARGS_((unsigned long mask));
static int		SetPalette _ANSI_ARGS_((Photo *php));
static void		GetColorTable _ANSI_ARGS_((Photo *php));
static void		InstallCmap _ANSI_ARGS_((Photo *php));
static int		AllocateColors _ANSI_ARGS_((Photo *php));
static int		ReclaimColors _ANSI_ARGS_((ColorTableId *id,
			    int ncolors));
static void		AllocateBlankColor _ANSI_ARGS_((Photo *php));
static void		DoScroll _ANSI_ARGS_((Photo *php, int x, int y));
static void		DoScrollCmds _ANSI_ARGS_((Photo *php));
static void		RedrawImage _ANSI_ARGS_((Photo *php, int x, int y,
			    int w, int h));
static void		RedrawPhoto _ANSI_ARGS_((Photo *php, int x, int y,
			    int w, int h));
static void		RedrawPhotoFrame _ANSI_ARGS_((Photo *php));
static void		UpdatePhoto _ANSI_ARGS_((ClientData clientData));
static void		DisplayPhoto _ANSI_ARGS_((Photo *php));
static void		PhotoEventProc _ANSI_ARGS_((ClientData clientData,
			    XEvent *event));
static void		AllocImage _ANSI_ARGS_((Photo *php, int width,
			    int height));
static void		Dither _ANSI_ARGS_((Photo *php, int x, int y,
			    int w, int h));

static void		ComputePhotoItemBbox _ANSI_ARGS_((Tk_Canvas *canvasPtr,
			    PhotoItem *vportPtr));
static int		ConfigurePhotoItem _ANSI_ARGS_((
			    Tk_Canvas *canvasPtr, Tk_Item *itemPtr, int argc,
			    char **argv, int flags));
static int		CreatePhotoItem _ANSI_ARGS_((Tk_Canvas *canvasPtr,
			    struct Tk_Item *itemPtr, int argc, char **argv));
static void		DeletePhotoItem _ANSI_ARGS_((Tk_Canvas *canvasPtr,
			    Tk_Item *itemPtr));
static void		DisplayPhotoItem _ANSI_ARGS_((Tk_Canvas *canvasPtr,
			    Tk_Item *itemPtr, Drawable dst));
static int		PhotoItemCoords _ANSI_ARGS_((Tk_Canvas *canvasPtr,
			    Tk_Item *itemPtr, int argc, char **argv));
static int		PhotoItemToArea _ANSI_ARGS_((Tk_Canvas *canvasPtr,
			    Tk_Item *itemPtr, double *rectPtr));
static double		PhotoItemToPoint _ANSI_ARGS_((Tk_Canvas *canvasPtr,
			    Tk_Item *itemPtr, double *pointPtr));
static void		ScalePhotoItem _ANSI_ARGS_((Tk_Canvas *canvasPtr,
			    Tk_Item *itemPtr, double originX, double originY,
			    double scaleX, double scaleY));
static void		TranslatePhotoItem _ANSI_ARGS_((Tk_Canvas *canvasPtr,
			    Tk_Item *itemPtr, double deltaX, double deltaY));

/*
 * The structures below defines the photo item type by means
 * of procedures that can be invoked by generic item code.
 */

Tk_ItemType TkPhotoItemType = {
    "photo",				/* name */
    sizeof(PhotoItem),			/* itemSize */
    CreatePhotoItem,			/* createProc */
    itemConfigSpecs,			/* configSpecs */
    ConfigurePhotoItem,			/* configureProc */
    PhotoItemCoords,			/* coordProc */
    DeletePhotoItem,			/* deleteProc */
    DisplayPhotoItem,			/* displayProc */
    0,					/* alwaysRedraw */
    PhotoItemToPoint,			/* pointProc */
    PhotoItemToArea,			/* areaProc */
    (Tk_ItemPostscriptProc *) NULL,	/* postscriptProc */
    ScalePhotoItem,			/* scaleProc */
    TranslatePhotoItem,			/* translateProc */
    (Tk_ItemIndexProc *) NULL,		/* indexProc */
    (Tk_ItemCursorProc *) NULL,		/* icursorProc */
    (Tk_ItemSelectionProc *) NULL,	/* selectionProc */
    (Tk_ItemInsertProc *) NULL,		/* insertProc */
    (Tk_ItemDCharsProc *) NULL,		/* dTextProc */
    (Tk_ItemType *) NULL		/* nextPtr */
};

/*
 *----------------------------------------------------------------------
 *
 * Tk_PhotoCmd --
 *
 *	This procedure is invoked to process the "photo" Tcl
 *	command.  See the user documentation for details on what
 *	it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
Tk_PhotoCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* main window of interpreter */
    Tcl_Interp *interp;		/* current interpreter */
    int argc;			/* number of arguments */
    char **argv;		/* arguments */
{
    Photo *php;
    int i, l;
    char *vis_opt;

    if( argc < 2 ){
	Tcl_AppendResult(interp, "usage: ", argv[0], " pathName ?options?",
			 (char *) NULL);
	return TCL_ERROR;
    }

    vis_opt = NULL;
    i = 2;
    if( argc >= 4 ){
	l = strlen(argv[2]);
	if( l >= 2 && strncmp(argv[2], "-visual", l) == 0 ){
	    /* trim off the special -visual option */
	    vis_opt = argv[3];
	    i = 4;
	}
    }

    php = MakePhotoWindow(interp, (Tk_Window) clientData, argv[1], vis_opt);
    if( php == NULL )
	return TCL_ERROR;

    if( ConfigurePhoto(interp, php, (char *) php,
		       argc-i, argv+i, configSpecs, 0) != TCL_OK ){
	Tk_DestroyWindow(php->tkwin);
	return TCL_ERROR;
    }

    interp->result = Tk_PathName(php->tkwin);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * MakePhotoWindow --
 *
 *	This procedure sets up the data structures for a new photo
 *	window.
 *
 * Results:
 *	A pointer to the Photo structure for the newly-created window,
 *	or NULL if there was an error.
 *
 * Side effects:
 *	Data structures get set up, the new window gets entered in
 *	various tables, etc.
 *
 *----------------------------------------------------------------------
 */

static Photo *
MakePhotoWindow(interp, tkwin, path, vis_opt)
    Tcl_Interp *interp;		/* for error reporting */
    Tk_Window tkwin;		/* interpret path wrt this */
    char *path;			/* name for new window */
    char *vis_opt;		/* -visual option value, if given */
{
    register Photo *php;
    Tk_Window new;

    /*
     * Create the new window
     */
    new = Tk_CreateWindowFromPath(interp, tkwin, path, (char *) NULL);
    if( new == NULL )
	return NULL;
    Tk_SetClass(new, "Photo");

    /*
     * Allocate a data structure and set default values.
     */
    php = (Photo *) ckalloc(sizeof(Photo));
    bzero(php, sizeof(Photo));
    php->interp = interp;
    php->tkwin = new;
    if( SetupPhoto(php, path, vis_opt) != TCL_OK ){
	Tk_DestroyWindow(new);
	ckfree((char *) php);
	return NULL;
    }

    /*
     * Set up an event handler and widget command for this window.
     */
    Tk_CreateEventHandler(new, ExposureMask | StructureNotifyMask
			       | EnterWindowMask | LeaveWindowMask,
			  PhotoEventProc, (ClientData) php);
    Tcl_CreateCommand(interp, Tk_PathName(new), PhotoWidgetCmd,
		      (ClientData) php, (void (*)()) NULL);

    return php;
}

/*
 *----------------------------------------------------------------------
 *
 * SetupPhoto --
 *
 *	This procedure sets up the data structures for a new photo,
 *	whether a photo widget or a photo canvas item.
 *
 * Results:
 *	A standard Tcl return value.
 *
 * Side effects:
 *	Fields of *php get initialized.
 *
 *----------------------------------------------------------------------
 */

static int
SetupPhoto(php, name, vis_opt)
    register Photo *php;
    char *name;
    char *vis_opt;
{
    Tk_Window w;
    XColor *black, *white;
    Tcl_HashEntry *entry;
    int hnew;
    union {			/* for determining big/little endianness */
	int	i;
	char	c[sizeof(int)];
    } kludge;
    XGCValues gcValues;

    w = php->tkwin;
    php->display = Tk_Display(w);
    php->palette = NULL;
    php->gamma = 1.0;
    php->colors = NULL;
    php->monoGC = None;
    php->cursor = None;
    php->reqWidth = DEFAULT_WIDTH;
    php->reqHeight = DEFAULT_HEIGHT;
    php->blankColorName = NULL;
    php->blankGC = None;
    php->region = None;
    php->xScrollCmd = NULL;
    php->yScrollCmd = NULL;
    php->pixels = None;
    php->pix24 = NULL;
    php->error = NULL;
    php->geometry = NULL;
    php->imageGeom = NULL;
#ifdef DO_BORDER
    php->bgBorder = NULL;
    php->activeBorder = NULL;
    php->borderWidth = 0;
#endif

    /*
     * Get information about the visual we'll use.
     */
    if( GetVisualInfo(php->interp, php, vis_opt) != TCL_OK )
	return TCL_ERROR;

    /*
     * Make a GC with background = black and foreground = white.
     */
    white = Tk_GetColor(php->interp, w, None, "white");
    black = Tk_GetColor(php->interp, w, None, "black");
    gcValues.foreground = (white != NULL)? white->pixel:
			  WhitePixelOfScreen(Tk_Screen(w));
    gcValues.background = (black != NULL)? black->pixel:
			  BlackPixelOfScreen(Tk_Screen(w));
    php->monoGC = Tk_GetGC(w, GCForeground|GCBackground, &gcValues);

    /*
     * Create an image structure for sending image data to the X server.
     */
    php->image = XCreateImage(php->display, php->visual, php->depth,
			      php->depth > 1? ZPixmap: XYBitmap,
			      0, (char *) NULL, 1, 1, 32, 0);
    if( php->image == NULL ){
	Tcl_AppendResult(php->interp, "allocation failure in XCreateImage",
			 (char *) NULL);
	return TCL_ERROR;
    }

    /*
     * Work out depth and endianness of image.
     */
    php->image->bits_per_pixel = php->depth == 1? 1: php->depth <= 8? 8: 32;
    kludge.i = 0;
    kludge.c[0] = 1;
    php->image->byte_order = (kludge.i == 1)? LSBFirst: MSBFirst;
    php->image->bitmap_unit = sizeof(unsigned long) * NBBY;
    _XInitImageFuncPtrs(php->image);

    /*
     * Enter this window in the hash table.
     */
    if( !hash_inited ){
	Tcl_InitHashTable(&photoHash, TCL_STRING_KEYS);
	hash_inited = 1;
    }
    entry = Tcl_CreateHashEntry(&photoHash, name, &hnew);
    Tcl_SetHashValue(entry, php);

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * PhotoWidgetCmd --
 *
 *	This procedure is invoked to process the Tcl command
 *	that corresponds to a photo widget.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

static int
PhotoWidgetCmd(clientData, interp, argc, argv)
    ClientData clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    register Photo *php;
    char *option;
    int c, length, result, i;
    int x, y, w, h, dw, dh;
    XColor color;
    int lac;
    char **lav;
    unsigned char *imp;
    Colormap cmap;
    char string[24];
    XWindowAttributes attrs;
    PhotoImage block;

    if( argc < 2 ){
	Tcl_AppendResult(interp, "wrong # args: should be \"",
			 argv[0], " option ?arg ...?\"", (char *) NULL);
	return TCL_ERROR;
    }

    php = (Photo *) clientData;
    Tk_Preserve((ClientData) php);

    option = argv[1];
    c = option[0];
    length = strlen(option);
    result = TCL_OK;

    if( c == 'b' && strncmp(option, "blank", length) == 0 ){
	/* blank the image */
	if( argc == 2 )
	    PhotoBlank(php);
	else {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
			     argv[0], " blank\"", (char *) NULL);
	    result = TCL_ERROR;
	}

    } else if( c == 'c' && strncmp(option, "configure", length) == 0 ){
	/* change or report the photo window configuration */
	if( argc == 2 )
	    result = Tk_ConfigureInfo(interp, php->tkwin, configSpecs,
				      (char *) php, (char *) NULL, 0);
	else if( argc == 3 )
	    result = Tk_ConfigureInfo(interp, php->tkwin, configSpecs,
				      (char *) php, argv[2], 0);
	else
	    result = ConfigurePhoto(interp, php, (char *) php,
				    argc-2, argv+2, configSpecs,
				    TK_CONFIG_ARGV_ONLY);

    } else if( c == 'd' && length >= 2
	      && strncmp(option, "dither", length) == 0 ){
	/* dither a portion of the image */
	if( argc == 2 || argc == 6 ){
	    if( argc == 2 ){
		x = y = 0;
		w = php->pixelWidth;
		h = php->pixelHeight;
	    } else {
		if( Tcl_GetInt(interp, argv[2], &x) != TCL_OK
		   || Tcl_GetInt(interp, argv[3], &y) != TCL_OK
		   || Tcl_GetInt(interp, argv[4], &w) != TCL_OK
		   || Tcl_GetInt(interp, argv[5], &h) != TCL_OK )
		    result = TCL_ERROR;
		else if( x < 0 || y < 0 || w <= 0 || h <= 0
			|| x + w > php->pixelWidth
			|| y + h > php->pixelHeight ){
		    Tcl_AppendResult(interp, argv[0], " dither: coordinates",
				     " not contained in image", (char *) NULL);
		    result = TCL_ERROR;
		}
	    }
	    if( result == TCL_OK ){
		if( php->ditherLevel == DITHER_NONE ){
		    Tcl_AppendResult(interp, argv[0], ": ",
				     "Dithering not available", (char *) NULL);
		    result = TCL_ERROR;
		} else
		    Dither(php, x, y, w, h);
	    }
	} else {
	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
			     " dither ?x y width height?", (char *) NULL);
	    result = TCL_ERROR;
	}

    } else if( c == 'p' && strncmp(option, "put", length) == 0 ){
	/* put pixels into the image */
	if( argc == 9 ){
	    if( Tcl_GetInt(interp, argv[2], &x) != TCL_OK
	       || Tcl_GetInt(interp, argv[3], &y) != TCL_OK
	       || Tcl_GetInt(interp, argv[4], &w) != TCL_OK
	       || Tcl_GetInt(interp, argv[5], &h) != TCL_OK
	       || Tcl_GetInt(interp, argv[6], &dw) != TCL_OK
	       || Tcl_GetInt(interp, argv[7], &dh) != TCL_OK
	       || Tcl_SplitList(interp, argv[8], &lac, &lav) != TCL_OK )
		result = TCL_ERROR;
	    else if( lac != dw * dh ){
		Tcl_AppendResult(interp, "color list has wrong # elements: ",
				 argv[6], "*", argv[7], " elements required",
				 (char *) NULL);
		free((char *) lav);
		result = TCL_ERROR;
	    } else {

		block.ptr = imp = (unsigned char *) ckalloc(lac * 3);
		cmap = DefaultColormap(php->display,
				       Tk_ScreenNumber(php->tkwin));
		for( i = 0; i < lac; ++i ){
		    if( !XParseColor(php->display, cmap, lav[i], &color) ){
			Tcl_AppendResult(interp, "can't parse color \"",
					 lav[i], "\"", (char *) NULL);
			result = TCL_ERROR;
			break;
		    }
		    *imp++ = color.red >> 8;
		    *imp++ = color.green >> 8;
		    *imp++ = color.blue >> 8;
		}

		if( result == TCL_OK ){
		    block.width = dw;
		    block.height = dh;
		    block.pitch = dw * 3;
		    block.pixel_size = 3;
		    block.comp_off[0] = 0;
		    block.comp_off[1] = 1;
		    block.comp_off[2] = 2;
		    PhotoPutBlock((ClientData)php, &block, x, y, w, h);
		}

		ckfree((char *) block.ptr);
		free((char *) lav);
	    }

	} else {
	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
			     " put x y w h data-width data-height {colors}",
			     (char *) NULL);
	    result = TCL_ERROR;
	}

    } else if( c == 'r' && strncmp(option, "redither", length) == 0 ){
	/* redither as much of the whole image as necessary */
	if( argc == 2 ){
	    if( php->ditherLevel > DITHER_NONE ){
		if( php->ditherX != 0 )
		    Dither(php, php->ditherX, php->ditherY,
				php->pixelWidth - php->ditherX, 1);
		if( php->ditherY < php->pixelHeight )
		    Dither(php, 0, php->ditherY, php->pixelWidth,
				php->pixelHeight - php->ditherY);
	    }
	} else {
	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
			     " redither", (char *) NULL);
	    result = TCL_ERROR;
	}

    } else if( c == 's' && strncmp(option, "scan", length) == 0 ){
	/* move the image within the window */
	if( argc != 5 ){
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " scan mark|dragto x y\"", (char *) NULL);
	    result = TCL_ERROR;
	} else if( Tcl_GetInt(interp, argv[3], &x) != TCL_OK
		  || Tcl_GetInt(interp, argv[4], &y) != TCL_OK ){
	    result = TCL_ERROR;
	} else if( argv[2][0] == 'm'
		  && strncmp(argv[2], "mark", strlen(argv[2])) == 0 ){
	    php->scanX = x;
	    php->scanY = y;
	} else if( argv[2][0] == 'd'
		  && strncmp(argv[2], "dragto", strlen(argv[2])) == 0 ){
	    DoScroll(php, php->xshift - (x - php->scanX) * php->scanSpeed,
			  php->yshift - (y - php->scanY) * php->scanSpeed);
	    php->scanX = x;
	    php->scanY = y;
	} else {
	    Tcl_AppendResult(interp, "bad scan option \"", argv[2],
		    "\":  must be mark or dragto", (char *) NULL);
	    result = TCL_ERROR;
	}

    } else if( c == 'x' && strncmp(option, "xview", length) == 0 ){
	/* scroll to a given image X coord at left window edge */
	if( argc == 3 ){
	    if( Tcl_GetInt(interp, argv[2], &x) == TCL_OK ){
		php->xscr_pos = -1; /* make sure we do xscroll cmd */
		DoScroll(php, x, php->yshift);
	    } else
		result = TCL_ERROR;
	} else if( argc == 2 ){
	    sprintf(string, "%d", php->xshift);
	    Tcl_AppendResult(interp, string, (char *) NULL);
	} else {
	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
			     " xscroll ?x-coord?\"", (char *) NULL);
	    result = TCL_ERROR;
	}

    } else if( c == 'y' && strncmp(option, "yview", length) == 0 ){
	/* scroll to a given image Y coord at left window edge */
	if( argc == 3 ){
	    if( Tcl_GetInt(interp, argv[2], &y) == TCL_OK ){
		php->yscr_pos = -1; /* make sure we do yscroll cmd */
		DoScroll(php, php->xshift, y);
	    } else
		result = TCL_ERROR;
	} else if( argc == 2 ){
	    sprintf(string, "%d", php->yshift);
	    Tcl_AppendResult(interp, string, (char *) NULL);
	} else {
	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
			     " yscroll ?y-coord?\"", (char *) NULL);
	    result = TCL_ERROR;
	}

    } else {
	Tcl_AppendResult(interp, "bad option \"", option,
			 "\": must be blank, configure, dither, redither,",
			 " scan, xview or yview", (char *) NULL);
	result = TCL_ERROR;
    }

    Tk_Release((ClientData) php);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigurePhoto --
 *
 *	This procedure is called to process an argv/argc list, plus
 *	the Tk option database, in order to configure (or reconfigure)
 *	a photo widget.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then interp->result contains an error message.
 *
 * Side effects:
 *	Configuration information, such as size, palette,
 *	etc. get set for php;  old resources get freed, if there
 *	were any.  The photo is redisplayed if necessary.
 *
 *----------------------------------------------------------------------
 */

static int
ConfigurePhoto(interp, php, widgRec, argc, argv, specs, flags)
    Tcl_Interp *interp;
    register Photo *php;
    char *widgRec;
    int argc;
    char **argv;
    Tk_ConfigSpec *specs;
    int flags;
{
    int w, h, result;
    GC newGC;
    Tk_Uid oldPalette, oldBlank, oldOwnCmap;
    double oldGamma;
    unsigned long oldBlankPixel;
    char geomString[64];
    XGCValues gcValues;

    oldPalette = php->palette;
    oldGamma = php->gamma;
    oldBlank = php->blankColorName;
    oldBlankPixel = php->blankColor.pixel;
    oldOwnCmap = php->ownCmap;

    if( Tk_ConfigureWidget(interp, php->tkwin, specs, argc, argv,
			   widgRec, flags) != TCL_OK )
	return TCL_ERROR;

    result = TCL_OK;
    if( php->blankColorName != oldBlank ){
	if( !XParseColor(php->display, php->defaultCmap, php->blankColorName,
			 &php->blankColor) ){
	    Tcl_AppendResult(interp, "unknown color name \"",
			     php->blankColorName, "\"", (char *) NULL);
	    result = TCL_ERROR;
	    /* default to black - we know its component values */
	    php->blankColor.red = 0;
	    php->blankColor.green = 0;
	    php->blankColor.blue = 0;
	    php->blankColorName = Tk_GetUid("black");
	}
	php->flags |= BLANK_CHANGED;
    }

    if( (php->flags & IS_PHOTO_ITEM) == 0 ){
	if( (php->geometry == NULL || php->geometry[0] == 0) ){
	    php->flags &= ~USER_GEOMETRY;
	    if( (php->flags & USER_IMAGE_GEOM) != 0 ){
		php->reqWidth = php->imageWidth;
		php->reqHeight = php->imageHeight;
	    } else if( php->pixels != None ){
		php->reqWidth = php->pixelWidth;
		php->reqHeight = php->pixelHeight;
	    }
	} else {
	    if( sscanf(php->geometry, "%dx%d", &w, &h) == 2 ){
		if( w > 0 && h > 0 ){
		    php->reqWidth = w;
		    php->reqHeight = h;
		    php->flags |= USER_GEOMETRY;
		} else {
		    Tcl_AppendResult(interp, "bad geometry \"", php->geometry,
				     "\": width and height must be positive",
				     (char *) NULL);
		    ckfree(php->geometry);
		    php->geometry = NULL;
		    result = TCL_ERROR;
		}
	    } else {
		Tcl_AppendResult(interp, "bad geometry \"", php->geometry,
				 "\": expected widthxheight", (char *) NULL);
		ckfree(php->geometry);
		php->geometry = NULL;
		result = TCL_ERROR;
	    }
	}
    }

    if( php->imageGeom == NULL || php->imageGeom[0] == 0 )
	php->flags &= ~USER_IMAGE_GEOM;
    else {
	if( sscanf(php->imageGeom, "%dx%d", &w, &h) == 2 ){
	    if( w > 0 && h > 0 ){
		php->imageWidth = w;
		php->imageHeight = h;
		php->flags |= USER_IMAGE_GEOM;
		if( (php->flags & USER_GEOMETRY) == 0 ){
		    php->reqWidth = w;
		    php->reqHeight = h;
		}
	    } else {
		Tcl_AppendResult(interp, "bad image geometry \"",
				 php->imageGeom,
				 "\": width and height must be positive",
				 (char *) NULL);
		ckfree(php->imageGeom);
		php->imageGeom = NULL;
		result = TCL_ERROR;
	    }
	} else {
	    Tcl_AppendResult(interp, "bad image geometry \"", php->imageGeom,
			     "\": expected widthxheight", (char *) NULL);
	    ckfree(php->imageGeom);
	    php->imageGeom = NULL;
	    result = TCL_ERROR;
	}
    }

    if( php->gamma <= 0 ){
	Tcl_AppendResult(interp, "gamma value must be positive",
			 (char *) NULL);
	php->gamma = oldGamma;
	result = TCL_ERROR;
    }

    if( oldOwnCmap != php->ownCmap ){
	if( strcmp(php->ownCmap, "always") == 0 ){
	    if( (php->flags & MUST_OWN_CMAP) == 0 ){
		php->flags &= ~MUST_DFLT_CMAP;
		php->flags |= MUST_OWN_CMAP;
	    }
	} else if( strcmp(php->ownCmap, "never") == 0 ){
	    if( (php->flags & MUST_DFLT_CMAP) == 0 ){
		php->flags &= ~MUST_OWN_CMAP;
		php->flags |= MUST_DFLT_CMAP;
	    }
	} else {
	    php->flags &= ~(MUST_DFLT_CMAP | MUST_OWN_CMAP);
	}
    }

    if( oldPalette == NULL || php->palette != oldPalette
       || php->gamma != oldGamma || php->ownCmap != oldOwnCmap )
	if( SetPalette(php) != TCL_OK ){
	    result = TCL_ERROR;
	    /*
	     * Failed to install new palette; try old value.
	     * If that fails, try the default, then 2/2/2 (if color),
	     * then 2.
	     */
	    php->palette = oldPalette;
	    php->gamma = oldGamma;
	    if( oldPalette == NULL || SetPalette(php) != TCL_OK ){
		/* try default palette */
		php->palette = "";
		php->gamma = 1.0;
		if( (oldPalette != NULL && oldPalette[0] == 0)
		   || SetPalette(php) != TCL_OK ){
		    php->palette = "2/2/2";
		    if( (php->flags & MONOCHROME) != 0
		       || SetPalette(php) != TCL_OK ){
			php->palette = "2";
			SetPalette(php);	/* this better not fail */
		    }
		}
	    }
	}


    if( (php->flags & BLANK_CHANGED) != 0 )
	AllocateBlankColor(php);

    if( php->blankGC == None || php->blankColor.pixel != oldBlankPixel ){
	gcValues.foreground = php->blankColor.pixel;
	newGC = Tk_GetGC(php->tkwin, GCForeground, &gcValues);
	if( php->blankGC != None )
	    Tk_FreeGC(php->display, php->blankGC);
	php->blankGC = newGC;
	if( (php->flags & IS_PHOTO_ITEM) == 0 )
	    Tk_SetWindowBackground(php->tkwin, php->blankColor.pixel);
    }

    if( php->ditherLevel < DITHER_NONE || php->ditherLevel > DITHER_BLOCKS )
	if( (php->flags & MAP_COLORS) != 0 || (php->flags & MONOCHROME) != 0 )
	    php->ditherLevel = DITHER_BLOCKS;
	else
	    php->ditherLevel = DITHER_NONE;

    ComputePhotoGeometry(php);

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ComputePhotoGeometry --
 *
 *	After changes in a photo widget's size or dither-level, this
 *	procedure recomputes the photo's geometry and storage
 *	requirements.  The geometry information is passed along to
 *	the geometry manager for the window.  Image storage gets
 *	freed or reallocated as necessary.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The photo's window may change size; Tcl commands may be
 *	executed to adjust any associated scrollbars.
 *
 *----------------------------------------------------------------------
 */

static void
ComputePhotoGeometry(php)
Photo *php;
{
    int b, w, h;
    int oldw, oldh, oldx, oldy;

    oldw = php->width;
    oldh = php->height;
    oldx = php->xorg;
    oldy = php->yorg;

    if( (php->flags & IS_PHOTO_ITEM) == 0 ){
#ifdef DO_BORDER
	Tk_GeometryRequest(php->tkwin,
			   php->reqWidth + 2 * php->borderWidth + php->padx,
			   php->reqHeight + 2 * php->borderWidth + php->pady);
	Tk_SetInternalBorder(php->tkwin, php->borderWidth);
	php->width = Tk_Width(php->tkwin) - 2 * php->borderWidth;
	php->height = Tk_Height(php->tkwin) - 2 * php->borderWidth;
#else  /* !DO_BORDER */
	Tk_GeometryRequest(php->tkwin,
			   php->reqWidth + php->padx,
			   php->reqHeight+ php->pady);
	php->width = Tk_Width(php->tkwin);
	php->height = Tk_Height(php->tkwin);
#endif /* DO_BORDER */

	if( php->width > php->reqWidth )
	    php->width = php->reqWidth;
	if( php->height > php->reqHeight )
	    php->height = php->reqHeight;
	php->xorg = (Tk_Width(php->tkwin) - php->width) / 2;
	php->yorg = (Tk_Height(php->tkwin) - php->height) / 2;
    }

    /* compute necessary size of pixmap */
    w = MAX(php->reqWidth, php->imageWidth);
    h = MAX(php->reqHeight, php->imageHeight);
    if( (php->flags & USER_IMAGE_GEOM) == 0 ){
	if( w < php->pixelWidth )
	    w = php->pixelWidth;
	if( h < php->pixelHeight )
	    h = php->pixelHeight;
    }
    if( php->pixels == None || w != php->pixelWidth || h != php->pixelHeight
       || php->ditherLevel > DITHER_NONE && php->pix24 == NULL
       || php->ditherLevel == DITHER_NONE && php->pix24 != NULL )
	/* reallocate the pixmap and dithering arrays */
	ReallocatePixmap(php, w, h);

    /* adjust xshift and yshift if necessary */
    DoScroll(php, php->xshift, php->yshift);

    if( (php->flags & IS_PHOTO_ITEM) == 0 ){
	/* make sure we redraw any necessary parts of the window */
	RedrawPhotoFrame(php);
	if( php->xorg != oldx || php->yorg != oldy )
	    RedrawPhoto(php, php->xorg, php->yorg, php->width, php->height);
	else {
	    if( php->width > oldw && oldh > 0 )
		RedrawPhoto(php, php->xorg + oldw, php->yorg,
			    php->width - oldw, oldh);
	    if( php->height > oldh )
		RedrawPhoto(php, php->xorg, php->yorg + oldh,
			    php->width, php->height - oldh);
	}
    }

}

/*
 *----------------------------------------------------------------------
 *
 * ReallocatePixmap --
 *
 *	After changes in a photo widget's image size or dither level,
 *	this procedure reallocates and/or frees the photo's pixmap
 *	and dithering arrays as necessary.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Storage gets reallocated, here and in the X server.
 *
 *----------------------------------------------------------------------
 */

static void
ReallocatePixmap(php, w, h)
    Photo *php;
    int w, h;
{
    Window root;
    int clr_x, clr_y, n;
    register int i0, i1, j, w3, x3, pw3;
    Pixmap newPixmap;
    unsigned char *newPix24;
    char *newError;

    if( w != php->pixelWidth || h != php->pixelHeight ){
	/* Create new pixmap using the root window,
	   since Tk windows might not yet exist, as far as X is concerned. */
	root = RootWindow(php->display, Tk_ScreenNumber(php->tkwin));
	newPixmap = XCreatePixmap(php->display, root, w, h,
				  php->vis_info.depth);
	
	if( php->pixels != None ){
	    /* copy bits from the old pixmap and free it */
	    clr_x = MIN(w, php->pixelWidth);
	    clr_y = MIN(h, php->pixelHeight);
	    XCopyArea(php->display, php->pixels, newPixmap, php->blankGC,
		      0, 0, clr_x, clr_y, 0, 0);
	    XFreePixmap(php->display, php->pixels);
	} else
	    clr_x = clr_y = 0;
	
	/* clear the new areas of the new pixmap */
	if( clr_x < w )
	    XFillRectangle(php->display, newPixmap, php->blankGC,
			   clr_x, 0, w - clr_x, h);
	if( clr_y < h && clr_x > 0 )
	    XFillRectangle(php->display, newPixmap, php->blankGC,
			   0, clr_y, clr_x, h - clr_y);

	php->pixels = newPixmap;

    } else {
	/* Not changing size; must need to allocate/deallocate dither stuff */
	clr_x = w;
	clr_y = h;
    }

    if( php->ditherLevel > DITHER_NONE ){
	/* reallocate pix24 and error as well */
	if( (php->flags & MONOCHROME) == 0 ){
	    w3 = w * 3;
	    x3 = clr_x * 3;
	    pw3 = php->pixelWidth * 3;
	} else {
	    /* store 1 byte per pixel for monochrome */
	    w3 = w;
	    x3 = clr_x;
	    pw3 = php->pixelWidth;
	}
	n = w3 * h;
	newPix24 = (unsigned char *) ckalloc(n);
	newError = (schar *) ckalloc(n * sizeof(schar));
	/* copy stuff from old to new */
	if( php->pix24 != NULL ){
	    i0 = i1 = 0;
	    for( j = 0; j < clr_y; ++j ){
		bcopy(php->pix24 + i0, newPix24 + i1, x3);
		bcopy(php->error + i0, newError + i1, x3);
		i0 += pw3;
		i1 += w3;
	    }
	    ckfree((char *) php->pix24);
	    ckfree((char *) php->error);
	} else
	    clr_x = clr_y = 0;
	/* clear out new areas */
	if( clr_x == 0 ){
	    bzero(newPix24, n);
	    bzero(newError, n);
	} else {
	    if( clr_x < w ){
		i1 = x3;
		n = w3 - x3;
		for( j = 0; j < clr_y; ++j ){
		    bzero(newPix24 + i1, n);
		    bzero(newError + i1, n);
		    i1 += w3;
		}
	    }
	    if( clr_y < h ){
		n = (h - clr_y) * w3;
		bzero(newPix24 + clr_y * w3, n);
		bzero(newError + clr_y * w3, n);
	    }
	}
	php->pix24 = newPix24;
	php->error = newError;
	/* dithering is correct up to the end of the last pre-existing
	   complete scanline. */
	if( clr_x == w ){
	    if( clr_y <= php->ditherY ){
		php->ditherX = 0;
		php->ditherY = clr_y;
	    }
	} else {
	    php->ditherX = clr_x;
	    php->ditherY = 0;
	}

    } else if( php->pix24 != NULL ){
	ckfree((char *) php->pix24);
	ckfree((char *) php->error);
	php->pix24 = NULL;
	php->error = NULL;
    }

    php->pixelWidth = w;
    php->pixelHeight = h;

}

/*
 *----------------------------------------------------------------------
 *
 * DestroyPhoto --
 *
 *	This procedure is invoked by Tk_EventuallyFree or Tk_Release
 *	to clean up the internal structure of a photo at a safe time
 *	(when no-one is using it anymore).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the widget is freed up.
 *
 *----------------------------------------------------------------------
 */

static void
DestroyPhoto(clientData)
    ClientData clientData;
{
    Photo *php;

    php = (Photo *) clientData;
    if( (php->flags & BLANK_ALLOCED) != 0 )
	XFreeColors(php->display, php->cmap, &php->blankColor.pixel, 1, 0);
    if( php->blankGC != None )
	Tk_FreeGC(php->display, php->blankGC);
    if( php->monoGC != None )
	Tk_FreeGC(php->display, php->monoGC);
    if( php->cursor != None )
	Tk_FreeCursor(php->display, php->cursor);
    if( php->region != None )
	XDestroyRegion(php->region);
    if( php->xScrollCmd != NULL )
	free(php->xScrollCmd);
    if( php->yScrollCmd != NULL )
	free(php->yScrollCmd);
    if( php->pixels != None )
	XFreePixmap(php->display, php->pixels);
    if( php->pix24 != NULL ){
	ckfree((char *) php->pix24);
	ckfree((char *) php->error);
    }
    if( php->image != NULL ){
	if( php->image->data != NULL )
	    ckfree((char *) php->image->data);
	XFree((char *) php->image);
    }
#ifdef DO_BORDER
    if( php->bgBorder != NULL )
	Tk_Free3DBorder(php->bgBorder);
    if( php->activeBorder != NULL )
	Tk_Free3DBorder(php->activeBorder);
#endif /* DO_BORDER */
    ckfree((char *) php);
}

/*
 *----------------------------------------------------------------------
 *
 * GetVisualInfo --
 *
 *	This procedure is called to process the -visual option
 *	for a photo widget, choose a visual to use, and get
 *	information about it.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then interp->result contains an error message.
 *
 * Side effects:
 *	Fields in php get set up.  A colormap may be allocated to
 *	be the default colormap for the visual chosen.
 *
 *----------------------------------------------------------------------
 */

/*
 * List of visual classes and their names.
 * We allow for variant spelings.
 */
static struct visual_list {
    char	*name;
    int		class;
    int		preference;	/* high => we like it */
} visual_names[] = {
    "directcolor",	DirectColor,	5,
    "truecolor",	TrueColor,	5,
    "pseudocolor",	PseudoColor,	5,
    "staticcolor",	StaticColor,	3,
    "grayscale",	GrayScale,	1,
    "staticgray",	StaticGray,	1,
    "greyscale",	GrayScale,	1,
    "staticgrey",	StaticGray,	1,
    NULL,		0,		0,
};

static int
GetVisualInfo(interp, php, vis_opt)
    Tcl_Interp *interp;		/* for error reporting */
    Photo *php;
    char *vis_opt;		/* value given for -visual option */
{
    int i, n_visi, vid, n, pref, class;
    int l, new, new_pref;
    unsigned long mask;
    XVisualInfo *visi_list, vis_info;
    Colormap cmap;
    Visual *default_visual;
    Screen *screen;
    struct visual_list *vp;
    Tcl_HashEntry *entry;
    char num[20];
    ColorTableId id;

    screen = Tk_Screen(php->tkwin);
    default_visual = DefaultVisualOfScreen(screen);

    /*
     * Process the -visual option value
     * and set up the vis_info template for matching.
     */
    vis_info.screen = Tk_ScreenNumber(php->tkwin);
    vis_info.visualid = XVisualIDFromVisual(default_visual);
    mask = VisualScreenMask | VisualIDMask;
    if( vis_opt == NULL )
	vis_opt = DEF_PHOTO_VISUAL;

    if( isdigit(vis_opt[0]) ){
	/* numeric visual ID; use it */
	vis_info.visualid = strtol(vis_opt, NULL, 0);

    } else {
	l = strlen(vis_opt);
	if( l > 0 && strncmp(vis_opt, "best", l) == 0 ){
	    /* get info on all visuals and choose the best */
	    mask = VisualScreenMask;

	} else if( l > 0 && strncmp(vis_opt, "default", l) == 0 ){
	    /* use the default visual */

	} else {
	    /*
	     * Try to match a visual class name from the list.
	     */
	    n = 0;
	    for( vp = visual_names; vp->name != NULL; ++vp )
		if( strncmp(vis_opt, vp->name, l) == 0 ){
		    ++n;
		    vis_info.class = vp->class;
		}
	    if( n != 1 ){
		Tcl_AppendResult(interp, "bad or ambiguous value \"", vis_opt,
				 "\" for -visual option: must be one of best",
				 (char *) NULL);
		for( vp = visual_names; vp->name != NULL; ++vp )
		    Tcl_AppendResult(interp, ", ", vp->name, (char *) NULL);
		Tcl_AppendResult(interp, " or default", (char *) NULL);
		return TCL_ERROR;
	    }
	    mask = VisualScreenMask | VisualClassMask;
	}
    }

    /*
     * Get visual info for any visuals which are acceptable.
     */
    visi_list = XGetVisualInfo(php->display, mask, &vis_info, &n_visi);
    if( visi_list == NULL || n_visi == 0 ){
	if( (mask & VisualClassMask) != 0 )
	    Tcl_AppendResult(interp, "no visual of requested class available",
			     (char *) NULL);
	else if( vis_opt != NULL && isdigit(vis_opt[0]) )
	    Tcl_AppendResult(interp, "no visual with ID \"", vis_opt, "\"",
			     (char *) NULL);
	else
	    Tcl_AppendResult(interp, "no visual information available",
			     (char *) NULL);
	return TCL_ERROR;
    }

    /*
     * Choose the best visual of those acceptable and available.
     * We choose the one with the greatest depth.  If there are
     * several such, we choose the one with the highest `preference'
     * value as defined in the table above.  The default visual gets
     * an extra preference point.
     */
    pref = 0;
    vis_info.depth = 0;
    for( i = 0; i < n_visi; ++i ){
	class = visi_list[i].class;
	for( vp = visual_names; vp->name != NULL && vp->class != class; ++vp )
	    ;
	new_pref = vp->preference + (visi_list[i].visual == default_visual);
	if( visi_list[i].depth > vis_info.depth
	   || visi_list[i].depth == vis_info.depth && new_pref > pref ){
	    vis_info = visi_list[i];
	    pref = new_pref;
	}
    }
    XFree((char *) visi_list);

    /*
     * Remember various properties of this visual.
     */
    switch( vis_info.class ){
    case PseudoColor:
    case StaticColor:
	php->flags |= MAP_COLORS;
	break;
    case GrayScale:
    case StaticGray:
	php->flags |= MONOCHROME;
	break;
    }
    php->depth = vis_info.depth;
    php->visual = vis_info.visual;
    php->vis_info = vis_info;

    /*
     * If we're not using the default visual, make/get a default
     * colormap for this visual, and set the window to use this
     * visual and colormap.
     */
    if( vis_info.visual != default_visual ){
	/*
	 * Look up this display/screen/visual combination
	 */
	bzero(&id, sizeof(id));
	id.display = php->display;
	id.screen = Tk_ScreenNumber(php->tkwin);
	id.visual = php->visual;
	if( !default_hash_inited ){
	    Tcl_InitHashTable(&defaultHash, N_DEFAULT_HASH);
	    default_hash_inited = TRUE;
	}
	entry = Tcl_CreateHashEntry(&defaultHash, (char *) &id, &new);
	if( !new ){
	    cmap = (Colormap) Tcl_GetHashValue(entry);
	} else {
	    /* No default colormap for this visual; get one. */
	    cmap = XCreateColormap(php->display, RootWindowOfScreen(screen),
				   php->visual, AllocNone);
	    Tcl_SetHashValue(entry, cmap);
	}

	Tk_SetWindowVisual(php->tkwin, php->visual, php->depth, cmap);
	php->defaultCmap = cmap;
	php->cmap = cmap;
	php->flags |= STRANGE_VISUAL;

    } else
	php->defaultCmap = php->cmap = DefaultColormapOfScreen(screen);

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * SetPalette --
 *
 *	This procedure is called to process the -palette option
 *	for a photo widget: it parses the option value, checks that
 *	the screen can display enough colors, and allocates the
 *	colors.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then interp->result contains an error message.
 *
 * Side effects:
 *	Fields in *php get modified to reflect the new palette.
 *	Colors are allocated (if necessary) in the widget's colormap.
 *
 *----------------------------------------------------------------------
 */

/*
 * Table of "best" choices for palette for PseudoColor displays
 * with between 3 and 14 bits/pixel.
 */
static int palette_choice[12][3] = {
/*  #red, #green, #blue */
    2, 2, 2,			/* 3 bits, 8 colors */
    2, 3, 2,			/* 4 bits, 12 colors */
    3, 4, 2,			/* 5 bits, 24 colors */
    4, 5, 3,			/* 6 bits, 60 colors */
    5, 6, 4,			/* 7 bits, 120 colors */
    7, 7, 4,			/* 8 bits, 198 colors */
    8, 10, 6,			/* 9 bits, 480 colors */
    10, 12, 8,			/* 10 bits, 960 colors */
    14, 15, 9,			/* 11 bits, 1890 colors */
    16, 20, 12,			/* 12 bits, 3840 colors */
    20, 24, 16,			/* 13 bits, 7680 colors */
    28, 30, 18,			/* 14 bits, 15120 colors */
};

static int
SetPalette(php)
    Photo *php;
{
    int res, mono, f, i;
    char *p;
    int n, nred, ngreen, nblue;
    double g;
    Colormap cmap;
    ColorTable *ctp;
    char spec[32];

    if( php->palette[0] != 0 ){
	/*
	 * User has given a specification; parse and check it.
	 */
	res = TCL_ERROR;
	p = php->palette;
	if( sscanf(p, "%d%n", &nred, &n) == 1 ){
	    /*
	     * Got a number so far; any more following?
	     */
	    p += n;
	    if( p[0] == 0 ){
		/* single number => monochrome */
		mono = TRUE;
		ngreen = nblue = 1;
		res = TCL_OK;
	    } else if( sscanf(p, "/%d/%d%n", &ngreen, &nblue, &n) == 2
		      && p[n] == 0 ){
		mono = FALSE;
		res = TCL_OK;
	    }
	}
	if( res != TCL_OK )
	    Tcl_AppendResult(php->interp, "can't parse palette specification",
			     (char *) NULL);

	if( res == TCL_OK ){
	    /*
	     * Check validity of numbers.
	     */
	    if( nred > 256 )	/* only have 8 bits to select shades with */
		nred = 256;
	    if( ngreen > 256 )
		ngreen = 256;
	    if( nblue > 256 )
		nblue = 256;
	    if( nred < 2 || !mono && (ngreen < 2 || nblue < 2) ){
		Tcl_AppendResult(php->interp,
				 "each number in palette must be >= 2",
				 (char *) NULL);
		res = TCL_ERROR;
	    }
	}

	if( res == TCL_OK ){
	    /*
	     * Check that we have enough colors.
	     */
	    switch( php->vis_info.class ){
	    case DirectColor:
	    case TrueColor:
		if( nred > (1 << CountBits(php->vis_info.red_mask))
		   || ngreen > (1 << CountBits(php->vis_info.green_mask))
		   || nblue > (1 << CountBits(php->vis_info.blue_mask)) )
		    res = TCL_ERROR;
		break;
	    case PseudoColor:
	    case StaticColor:
		if( nred * ngreen * nblue > 1 << php->vis_info.depth )
		    res = TCL_ERROR;
		break;
	    case GrayScale:
	    case StaticGray:
		if( !mono || nred > 1 << php->vis_info.depth )
		    res = TCL_ERROR;
		break;
	    }
	    if( res != TCL_OK )
		Tcl_AppendResult(php->interp, "palette requires more colors ",
				 "than display provides", (char *) NULL);
	}
    } else
	res = TCL_OK;

    if( php->palette[0] == 0 || res != TCL_OK ){
	/*
	 * No palette or invalid spec given; choose default.
	 */
	nred = 2;		/* worst-case default */
	mono = TRUE;
	switch( php->vis_info.class ){
	case DirectColor:
	case TrueColor:
	    nred = 1 << CountBits(php->vis_info.red_mask);
	    ngreen = 1 << CountBits(php->vis_info.green_mask);
	    nblue = 1 << CountBits(php->vis_info.blue_mask);
	    mono = FALSE;
	    break;
	case PseudoColor:
	case StaticColor:
	    /* these values may not be optimal for all depths */
	    if( php->vis_info.depth >= 15 ){
		nred = ngreen = nblue = 5;
		mono = FALSE;
	    } else if( php->vis_info.depth >= 3 ){
		i = php->vis_info.depth - 3;
		nred = palette_choice[i][0];
		ngreen = palette_choice[i][1];
		nblue = palette_choice[i][2];
		mono = FALSE;
	    }
	    break;
	case GrayScale:
	case StaticGray:
	    nred = 1 << php->vis_info.depth;
	    break;
	}
    }

    /*
     * Convert spec back to canonical form.
     */
    sprintf(spec, mono? "%d": "%d/%d/%d", nred, ngreen, nblue);
    php->palette = Tk_GetUid(spec);

    /*
     * If we are changing to/from monochrome, we need to reallocate the
     * pix24 and error arrays, because they've changed size.
     */
    f = php->flags;
    if( mono )
	php->flags |= MONOCHROME;
    else
	php->flags &= ~MONOCHROME;
    if( f != php->flags && php->pix24 != NULL ){
	ckfree((char *) php->pix24);
	ckfree((char *) php->error);
	php->pix24 = NULL;
	php->error = NULL;
	/* A subsequent call to ComputePhotoGeometry will alloc new arrays. */
	/* Should we blank the image here? or whenever changing palettes? */
    }

    /*
     * Allocate colors and set colormap as necessary.
     */
    if( mono && nred == 2 ){
	php->depth = 1;
	php->colors = NULL;
	cmap = php->defaultCmap;
    } else {
	php->depth = php->vis_info.depth;
	/*
	 * Get a ColorTable for this display screen and palette.
	 * Fill in the colors for it if necessary.
	 */
	GetColorTable(php);
	ctp = php->colors;
	if( (ctp->flags & NEED_ALLOC) != 0 )
	    if( !AllocateColors(php) ){
		if( php->interp->result[0] == 0 )
		    Tcl_AppendResult(php->interp, "couldn't allocate colors ",
				     "for palette ", php->palette, NULL);
		return TCL_ERROR;
	    }
	cmap = ctp->cmap;
    }

    /*
     * If we're changing colormaps, we might have to change
     * the pixel value for the blank color.
     */
    if( cmap != php->cmap || (php->flags & BLANK_ALLOCED) == 0 ){
	if( (php->flags & BLANK_ALLOCED) != 0 ){
	    XFreeColors(php->display, php->cmap, &php->blankColor.pixel, 1, 0);
	    php->flags &= ~BLANK_ALLOCED;
	}
	php->flags |= BLANK_CHANGED;
    }

    /*
     * Put the new colormap on the window.
     */
    php->cmap = cmap;
    Tk_SetWindowColormap(php->tkwin, cmap);

    /*
     * Dithering/quantization isn't correct now we've changed
     * the color allocations.
     */
    php->ditherX = 0;
    php->ditherY = 0;

    return res;
}

/*
 * Count the number of bits in a mask.
 */
static int
CountBits(mask)
    unsigned long mask;
{
    int n;

    for( n = 0; mask != 0; mask >>= 1 )
	if( (mask & 1) != 0 )
	    ++n;
    return n;
}

/*
 *----------------------------------------------------------------------
 *
 * GetColorTable --
 *
 *	This procedure is called to allocate a table of colormap
 *	information for a photo widget.  Only one such table is
 *	allocated for all photos using the same display, screen,
 *	visual, palette and gamma values, so that the application
 *	need only request a set of colors from the X server once
 *	for all such photo widgets.  This procedure maintains a
 *	hash table to find previously-allocated ColorTables.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A new ColorTable may be allocated and placed in the hash
 *	table, and have colors allocated for it.
 *
 *----------------------------------------------------------------------
 */

static void
GetColorTable(php)
    Photo *php;
{
    ColorTable *ctp;
    Tcl_HashEntry *entry;
    ColorTableId id;
    int new;

    /*
     * Look for any existing ColorTable in the hash table.
     */
    id.display = php->display;
    id.screen = Tk_ScreenNumber(php->tkwin);
    id.visual = php->visual;
    id.palette = php->palette;
    id.gamma = php->gamma;
    id.flags = php->flags & (MUST_OWN_CMAP | MUST_DFLT_CMAP);
    if( !color_hash_inited ){
	Tcl_InitHashTable(&colorHash, N_COLOR_HASH);
	color_hash_inited = TRUE;
    }
    entry = Tcl_CreateHashEntry(&colorHash, (char *) &id, &new);

    if( !new ){
	ctp = (ColorTable *) Tcl_GetHashValue(entry);

    } else {
	/*
	 * No color table currently available; need to make one.
	 */
	ctp = (ColorTable *) ckalloc(sizeof(ColorTable));
	ctp->id = id;
	ctp->flags = NEED_ALLOC | id.flags;
	ctp->refcnt = 0;
	ctp->ncolors = 0;
	ctp->pixel_map = NULL;
	Tcl_SetHashValue(entry, ctp);
    }

    ++ctp->refcnt;
    if( php->colors != NULL )
	--php->colors->refcnt;
    php->colors = ctp;

}

/*
 *----------------------------------------------------------------------
 *
 * AllocateColors --
 *
 *	This procedure allocates the colors specified in a ColorTable.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A private colormap may be allocated.  Colors are allocated
 *	from the X server.  Fields in the photo widget's color table
 *	are updated.
 *
 *----------------------------------------------------------------------
 */

/* 16-bit intensity value for i/n of full intensity. */
#define CFRAC(i, n)	((i) * 65535 / (n))

/* As for CFRAC, but apply exponent of g. */
#define CGFRAC(i, n, g)	((int)(65535 * pow((double)(i) / (n), (g))))

static int
AllocateColors(php)
    Photo *php;
{
    ColorTable *ctp;
    int i, r, g, b, rmult, mono;
    int ncol, nred, ngreen, nblue, nctot;
    double fr, fg, fb, igam;
    XColor *colors;
    Colormap cmap, dflt;
    Display *disp;
    Window root;

    ctp = php->colors;
    if( (ctp->flags & NEED_ALLOC) == 0 )
	return;

    disp = ctp->id.display;
    cmap = dflt = php->defaultCmap;

    mono = sscanf(ctp->id.palette, "%d/%d/%d", &nred, &ngreen, &nblue) <= 1;
    igam = 1.0 / ctp->id.gamma;

    if( php->vis_info.class == DirectColor
       || php->vis_info.class == TrueColor ){
	/*
	 * Direct/True Color: allocate shades of R, G, B independently.
	 */
	nctot = 1 << php->vis_info.bits_per_rgb;
	colors = (XColor *) ckalloc(nctot * sizeof(XColor));
	if( mono )
	    ncol = ngreen = nblue = nred;
	else
	    ncol = MAX(MAX(nred, ngreen), nblue);

	for( i = 0; i < ncol; ++i ){
	    if( igam == 1.0 ){
		colors[i].red = CFRAC(i, nred - 1);
		colors[i].green = CFRAC(i, ngreen - 1);
		colors[i].blue = CFRAC(i, nblue - 1);
	    } else {
		colors[i].red = CGFRAC(i, nred - 1, igam);
		colors[i].green = CGFRAC(i, ngreen - 1, igam);
		colors[i].blue = CGFRAC(i, nblue - 1, igam);
	    }
	}

    } else {
	nctot = 1 << php->vis_info.depth;
	colors = (XColor *) ckalloc(nctot * sizeof(XColor));
	rmult = mono? 1: ngreen * nblue;
	ncol = nred * rmult;

	if( !mono ){
	    /*
	     * Color display using a PseudoColor or StaticColor visual:
	     * we have to allocate the Cartesian product of the shades
	     * we want of each primary.
	     */
	    i = 0;
	    for( r = 0; r < nred; ++r )
		for( g = 0; g < ngreen; ++g )
		    for( b = 0; b < nblue; ++b ){
			if( igam == 1.0 ){
			    colors[i].red = CFRAC(r, nred - 1);
			    colors[i].green = CFRAC(g, ngreen - 1);
			    colors[i].blue = CFRAC(b, nblue - 1);
			} else {
			    colors[i].red = CGFRAC(r, nred - 1, igam);
			    colors[i].green = CGFRAC(g, ngreen - 1, igam);
			    colors[i].blue = CGFRAC(b, nblue - 1, igam);
			}
			++i;
		    }
	} else {
	    /*
	     * Monochrome display - allocate the shades of grey we want.
	     */
	    for( i = 0; i < ncol; ++i ){
		if( igam == 1.0 )
		    r = CFRAC(i, ncol - 1);
		else
		    r = CGFRAC(i, ncol - 1, igam);
		colors[i].red = colors[i].green = colors[i].blue = r;
	    }
	}
    }

    /*
     * Now allocate the colors we've calculated.
     * Try first in the default colormap unless MUST_OWN_CMAP is set.
     */
    ctp->pixel_map = (unsigned long *) ckalloc(ncol * sizeof(unsigned long));
    i = 0;
    if( (ctp->flags & MUST_OWN_CMAP) == 0 ){
	for( ; i < ncol; ++i ){
	    if( !XAllocColor(disp, cmap, &colors[i]) ){
		/*
		 * Can't get all the colors we want in the default colormap;
		 * first try freeing colors from other unused palettes.
		 */
		if( !ReclaimColors(&ctp->id, ncol - i)
		   || !XAllocColor(disp, cmap, &colors[i]) ){
		    /*
		     * Still can't allocate it; we'll have to use our
		     * own cmap.  Free up the colors we've allocated
		     * from the default cmap.
		     */
		    if( i > 0 )
			XFreeColors(disp, cmap, ctp->pixel_map, i, 0);
		    break;
		}
	    }
	    ctp->pixel_map[i] = colors[i].pixel;
	}
    }
    
    if( i < ncol ){
	/*
	 * Didn't get all the colors we want in the default colormap:
	 * allocate a private colormap.
	 */
	if( (ctp->flags & MUST_DFLT_CMAP) != 0 )
	    return FALSE;		/* failed. */

	root = RootWindow(disp, ctp->id.screen);
	/*
	 * We use the root window because maybe the widget's window
	 * doesn't exist yet.
	 */
	cmap = XCreateColormap(disp, root, php->visual, AllocAll);
	ctp->flags |= OWN_CMAP;
	for( i = 0; i < ncol; ++i ){
	    colors[i].flags = DoRed | DoGreen | DoBlue;
	    colors[i].pixel = ctp->pixel_map[i] = i + nctot - ncol;
	}
	for( ; i < nctot; ++i ){
	    colors[i].flags = DoRed | DoGreen | DoBlue;
	    colors[i].pixel = i - ncol;
	}
	/*
	 * If we don't need all the colors, we use the high-numbered
	 * colors and copy the low-numbered ones from the default
	 * colormap.  This helps minimize color changes in other
	 * windows when our colormap is installed.
	 */
	XQueryColors(disp, dflt, &colors[ncol], nctot - ncol);
	XStoreColors(disp, cmap, colors, nctot);
    }

    ctp->ncolors = ncol;
    ctp->cmap = cmap;

    /*
     * Set up quantization tables for dithering.
     */
    for( i = 0; i < 256; ++i ){
	r = (i * (nred - 1) + 127) / 255;
	if( mono ){
	    fr = colors[r].red / 65535.0;
	    if( php->gamma != 1.0 )
		fr = pow(fr, php->gamma);
	    ctp->color_quant[0][i] = (int)(fr * 255.99);
	    ctp->red_values[i] = colors[r].pixel;
	} else {
	    g = (i * (ngreen - 1) + 127) / 255;
	    b = (i * (nblue - 1) + 127) / 255;
	    if( php->vis_info.class == DirectColor
	       || php->vis_info.class == TrueColor ){
		ctp->red_values[i] = colors[r].pixel & php->vis_info.red_mask;
		ctp->green_values[i] = colors[g].pixel
				& php->vis_info.green_mask;
		ctp->blue_values[i] = colors[b].pixel
				& php->vis_info.blue_mask;
	    } else {
		r *= rmult;
		g *= nblue;
		ctp->red_values[i] = r;
		ctp->green_values[i] = g;
		ctp->blue_values[i] = b;
	    }
	    fr = colors[r].red / 65535.0;
	    fg = colors[g].green / 65535.0;
	    fb = colors[b].blue / 65535.0;
	    if( php->gamma != 1.0 ){
		fr = pow(fr, php->gamma);
		fg = pow(fg, php->gamma);
		fb = pow(fb, php->gamma);
	    }
	    ctp->color_quant[0][i] = (int)(fr * 255.99);
	    ctp->color_quant[1][i] = (int)(fg * 255.99);
	    ctp->color_quant[2][i] = (int)(fb * 255.99);
	}

    }

    ctp->flags &= ~NEED_ALLOC;
    return TRUE;		/* success */
}

/*
 *----------------------------------------------------------------------
 *
 * ReclaimColors --
 *
 *	This procedure is called to try to free up colors in the
 *	default colormap (for this visual).  It looks for ColorTables
 *	which are not currently being used by any photo widget in
 *	this application.  This procedure only frees up any colors
 *	if there is a possibility of freeing up at least `ncol'
 *	colors.
 *
 * Results:
 *	The return value is TRUE if any colors were freed, FALSE
 *	otherwise.
 *
 * Side effects:
 *	ColorTables which are not currently in use may lose their
 *	color allocations.
 *
 *----------------------------------------------------------------------
 */

static int
ReclaimColors(id, ncol)
    ColorTableId *id;
    int ncol;
{
    Tcl_HashSearch srch;
    Tcl_HashEntry *entry;
    ColorTable *ctp;
    int navail;

    /* first scan through to see how many we may possibly be able to get */
    navail = 0;
    entry = Tcl_FirstHashEntry(&colorHash, &srch);
    while( entry != NULL ){
	ctp = (ColorTable *) Tcl_GetHashValue(entry);
	if( ctp->id.display == id->display && ctp->id.screen == id->screen
	   && ctp->id.visual == id->visual && (ctp->flags & OWN_CMAP) == 0
	   && ctp->refcnt == 0 && ctp->ncolors != 0
	   && (ctp->id.palette != id->palette || ctp->id.gamma != id->gamma) ){
	    /*
	     * We could take this guy's colors off him.
	     */
	    navail += ctp->ncolors;
	}
	entry = Tcl_NextHashEntry(&srch);
    }

    /*
     * navail is an (over)estimate of the number of colors we could get
     * if we try.
     */
    if( navail < ncol )
	return FALSE;		/* don't disturb other maps unnecessarily */

    /*
     * Scan through a second time freeing colors.
     */
    entry = Tcl_FirstHashEntry(&colorHash, &srch);
    while( entry != NULL && ncol > 0 ){
	ctp = (ColorTable *) Tcl_GetHashValue(entry);
	if( ctp->id.display == id->display && ctp->id.screen == id->screen
	   && ctp->id.visual == id->visual && (ctp->flags & OWN_CMAP) == 0
	   && ctp->refcnt == 0 && ctp->ncolors != 0
	   && (ctp->id.palette != id->palette || ctp->id.gamma != id->gamma) ){
	    /*
	     * Free the colors that this ColorTable has.
	     */
	    XFreeColors(ctp->id.display, ctp->cmap, ctp->pixel_map,
			ctp->ncolors, 0);
	    ncol -= ctp->ncolors;
	    ctp->flags |= NEED_ALLOC;
	    ctp->ncolors = 0;
	    ckfree((char *) ctp->pixel_map);
	    ctp->pixel_map = NULL;
	}
	entry = Tcl_NextHashEntry(&srch);
    }
    return TRUE;		/* we freed some colors */
}

/*
 *----------------------------------------------------------------------
 *
 * AllocateBlankColor --
 *
 *	This procedure is called to determine the pixel value used
 *	for blank areas of the image.  This depends on the color
 *	value requested by the user and on the colormap (which depends
 *	on the palette).  This procedure is complicated by the fact
 *	that the palette may take up all of the colors in the colormap:
 *	in this case we use the closest existing color.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Fields in *php are updated.
 *
 *----------------------------------------------------------------------
 */

static void
AllocateBlankColor(php)
    Photo *php;
{
    int r, g, b;
    unsigned long n;
    ColorTable *ctp;
    XGCValues gcValues;

    /*
     * If we had a color allocated previously, free it.
     */
    if( (php->flags & BLANK_ALLOCED) != 0 )
	XFreeColors(php->display, php->cmap, &php->blankColor.pixel, 1, 0);
    php->flags &= ~(BLANK_ALLOCED | BLANK_CHANGED);

    /*
     * Try and allocate the color in our widget's colormap.
     */
    if( XAllocColor(php->display, php->cmap, &php->blankColor) ){
	php->flags |= BLANK_ALLOCED;
	return;
    }

    /*
     * Fallback: take the closest color we have in the color table.
     */
    ctp = php->colors;
    if( ctp != NULL ){
	r = php->blankColor.red >> 8;
	g = php->blankColor.green >> 8;
	b = php->blankColor.blue >> 8;
	if( (php->flags & MONOCHROME) != 0 ){
	    n = ctp->red_values[(r * 11 + g * 16 + b * 5) >> 5];
	} else {
	    n = ctp->red_values[r] + ctp->green_values[g]
		+ ctp->blue_values[b];
	    if( (php->flags & MAP_COLORS) != 0 )
		n = ctp->pixel_map[n];
	}
	php->blankColor.pixel = n;
	return;
    }

    /*
     * Oh dear, we are displaying in 1-bit monochrome.
     * Use the background color from monoGC (i.e. black).
     */
    XGetGCValues(php->display, php->monoGC, GCBackground, &gcValues);
    php->blankColor.pixel = gcValues.background;

}

/*
 *----------------------------------------------------------------------
 *
 * DoScroll --
 *
 *	This procedure is called to move the image in the photo
 *	window so that the pixel at (x, y) in the image is in the
 *	top-left corner of the window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The window may get redrawn.  Tcl commands may be executed
 *	to update any associated scrollbars.
 *
 *----------------------------------------------------------------------
 */

static void
DoScroll(php, x, y)
    Photo *php;
    int x, y;
{
    if( x < 0 )
	x = 0;
    else if( x > php->pixelWidth - php->width )
	x = php->pixelWidth - php->width;
    if( y < 0 )
	y = 0;
    else if( y > php->pixelHeight - php->height )
	y = php->pixelHeight - php->height;
    if( x != php->xshift || y != php->yshift ){
	php->xshift = x;
	php->yshift = y;
	RedrawPhoto(php, php->xorg, php->yorg, php->width, php->height);
    }
    DoScrollCmds(php);
}

/*
 *----------------------------------------------------------------------
 *
 * DoScrollCmds --
 *
 *	This procedure is called to execute the Tcl commands that
 *	update any scrollbars associated with this photo widget.
 *	The commands only get executed if the parameter values are
 *	different from the values most recently used.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Tcl commands may get executed.
 *
 *----------------------------------------------------------------------
 */

static void
DoScrollCmds(php)
    Photo *php;
{
    int res;
    char string[64];

    if( php->width <= 0 || php->height <= 0 )
	return;			/* window not set up yet */
    if( php->xScrollCmd != NULL
       && (php->xscr_pos != php->xshift || php->xscr_wid != php->width
	   || php->xscr_pwid != php->pixelWidth) ){
	sprintf(string, " %d %d %d %d", php->pixelWidth, php->width,
		php->xshift, php->xshift + php->width - 1);
	res = Tcl_VarEval(php->interp, php->xScrollCmd, string, (char *) NULL);
	if( res != TCL_OK )
	    Tk_BackgroundError(php->interp);
	php->xscr_pos = php->xshift;
	php->xscr_wid = php->width;
	php->xscr_pwid = php->pixelWidth;
    }
    if( php->yScrollCmd != NULL
       && (php->yscr_pos != php->yshift || php->yscr_ht != php->height
	   || php->yscr_pht != php->pixelHeight) ){
	sprintf(string, " %d %d %d %d", php->pixelHeight, php->height,
		php->yshift, php->yshift + php->height - 1);
	res = Tcl_VarEval(php->interp, php->yScrollCmd, string, (char *) NULL);
	if( res != TCL_OK )
	    Tk_BackgroundError(php->interp);
	php->yscr_pos = php->yshift;
	php->yscr_ht = php->height;
	php->yscr_pht = php->pixelHeight;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * RedrawImage --
 *
 *	This procedure is called to redraw the photo's window
 *	because a region of the image has been changed.
 *	Parameters x, y, w and h specify the changed region
 *	of the image.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Part of the photo's window may be redrawn.
 *
 *----------------------------------------------------------------------
 */

static void
RedrawImage(php, x, y, w, h)
    Photo *php;
    int x, y, w, h;
{
    if( (x -= php->xshift) < 0 ){
	w += x;
	x = 0;
    }
    if( (y -= php->yshift) < 0 ){
	h += y;
	y = 0;
    }
    if( x + w > php->width )
	w = php->width - x;
    if( y + h > php->height )
	h = php->height - y;
    if( w <= 0 || h <= 0 )
	return;
    RedrawPhoto(php, x + php->xorg, y + php->yorg, w, h);
}

/*
 *----------------------------------------------------------------------
 *
 * RedrawPhoto --
 *
 *	This procedure is called to redraw the part of the photo's
 *	window specified by the parameters x, y, w and h.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Part of the photo's window may be redrawn.
 *
 *----------------------------------------------------------------------
 */

static void
RedrawPhoto(php, x, y, w, h)
    Photo *php;
    int x, y, w, h;
{
    XRectangle rect;
    int xi, yi, wi, hi;

    /* Photo items are redrawn elsewhere */
    if( (php->flags & IS_PHOTO_ITEM) != 0 )
	return;

    if( php->tkwin == NULL || Tk_WindowId(php->tkwin) == None
       || (php->flags & EXPOSED) == 0 )
	return;

    /*
     * Request redraw of any necessary part of the image.
     */
    xi = MAX(x - php->xorg, 0);
    yi = MAX(y - php->yorg, 0);
    wi = MIN(w, php->width - xi);
    hi = MIN(h, php->height - yi);
    if( wi > 0 && hi > 0 ){
	if( (php->flags & REDRAW_REQUESTED) == 0 )
	    Tk_DoWhenIdle(UpdatePhoto, (ClientData) php);
	php->flags |= REDRAW_IMAGE | REDRAW_REQUESTED;
	if( php->region == None )
	    php->region = XCreateRegion();
	rect.x = xi + php->xorg;
	rect.y = yi + php->yorg;
	rect.width = wi;	/* these are unsigned shorts */
	rect.height = hi;
	XUnionRectWithRegion(&rect, php->region, php->region);

	php->undrawn += wi * hi;
	if( php->undrawn >= php->drawInterval ){
	    /*
	     * It's a while since we put anything on the screen -
	     * update it now.
	     */
	    DisplayPhoto(php);
	}
    }

    if( x < php->xorg || y < php->yorg
       || x + w > php->xorg + php->width
       || y + h > php->yorg + php->height ){
	/*
	 * Need to redraw the frame around the photo - do it later.
	 * (Why put off until tomorrow what you can put off until
	 * the next day. :-)
	 */
	RedrawPhotoFrame(php);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * RedrawPhotoFrame --
 *
 *	This procedure is called to request that the frame around
 *	the image be redrawn.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A when-idle call to UpdatePhoto may be registered.
 *
 *----------------------------------------------------------------------
 */

static void
RedrawPhotoFrame(php)
    Photo *php;
{
    /* Photo items are redrawn elsewhere */
    if( (php->flags & IS_PHOTO_ITEM) != 0 )
	return;

    if( (php->flags & REDRAW_REQUESTED) == 0 )
	Tk_DoWhenIdle(UpdatePhoto, (ClientData) php);
    php->flags |= REDRAW_FRAME | REDRAW_REQUESTED;
}

/*
 *----------------------------------------------------------------------
 *
 * UpdatePhoto --
 *
 *	This procedure is called as a when-idle handler when
 *	it is necessary to redraw some part of the photo's window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The window may redrawn (via a call to DisplayPhoto).
 *
 *----------------------------------------------------------------------
 */

static void
UpdatePhoto(clientData)
    ClientData clientData;
{
    Photo *php;

    php = (Photo *) clientData;
    php->flags &= ~REDRAW_REQUESTED;
    DisplayPhoto(php);
}

/*
 *----------------------------------------------------------------------
 *
 * DisplayPhoto --
 *
 *	This procedure is called to redraw parts of the photo
 *	widget's window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	
 *
 *----------------------------------------------------------------------
 */

static void
DisplayPhoto(php)
    Photo *php;
{
    Tk_Window win;
    int t, o, w, h, x1, y1;
    Tk_3DBorder border;

    win = php->tkwin;

    if( win == NULL || !Tk_IsMapped(win) || (php->flags & EXPOSED) == 0 ){
	php->flags &= ~(REDRAW_IMAGE | REDRAW_FRAME);
	return;
    }

#ifdef DO_BORDERS
    if( (php->flags & REDRAW_FRAME) != 0 ){
	o = (php->relief == TK_RELIEF_FLAT)? 0: php->borderWidth;
	w = Tk_Width(win);
	h = Tk_Height(win);
	border = (php->flags & ACTIVE)? php->activeBorder: php->bgBorder;

	/*
	 * Draw the border (if any).
	 */
	if( o > 0 )
	    Tk_Draw3DRectangle(php->display, Tk_WindowId(win),
			       border, 0, 0, w, h,
			       php->borderWidth, php->relief);

	/*
	 * Fill in any area between the border and the image
	 * with the border color (in flat relief).  Only happens
	 * if padx or pady are non zero, or if the geometry manager
	 * has given us more space than we asked for.
	 */
	if( (t = php->yorg - o) > 0 )
	    Tk_Fill3DRectangle(php->display, Tk_WindowId(win),
			       border, o, o, w - 2 * o, t,
			       0, TK_RELIEF_FLAT);
	
	y1 = php->yorg + php->height;
	if( (t = h - o - y1) > 0 )
	    Tk_Fill3DRectangle(php->display, Tk_WindowId(win),
			       border, o, y1, w - 2 * o, t,
			       0, TK_RELIEF_FLAT);
	
	if( (t = php->xorg - o) > 0 )
	    Tk_Fill3DRectangle(php->display, Tk_WindowId(win),
			       border, o, php->yorg, t, php->height,
			       0, TK_RELIEF_FLAT);
	
	x1 = php->xorg + php->width;
	if( (t = w - o - x1) > 0 )
	    Tk_Fill3DRectangle(php->display, Tk_WindowId(win),
			       border, x1, php->yorg, t, php->height,
			       0, TK_RELIEF_FLAT);
    }
#endif /* DO_BORDERS */

    if( (php->flags & REDRAW_IMAGE) != 0 ){
	/*
	 * Copy the whole of the potentially-visible part of the
	 * pixmap to the window, using the clip-list to delineate
	 * which parts of the image actually need redrawing.
	 */
	XSetRegion(php->display, php->blankGC, php->region);
	XCopyArea(php->display, php->pixels, Tk_WindowId(php->tkwin),
		  php->blankGC, php->xshift, php->yshift, php->width,
		  php->height, php->xorg, php->yorg);
	XDestroyRegion(php->region);
	php->region = None;
	XSetClipMask(php->display, php->blankGC, None);
	php->undrawn = 0;
    }

    php->flags &= ~(REDRAW_IMAGE | REDRAW_FRAME);
}

/*
 *----------------------------------------------------------------------
 *
 * PhotoEventProc --
 *
 *	This procedure is invoked by the Tk dispatcher for various
 *	events on photo windows.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When the window gets deleted, internal structures get
 *	cleaned up.  When it gets exposed, it is redisplayed.
 *
 *----------------------------------------------------------------------
 */

static void
PhotoEventProc(clientData, event)
ClientData clientData;
XEvent *event;
{
    register Photo *php;
    XWindowAttributes attrs;

    php = (Photo *) clientData;

    if( event->type == Expose ){
	php->flags |= EXPOSED;
	RedrawPhoto(php, event->xexpose.x, event->xexpose.y,
		    event->xexpose.width, event->xexpose.height);

    } else if( event->type == DestroyNotify ){
	Tcl_DeleteCommand(php->interp, Tk_PathName(php->tkwin));
	php->tkwin = NULL;
	if( (php->flags & REDRAW_REQUESTED) != 0 )
	    Tk_CancelIdleCall(UpdatePhoto, (ClientData) php);
	Tk_EventuallyFree((ClientData) php, DestroyPhoto);

    } else if( event->type == ConfigureNotify ){
	ComputePhotoGeometry(php);

#ifdef DO_BORDERS
    } else if( event->type == EnterNotify ){
	php->flags |= ACTIVE;
	RedrawPhotoFrame(php);

    } else if( event->type == LeaveNotify ){
	php->flags &= ~ACTIVE;
	RedrawPhotoFrame(php);
#endif /* DO_BORDERS */
    }
}

/*
 *----------------------------------------------------------------------
 *
 * AllocImage --
 *
 *	This procedure allocates an XImage structure to hold
 *	an image block to be transferred to our pixmap.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Storage may be (re)allocated; fields in *php are updated.
 *
 *----------------------------------------------------------------------
 */

static void
AllocImage(php, width, height)
    Photo *php;
    int width, height;
{
    XImage *im;
    int bpp, n;

    im = php->image;
    bpp = php->depth == 1? 1: php->depth <= 8? 8: 32;
    if( im->bits_per_pixel != bpp ){
	im->bits_per_pixel = bpp;
	im->format = bpp == 1? XYBitmap: ZPixmap;
	im->depth = php->depth;
	_XInitImageFuncPtrs(im);
    }
    im->width = width;
    im->height = height;
    im->bytes_per_line = ((bpp * width + 31) >> 3) & ~3;
    n = im->bytes_per_line * height;
    if( im->data == NULL || n > php->imageSize ){
	if( im->data != NULL )
	    ckfree((char *) im->data);
	im->data = ckalloc(n);
	php->imageSize = n;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * FindPhoto --
 *
 *	This procedure is called by clients of a photo widget to
 *	get an opaque handle (actually a Photo *) for a given
 *	photo, which can be used in subsequent calls to PhotoPutBlock,
 *	etc.  The `name' parameter is the window pathname for the
 *	photo widget.
 *
 * Results:
 *	The handle for the photo widget, or NULL if there is no
 *	photo widget with the name given.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

PhotoHandle
FindPhoto(name)
char *name;
{
    Tcl_HashEntry *entry;

    if( !hash_inited
       || (entry = Tcl_FindHashEntry(&photoHash, name)) == NULL )
	return NULL;
    return (PhotoHandle) Tcl_GetHashValue(entry);
}

/*
 *----------------------------------------------------------------------
 *
 * PhotoPutBlock --
 *
 *	This procedure is called by clients of a photo widget to
 *	put image data into the widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The image data is stored.  Dithering and/or redisplay of the
 *	image may be done.
 *
 *----------------------------------------------------------------------
 */

void
PhotoPutBlock(handle, block, x, y, w, h)
    PhotoHandle handle;
    register struct image_block *block;
    int x, y, w, h;
{
    register Photo *php;
    register unsigned long *ldest, mask, word;
    register unsigned char *cdest, *src;
    register ColorTable *ctp;
    register int goff, boff, big_endian;
    XImage *im;
    unsigned long first_bit;
    int bw, bh, n, xx, yy;
    int ww, hh, iw, ih;
    int nb_line, pw3;
    unsigned char *src0, *dst0;
    unsigned char *p24, *q24;

    php = (Photo *) handle;
    ctp = php->colors;
    im = php->image;

    xx = x + w;
    yy = y + h;
    if( (php->flags & USER_IMAGE_GEOM) != 0 ){
	if( xx > php->imageWidth )
	    w = php->imageWidth - x;
	if( yy > php->imageHeight )
	    h = php->imageHeight - y;
	if( w <= 0 || h <= 0 )
	    return;
    } else if( xx > php->pixelWidth || yy > php->pixelHeight ){
	PhotoExpand((PhotoHandle) php, xx, yy);
    }

    if( y < php->ditherY || y == php->ditherY && x < php->ditherX ){
	/* the dithering isn't correct past the start of this block */
	php->ditherX = x;
	php->ditherY = y;
    }

    goff = block->comp_off[1] - block->comp_off[0];
    boff = block->comp_off[2] - block->comp_off[0];

    /*
     * First work out how many bytes we'll need for pixel storage,
     * and allocate more if necessary.
     */
    bw = MIN(block->width, w);
    bh = MIN(block->height, h);
    if( php->ditherLevel != DITHER_BLOCKS )
	AllocImage(php, bw, bh);

    /*
     * Convert image data to pixel values.
     */
    if( php->ditherLevel > DITHER_NONE ){
	/*
	 * Dithering - copy 24-bit data in, converting to monochrome
	 * if necessary; then if required, call dither routine.
	 */
	if( (php->flags & MONOCHROME) == 0 ){
	    pw3 = php->pixelWidth * 3;
	    p24 = php->pix24 + y * pw3 + x * 3;
	} else {
	    pw3 = php->pixelWidth;
	    p24 = php->pix24 + y * pw3 + x;
	}
	for( hh = h; hh > 0; ){
	    src0 = block->ptr + block->comp_off[0];
	    ih = MIN(hh, bh);
	    hh -= ih;
	    for( ; ih > 0; --ih ){
		q24 = p24;
		for( ww = w; ww > 0; ){
		    iw = MIN(bw, ww);
		    ww -= iw;
		    src = src0;
		    for( ; iw > 0; --iw ){
			if( (php->flags & MONOCHROME) != 0 ){
			    *q24++ = (src[0]*11 + src[goff]*16 + src[boff]*5)
					>> 5;
			} else {
			    *q24++ = src[0];
			    *q24++ = src[goff];
			    *q24++ = src[boff];
			}
			src += block->pixel_size;
		    }
		}
		src0 += block->pitch;
		p24 += pw3;
	    }
	}
	if( php->ditherLevel == DITHER_BLOCKS ){
	    Dither(php, x, y, w, h);
	    return;
	}
    }

    src0 = block->ptr + block->comp_off[0];
    dst0 = (unsigned char *) im->data;
    nb_line = im->bytes_per_line;

    if( (php->flags & MONOCHROME) != 0 ){
	if( php->depth == 1 ){
	    /*
	     * Monochrome 1-bit per pixel (not dithering at this stage).
	     */
	    ldest = (unsigned long *) dst0;
	    big_endian = im->bitmap_bit_order == MSBFirst;
	    first_bit = big_endian? (1 << (im->bitmap_unit - 1)): 1;
	    for( yy = 0; yy < bh; ++yy ){
		src = src0;
		mask = first_bit;
		word = 0;
		for( xx = 0; xx < bw; ++xx ){
		    if( mask == 0 ){
			*ldest++ = word;
			mask = first_bit;
			word = 0;
		    }
		    if( src[0]*11 + src[goff]*16 + src[boff]*5 > 128*32 )
			word |= mask;
		    mask = big_endian? (mask >> 1): (mask << 1);
		    src += block->pixel_size;
		}
		*ldest++ = word;
		src0 += block->pitch;
	    }

	} else {
	    /*
	     * Multibit monochrome display.
	     */
	    if( im->bits_per_pixel <= 8 ){
		/* monochrome, 2 to 8 bits/pixel */
		for( yy = 0; yy < bh; ++yy ){
		    src = src0;
		    cdest = dst0;
		    for( xx = 0; xx < bw; ++xx ){
			n = (src[0]*11 + src[goff]*16 + src[boff]*5 + 31) >> 5;
			*cdest++ = ctp->red_values[n];
			src += block->pixel_size;
		    }
		    src0 += block->pitch;
		    dst0 += nb_line;
		}
	    } else {
		/* monochrome, 9 to 32 bits/pixel */
		for( yy = 0; yy < bh; ++yy ){
		    src = src0;
		    ldest = (unsigned long *) dst0;
		    for( xx = 0; xx < bw; ++xx ){
			n = (src[0]*11 + src[goff]*16 + src[boff]*5 + 31) >> 5;
			*ldest++ = ctp->red_values[n];
			src += block->pixel_size;
		    }
		    src0 += block->pitch;
		    dst0 += nb_line;
		}
	    }

	}

    } else if( (php->flags & MAP_COLORS) != 0 ){
	if( im->bits_per_pixel <= 8 ){
	    /*
	     * PseudoColor or StaticColor with <= 8 bits/pixel.
	     */
	    for( yy = 0; yy < bh; ++yy ){
		src = src0;
		cdest = dst0;
		for( xx = 0; xx < bw; ++xx ){
		    n = ctp->red_values[src[0]] + ctp->green_values[src[goff]]
			+ ctp->blue_values[src[boff]];
		    *cdest++ = ctp->pixel_map[n];
		    src += block->pixel_size;
		}
		src0 += block->pitch;
		dst0 += nb_line;
	    }
	} else {
	    /*
	     * PseudoColor or StaticColor with 9 to 32 bits/pixel.
	     */
	    for( yy = 0; yy < bh; ++yy ){
		src = src0;
		ldest = (unsigned long *) dst0;
		for( xx = 0; xx < bw; ++xx ){
		    n = ctp->red_values[src[0]] + ctp->green_values[src[goff]]
			+ ctp->blue_values[src[boff]];
		    *ldest++ = ctp->pixel_map[n];
		    src += block->pixel_size;
		}
		src0 += block->pitch;
		dst0 += nb_line;
	    }
	}

    } else {
	if( im->bits_per_pixel <= 8 ){
	    /*
	     * DirectColor or TrueColor display with <= 8 bits/pixel.
	     */
	    for( yy = 0; yy < bh; ++yy ){
		src = src0;
		cdest = dst0;
		for( xx = 0; xx < bw; ++xx ){
		    *cdest++ = ctp->red_values[src[0]]
			     + ctp->green_values[src[goff]]
			     + ctp->blue_values[src[boff]];
		    src += block->pixel_size;
		}
		src0 += block->pitch;
		dst0 += nb_line;
	    }
	} else {
	    /*
	     * DirectColor or TrueColor display with 9 to 32 bits/pixel.
	     */
	    for( yy = 0; yy < bh; ++yy ){
		src = src0;
		ldest = (unsigned long *) dst0;
		for( xx = 0; xx < bw; ++xx ){
		    *ldest++ = ctp->red_values[src[0]]
			     + ctp->green_values[src[goff]]
			     + ctp->blue_values[src[boff]];
		    src += block->pixel_size;
		}
		src0 += block->pitch;
		dst0 += nb_line;
	    }
	}
    }

    yy = y;
    for( hh = h; hh > 0; hh -= ih ){
	ih = MIN(hh, bh);
	xx = x;
	for( ww = w; ww > 0; ww -= iw ){
	    iw = MIN(bw, ww);
	    XPutImage(php->display, php->pixels, php->monoGC,
		      im, 0, 0, xx, yy, iw, ih);
	    xx += iw;
	}
	yy += ih;
    }

    /*
     * Don't hang on to our image storage area if it's large.
     */
    if( php->imageSize > MAX_KEEP_IMAGE ){
	ckfree(php->image->data);
	php->image->data = NULL;
	php->imageSize = 0;
    }

    RedrawImage(php, x, y, w, h);
}

/*
 *----------------------------------------------------------------------
 *
 * PhotoPutZoomedBlock --
 *
 *	This procedure is called by clients of a photo widget to
 *	put image data into the widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The image data is stored.  Dithering and/or redisplay of the
 *	image may be done.
 *
 *----------------------------------------------------------------------
 */

void
PhotoPutZoomedBlock(handle, block, x, y, w, h,
		    zoom_x, zoom_y, decimate_x, decimate_y)
    PhotoHandle handle;
    register struct image_block *block;
    int x, y, w, h;
    int zoom_x, zoom_y, decimate_x, decimate_y;
{
    register Photo *php;
    register unsigned long *ldest, mask, word;
    register unsigned char *cdest, *src;
    register ColorTable *ctp;
    register int goff, boff, big_endian;
    XImage *im;
    unsigned long first_bit, p;
    int bw, bh, n, xx, yy;
    int real_bw, real_bh, im_xskip, im_yskip;
    int ww, hh, iw, ih, zx, zy;
    int nb_line, pw3;
    unsigned char *src0, *dst0;
    unsigned char *p24, *q24;

    php = (Photo *) handle;
    ctp = php->colors;
    im = php->image;

    if( zoom_x <= 0 || zoom_y <= 0 )
	return;

    xx = x + w;
    yy = y + h;
    if( (php->flags & USER_IMAGE_GEOM) != 0 ){
	if( xx > php->imageWidth )
	    w = php->imageWidth - x;
	if( yy > php->imageHeight )
	    h = php->imageHeight - y;
	if( w <= 0 || h <= 0 )
	    return;
    } else if( xx > php->pixelWidth || yy > php->pixelHeight ){
	PhotoExpand((PhotoHandle) php, xx, yy);
    }

    if( y < php->ditherY || y == php->ditherY && x < php->ditherX ){
	/* the dithering isn't correct past the start of this block */
	php->ditherX = x;
	php->ditherY = y;
    }

    goff = block->comp_off[1] - block->comp_off[0];
    boff = block->comp_off[2] - block->comp_off[0];

    /*
     * Work out what area the pixel data expands to after
     * decimation and zooming.
     */
    im_xskip = decimate_x * block->pixel_size;
    im_yskip = decimate_y * block->pitch;
    if( decimate_x > 0 )
	real_bw = (block->width + decimate_x - 1) / decimate_x;
    else if( decimate_x == 0 )
	real_bw = w;
    else
	real_bw = (block->width - decimate_x - 1) / -decimate_x;
    if( decimate_y > 0 )
	real_bh = (block->height + decimate_y - 1) / decimate_y;
    else if( decimate_y == 0 )
	real_bh = h;
    else
	real_bh = (block->height - decimate_y - 1) / -decimate_y;
    bw = real_bw * zoom_x;
    if( bw > w )
	bw = w;
    bh = real_bh * zoom_y;
    if( bh > h )
	bh = h;

    /*
     * Allocate image storage if necessary. 
     */
    if( php->ditherLevel != DITHER_BLOCKS )
	AllocImage(php, bw, bh);

    /*
     * Store image data in the original image array if dithering.
     */
    if( php->ditherLevel > DITHER_NONE ){
	/*
	 * Dithering - copy 24-bit data in, converting to monochrome
	 * if necessary; then if required, call dither routine.
	 */
	if( (php->flags & MONOCHROME) == 0 ){
	    pw3 = php->pixelWidth * 3;
	    p24 = php->pix24 + y * pw3 + x * 3;
	} else {
	    pw3 = php->pixelWidth;
	    p24 = php->pix24 + y * pw3 + x;
	}
	for( hh = h; hh > 0; ){
	    ih = MIN(hh, bh);
	    hh -= ih;
	    zy = zoom_y;
	    src0 = block->ptr + block->comp_off[0];
	    for( ; ih > 0; --ih ){
		q24 = p24;
		for( ww = w; ww > 0; ){
		    iw = MIN(bw, ww);
		    ww -= iw;
		    src = src0;
		    for( ; iw > 0; iw -= zoom_x ){
			if( (php->flags & MONOCHROME) != 0 ){
			    p = (src[0]*11 + src[goff]*16 + src[boff]*5) >> 5;
			    for( zx = MIN(iw, zoom_x); zx > 0; --zx )
				*q24++ = p;
			} else {
			    for( zx = MIN(iw, zoom_x); zx > 0; --zx ){
				*q24++ = src[0];
				*q24++ = src[goff];
				*q24++ = src[boff];
			    }
			}
			src += im_xskip;
		    }
		}
		p24 += pw3;
		if( --zy <= 0 ){
		    src0 += im_yskip;
		    zy = zoom_y;
		}
	    }
	}
	if( php->ditherLevel == DITHER_BLOCKS ){
	    Dither(php, x, y, w, h);
	    return;
	}
    }

    /*
     * Quantize image data to pixel values.
     */
    src0 = block->ptr + block->comp_off[0];
    dst0 = (unsigned char *) im->data;
    nb_line = im->bytes_per_line;
    zy = zoom_y;

    if( (php->flags & MONOCHROME) != 0 ){
	if( php->depth == 1 ){
	    /*
	     * Monochrome 1-bit per pixel (not dithering at this stage).
	     */
	    ldest = (unsigned long *) dst0;
	    big_endian = im->bitmap_bit_order == MSBFirst;
	    first_bit = big_endian? (1 << (im->bitmap_unit - 1)): 1;
	    for( yy = 0; yy < bh; ++yy ){
		src = src0;
		mask = first_bit;
		word = 0;
		for( xx = bw; xx > 0; xx -= zoom_x ){
		    p = src[0]*11 + src[goff]*16 + src[boff]*5 > 128*32;
		    for( zx = MIN(xx, zoom_x); zx > 0; --zx ){
			if( mask == 0 ){
			    *ldest++ = word;
			    mask = first_bit;
			    word = 0;
			}
			if( p )
			    word |= mask;
			mask = big_endian? (mask >> 1): (mask << 1);
		    }
		    src += im_xskip;
		}
		*ldest++ = word;
		if( --zy <= 0 ){
		    src0 += im_yskip;
		    zy = zoom_y;
		}
	    }

	} else {
	    /*
	     * Multibit monochrome display.
	     */
	    if( im->bits_per_pixel <= 8 ){
		/* monochrome, 2 to 8 bits/pixel */
		for( yy = 0; yy < bh; ++yy ){
		    src = src0;
		    cdest = dst0;
		    for( xx = bw; xx > 0; xx -= zoom_x ){
			n = (src[0]*11 + src[goff]*16 + src[boff]*5 + 31) >> 5;
			p = ctp->red_values[n];
			for( zx = MIN(xx, zoom_x); zx > 0; --zx )
			    *cdest++ = p;
			src += im_xskip;
		    }
		    if( --zy <= 0 ){
			src0 += im_yskip;
			zy = zoom_y;
		    }
		    dst0 += nb_line;
		}
	    } else {
		/* monochrome, 9 to 32 bits/pixel */
		for( yy = 0; yy < bh; ++yy ){
		    src = src0;
		    ldest = (unsigned long *) dst0;
		    for( xx = bw; xx > 0; xx -= zoom_x ){
			n = (src[0]*11 + src[goff]*16 + src[boff]*5 + 31) >> 5;
			p = ctp->red_values[n];
			for( zx = MIN(xx, zoom_x); zx > 0; --zx )
			    *ldest++ = p;
			src += im_xskip;
		    }
		    if( --zy <= 0 ){
			src0 += im_yskip;
			zy = zoom_y;
		    }
		    dst0 += nb_line;
		}
	    }

	}

    } else if( (php->flags & MAP_COLORS) != 0 ){
	if( im->bits_per_pixel <= 8 ){
	    /*
	     * PseudoColor or StaticColor with <= 8 bits/pixel.
	     */
	    for( yy = 0; yy < bh; ++yy ){
		src = src0;
		cdest = dst0;
		for( xx = bw; xx > 0; xx -= zoom_x ){
		    n = ctp->red_values[src[0]] + ctp->green_values[src[goff]]
			+ ctp->blue_values[src[boff]];
		    p = ctp->pixel_map[n];
		    for( zx = MIN(xx, zoom_x); zx > 0; --zx )
			*cdest++ = p;
		    src += im_xskip;
		}
		if( --zy <= 0 ){
		    src0 += im_yskip;
		    zy = zoom_y;
		}
		dst0 += nb_line;
	    }
	} else {
	    /*
	     * PseudoColor or StaticColor with 9 to 32 bits/pixel.
	     */
	    for( yy = 0; yy < bh; ++yy ){
		src = src0;
		ldest = (unsigned long *) dst0;
		for( xx = bw; xx > 0; xx -= zoom_x ){
		    n = ctp->red_values[src[0]] + ctp->green_values[src[goff]]
			+ ctp->blue_values[src[boff]];
		    p = ctp->pixel_map[n];
		    for( zx = MIN(xx, zoom_x); zx > 0; --zx )
			*ldest++ = p;
		    src += im_xskip;
		}
		if( --zy <= 0 ){
		    src0 += im_yskip;
		    zy = zoom_y;
		}
		dst0 += nb_line;
	    }
	}

    } else {
	if( im->bits_per_pixel <= 8 ){
	    /*
	     * DirectColor or TrueColor display with <= 8 bits/pixel.
	     */
	    for( yy = 0; yy < bh; ++yy ){
		src = src0;
		cdest = dst0;
		for( xx = bw; xx > 0; xx -= zoom_x ){
		    p = ctp->red_values[src[0]]
			+ ctp->green_values[src[goff]]
			+ ctp->blue_values[src[boff]];
		    for( zx = MIN(xx, zoom_x); zx > 0; --zx )
			*cdest++ = p;
		    src += im_xskip;
		}
		if( --zy <= 0 ){
		    src0 += im_yskip;
		    zy = zoom_y;
		}
		dst0 += nb_line;
	    }
	} else {
	    /*
	     * DirectColor or TrueColor display with 9 to 32 bits/pixel.
	     */
	    for( yy = 0; yy < bh; ++yy ){
		src = src0;
		ldest = (unsigned long *) dst0;
		for( xx = bw; xx > 0; xx -= zoom_x ){
		    p = ctp->red_values[src[0]]
			+ ctp->green_values[src[goff]]
			+ ctp->blue_values[src[boff]];
		    for( zx = MIN(xx, zoom_x); zx > 0; --zx )
			*ldest++ = p;
		    src += im_xskip;
		}
		if( --zy <= 0 ){
		    src0 += im_yskip;
		    zy = zoom_y;
		}
		dst0 += nb_line;
	    }
	}
    }

    yy = y;
    for( hh = h; hh > 0; hh -= ih ){
	ih = MIN(hh, bh);
	xx = x;
	for( ww = w; ww > 0; ww -= iw ){
	    iw = MIN(bw, ww);
	    XPutImage(php->display, php->pixels, php->monoGC,
		      im, 0, 0, xx, yy, iw, ih);
	    xx += iw;
	}
	yy += ih;
    }

    /*
     * Don't hang on to our image storage area if it's large.
     */
    if( php->imageSize > MAX_KEEP_IMAGE ){
	ckfree(php->image->data);
	php->image->data = NULL;
	php->imageSize = 0;
    }

    RedrawImage(php, x, y, w, h);
}

/*
 *----------------------------------------------------------------------
 *
 * Dither --
 *
 *	This procedure is called to dither an area of the image.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The photo's pixmap is updated in the specified region.
 *	The photo's window may get updated.
 *
 *----------------------------------------------------------------------
 */

static void
Dither(php, x, y, w, h)
    Photo *php;
    int x, y, w, h;
{
    ColorTable *ctp;
    XImage *im;
    int n, nb_line, ll;
    register int i, c, big_endian;
    int xx, yy, col[3];
    register unsigned char *cdest, *src;
    register schar *err;
    register unsigned long *ldest, mask, word;
    unsigned char *src0, *dst0;
    schar *err0;
    unsigned long first_bit;
    int nlines;

    if( w <= 0 || h <= 0 )
	return;
    ctp = php->colors;
    im = php->image;

    /*
     * Work out if this stuff will be correctly dithered and if it
     * will extend the correctly dithered region.
     */
    if( (y < php->ditherY || y == php->ditherY && x <= php->ditherX)
       && y + h > php->ditherY ){
	/* This block starts inside (or immediately after) the correctly
	   dithered region, so the first scan line at least will be right.
	   Furthermore this block extends into scanline php->ditherY. */
	if( x == 0 && w == php->pixelWidth ){
	    /* doing the full width => correct dithering to the end */
	    php->ditherX = 0;
	    php->ditherY = y + h;
	} else {
	    /* doing partial scanlines => extend correct region by
	       at most one scan line */
	    if( x <= php->ditherX ){
		if( (php->ditherX = x + w) >= php->pixelWidth ){
		    php->ditherX = 0;
		    ++php->ditherY;
		}
	    }
	}
    }

    /*
     * First work out how many lines to do at a time,
     * then how many bytes we'll need for pixel storage,
     * and allocate more if necessary.
     */
    nlines = (php->drawInterval + w - 1) / w;
    if( nlines < 1 )
	nlines = 1;
    if( nlines > h )
	nlines = h;
    AllocImage(php, w, nlines);
    nb_line = im->bytes_per_line;

    if( (php->flags & MONOCHROME) == 0 ){
	ll = php->pixelWidth * 3;
	src0 = php->pix24 + y * ll + x * 3;
	err0 = php->error + y * ll + x * 3;
    } else {
	ll = php->pixelWidth;
	src0 = php->pix24 + y * ll + x;
	err0 = php->error + y * ll + x;
    }

    for( ; h > 0; h -= nlines, y += nlines ){
	if( nlines > h )
	    nlines = h;
	dst0 = (unsigned char *) im->data;
	if( php->depth == 1 ){
	    /* set up for 1-bit/pixel monochrome */
	    big_endian = im->bitmap_bit_order == MSBFirst;
	    first_bit = big_endian? (1 << (im->bitmap_unit - 1)): 1;
	}
	for( yy = y; yy < y + nlines; ++yy ){
	    src = src0;
	    err = err0;
	    cdest = dst0;
	    ldest = (unsigned long *) dst0;
	    if( (php->flags & MONOCHROME) == 0 ){
		/* color display */
		for( xx = x; xx < x + w; ++xx ){
		    for( i = 0; i < 3; ++i ){
			c = xx > 0? err[-3] * 7: 0;
			if( yy > 0 ){
			    if( xx > 0 )
				c += err[-ll-3];
			    c += err[-ll] * 5;
			    if( xx + 1 < php->pixelWidth )
				c += err[-ll+3] * 3;
			}
			c = ((c + 8) >> 4) + *src++;
			if( c < 0 )
			    c = 0;
			else if( c > 255 )
			    c = 255;
			col[i] = ctp->color_quant[i][c];
			*err++ = c - col[i];
		    }
		    i = ctp->red_values[col[0]] + ctp->green_values[col[1]]
			+ ctp->blue_values[col[2]];
		    if( (php->flags & MAP_COLORS) != 0 )
			i = ctp->pixel_map[i];
		    if( im->bits_per_pixel <= 8 )
			*cdest++ = i;
		    else
			*ldest++ = i;
		}

	    } else if( im->bits_per_pixel > 1 ){
		/* multibit monochrome */
		for( xx = x; xx < x + w; ++xx ){
		    c = xx > 0? err[-1] * 7: 0;
		    if( yy > 0 ){
			if( xx > 0 )
			    c += err[-ll-1];
			c += err[-ll] * 5;
			if( xx + 1 < php->pixelWidth )
			    c += err[-ll+1] * 3;
		    }
		    c = ((c + 8) >> 4) + *src++;
		    if( c < 0 )
			c = 0;
		    else if( c > 255 )
			c = 255;
		    i = ctp->color_quant[0][c];
		    *err++ = c - i;
		    if( im->bits_per_pixel <= 8 )
			*cdest++ = ctp->red_values[i];
		    else
			*ldest++ = ctp->red_values[i];
		}

	    } else {
		/* 1-bit monochrome */
		word = 0;
		mask = first_bit;
		for( xx = x; xx < x + w; ++xx ){
		    if( mask == 0 ){
			*ldest++ = word;
			mask = first_bit;
			word = 0;
		    }
		    c = xx > 0? err[-1] * 7: 0;
		    if( yy > 0 ){
			if( xx > 0 )
			    c += err[-ll-1];
			c += err[-ll] * 5;
			if( xx + 1 < php->pixelWidth )
			    c += err[-ll+1] * 3;
		    }
		    c = ((c + 8) >> 4) + *src++;
		    if( c < 0 )
			c = 0;
		    else if( c > 255 )
			c = 255;
		    if( c >= 128 ){
			word |= mask;
			*err++ = c - 255;
		    } else
			*err++ = c;
		    mask = big_endian? (mask >> 1): (mask << 1);
		}
		*ldest = word;
	    }
	    src0 += ll;
	    err0 += ll;
	    dst0 += nb_line;
	}
	
	XPutImage(php->display, php->pixels, php->monoGC, im,
		  0, 0, x, y, w, nlines);
	
	RedrawImage(php, x, y, w, nlines);
    }

    /*
     * Don't hang on to our image storage area if it's large.
     */
    if( php->imageSize > MAX_KEEP_IMAGE ){
	ckfree(php->image->data);
	php->image->data = NULL;
	php->imageSize = 0;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * PhotoBlank --
 *
 *	This procedure is called by clients of the photo widget to
 *	clear the entire image to the blank color.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The photo's pixmap and window are cleared.
 *
 *----------------------------------------------------------------------
 */

void
PhotoBlank(handle)
    PhotoHandle handle;
{
    Photo *php;
    int n;

    php = (Photo *) handle;
    XFillRectangle(php->display, php->pixels, php->blankGC,
		   0, 0, php->pixelWidth, php->pixelHeight);
    if( php->ditherLevel > DITHER_NONE ){
	n = php->pixelWidth * php->pixelHeight;
	if( (php->flags & MONOCHROME) == 0 )
	    n *= 3;
	bzero(php->pix24, n);
	bzero(php->error, n);
	php->ditherX = php->ditherY = 0;
    }
    RedrawImage(php, 0, 0, php->pixelWidth, php->pixelHeight);
}

/*
 *----------------------------------------------------------------------
 *
 * PhotoExpand --
 *
 *	This procedure is called by clients of the photo widget to
 *	request that the image be expanded if necessary to be at least
 *	`width' pixels wide and `height' pixels high.  If the user
 *	has declared a definite image size (using the -imagesize
 *	configuration option) then this call has no effect.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The size of the photo's pixmap and window may change.
 *
 *----------------------------------------------------------------------
 */

void
PhotoExpand(handle, width, height)
    PhotoHandle handle;
    int width, height;
{
    Photo *php;

    php = (Photo *) handle;

    if( width <= 0 || height <= 0 || (php->flags & USER_IMAGE_GEOM) != 0 )
	return;

    if( php->imageWidth < width )
	php->imageWidth = width;
    if( php->imageHeight < height )
	php->imageHeight = height;
    if( (php->flags & USER_GEOMETRY) == 0 ){
	php->reqWidth = php->imageWidth;
	php->reqHeight = php->imageHeight;
    }

    ComputePhotoGeometry(php);
}

/*
 *----------------------------------------------------------------------
 *
 * PhotoGetSize --
 *
 *	This procedure is called by clients of the photo widget to
 *	obtain the current size of the image.
 *
 * Results:
 *	The photo image's width and height are returned in *widthp
 *	and *heightp.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
PhotoGetSize(handle, widthp, heightp)
    PhotoHandle handle;
    int *widthp, *heightp;
{
    Photo *php;

    php = (Photo *) handle;
    if( (php->flags & USER_IMAGE_GEOM) != 0 ){
	*widthp = php->imageWidth;
	*heightp = php->imageHeight;
    } else {
	*widthp = php->pixelWidth;
	*heightp = php->pixelHeight;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * PhotoSetSize --
 *
 *	This procedure is called by clients of the photo widget to
 *	set the image size for the photo.  This call is equivalent
 *	to using the -imagesize configuration option.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The photo's pixmap and/or window may change size.
 *
 *----------------------------------------------------------------------
 */

void
PhotoSetSize(handle, w, h)
    PhotoHandle handle;
    int w, h;
{
    Photo *php;

    php = (Photo *) handle;

    if( w == 0 && h == 0 )
	php->flags &= ~USER_IMAGE_GEOM;
    else if( w > 0 && h > 0 ){
	php->imageWidth = w;
	php->imageHeight = h;
	php->flags |= USER_IMAGE_GEOM;
	if( (php->flags & USER_GEOMETRY) == 0 ){
	    php->reqWidth = w;
	    php->reqHeight = h;
	}
    }

    ComputePhotoGeometry(php);
}

/*
 *----------------------------------------------------------------------
 *
 * PhotoGetImage --
 *
 *	This procedure is called by clients of the photo widget to
 *	obtain image data from the photo widget.  Image data is only
 *	available if the photo's dither-level is not zero.  If the
 *	image data is available, this procedure fills in the PhotoImage
 *	structure pointed to by `block' with details of the address
 *	and layout of the image data in memory.
 *
 * Results:
 *	TRUE (1) if image data is available; FALSE (0) if it is not.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
PhotoGetImage(handle, block)
    PhotoHandle handle;
    PhotoImage *block;
{
    Photo *php;

    php = (Photo *) handle;
    block->ptr = php->pix24;
    block->width = php->pixelWidth;
    block->height = php->pixelHeight;
    if( (php->flags & MONOCHROME) == 0 ){
	block->pitch = php->pixelWidth * 3;
	block->pixel_size = 3;
	block->comp_off[0] = 0;
	block->comp_off[1] = 1;
	block->comp_off[2] = 2;
    } else {
	block->pitch = php->pixelWidth;
	block->pixel_size = 1;
	block->comp_off[0] = 0;
	block->comp_off[1] = 0;
	block->comp_off[2] = 0;
    }
    return block->ptr != NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * PhotoGetInfo --
 *
 *	This procedure returns information useful to clients who wish
 *	to use PhotoPutXImage, namely:
 *	- the tkwin handle for the photo widget's window
 *	- information about the visual for the window
 *	- the X colormap for the window
 *
 * Results:
 *	See above.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
PhotoGetInfo(handle, pTkwin, pVisInfo, pCmap)
    PhotoHandle handle;
    Tk_Window *pTkwin;
    XVisualInfo *pVisInfo;
    Colormap *pCmap;
{
    Photo *php;

    php = (Photo *) handle;
    *pTkwin = php->tkwin;
    *pVisInfo = php->vis_info;
    *pCmap = php->cmap;
}

/*
 *----------------------------------------------------------------------
 *
 * PhotoPutXImage --
 *
 *	This procedure is used by clients who have image data already
 *	in the correct form for use in an XPutImage call to the photo
 *	widget's window.  The image data supplied is written to the
 *	photo's backing pixmap and copied to the photo's window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The contents of the photo's window are modified.
 *
 *----------------------------------------------------------------------
 */

void
PhotoPutXImage(handle, gc, image, src_x, src_y, dest_x, dest_y,
	       width, height)
    PhotoHandle handle;		/* photo to be updated */
    GC gc;			/* GC for the XPutImage operation */
    XImage *image;		/* source image data */
    int src_x, src_y;		/* top-left coords in source image */
    int dest_x, dest_y;		/* top-left coords in photo image */
    int width, height;		/* size of rectangle to be copied */
{
    int xx, yy;
    Photo *php;

    php = (Photo *) handle;

    xx = dest_x + width;
    yy = dest_y + height;
    if( (php->flags & USER_IMAGE_GEOM) != 0 ){
	if( xx > php->imageWidth )
	    width = php->imageWidth - dest_x;
	if( yy > php->imageHeight )
	    height = php->imageHeight - dest_y;
	if( width <= 0 || height <= 0 )
	    return;
    } else if( xx > php->pixelWidth || yy > php->pixelHeight ){
	PhotoExpand((PhotoHandle) php, xx, yy);
    }

    XPutImage(php->display, php->pixels, gc, image, src_x, src_y,
	      dest_x, dest_y, width, height);

    RedrawImage(php, dest_x, dest_y, width, height);
}

/*
 *--------------------------------------------------------------
 *
 * CreatePhotoItem --
 *
 *	This procedure is invoked to create a new photo item in
 *	a canvas.
 *
 * Results:
 *	A standard Tcl return value.  If an error occurred in
 *	creating the item, then an error message is left in
 *	canvasPtr->interp->result;  in this case itemPtr is
 *	left uninitialized, so it can be safely freed by the
 *	caller.
 *
 * Side effects:
 *	A new photo item is created.
 *
 *--------------------------------------------------------------
 */

static int
CreatePhotoItem(canvPtr, itemPtr, argc, argv)
    register Tk_Canvas *canvPtr;	/* Canvas to hold new item. */
    Tk_Item *itemPtr;			/* Record to hold new item;  header
					 * has been initialized by caller. */
    int argc;				/* Number of arguments in argv. */
    char **argv;			/* Arguments describing photo item. */
{
    register PhotoItem *photoItemPtr = (PhotoItem *) itemPtr;
    register Photo *php;
    char *cname, *pref, suff[24];

    if (argc < 4) {
	Tcl_AppendResult(canvPtr->interp, "wrong # args:  should be \"",
		Tk_PathName(canvPtr->tkwin),
		" create x1 y1 x2 y2 ?options?\"",
		(char *) NULL);
	return TCL_ERROR;
    }

    /*
     * Form the name which can be passed to FindPhoto to get
     * the PhotoHandle for this photo item.
     */

    sprintf(suff, "-%d", itemPtr->id);
    pref = Tk_PathName(canvPtr->tkwin);
    cname = ckalloc(strlen(pref) + strlen(suff) + 1);
    strcpy(cname, pref);
    strcat(cname, suff);
    photoItemPtr->name = cname;

    /*
     * Carry out initialization that is needed in order to clean
     * up after errors during the the remainder of this procedure.
     */
    php = &photoItemPtr->photo;
    bzero(php, sizeof(Photo));

    /*
     * Initialize the photo record and associated data structures.
     */

    php->flags = MUST_DFLT_CMAP | IS_PHOTO_ITEM;
    php->interp = canvPtr->interp;
    php->tkwin = canvPtr->tkwin;
    if( SetupPhoto(php, cname, "default") != TCL_OK ){
	DeletePhotoItem(canvPtr, itemPtr);
	return TCL_ERROR;
    }

    /*
     * Parse the canvas coords defining the extent of the photo item,
     * and process the configuration options given.
     */

    if( ConfigurePhotoItem(canvPtr, itemPtr, argc-4, argv+4, 0) != TCL_OK
       || PhotoItemCoords(canvPtr, itemPtr, 4, argv) != TCL_OK ){
	DeletePhotoItem(canvPtr, itemPtr);
	return TCL_ERROR;
    }

    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * PhotoItemCoords --
 *
 *	This procedure is invoked to process the "coords" widget
 *	command on PhotoItems.  See the user documentation for details
 *	on what it does.
 *
 * Results:
 *	Returns TCL_OK or TCL_ERROR, and sets canvasPtr->interp->result.
 *
 * Side effects:
 *	The coordinates for the given item may be changed.
 *
 *--------------------------------------------------------------
 */

static int
PhotoItemCoords(canvasPtr, itemPtr, argc, argv)
    register Tk_Canvas *canvasPtr;	/* Canvas containing item. */
    Tk_Item *itemPtr;			/* Item whose coordinates are to be
					 * read or modified. */
    int argc;				/* Number of coordinates supplied in
					 * argv. */
    char **argv;			/* Array of coordinates: x1, y1,
					 * x2, y2. */
{
    register PhotoItem *pItemPtr = (PhotoItem *) itemPtr;
    double t;
    char buffer[128];

    if( argc == 0 ){
	sprintf(buffer, "%g %g %g %g", pItemPtr->x1, pItemPtr->y1,
		pItemPtr->x2, pItemPtr->y2);
	Tcl_AppendResult(canvasPtr->interp, buffer, (char *) NULL);

    } else if( argc == 4 ){
	if (TkGetCanvasCoord(canvasPtr, argv[0], &pItemPtr->x1) != TCL_OK
	    || TkGetCanvasCoord(canvasPtr, argv[1], &pItemPtr->y1) != TCL_OK
	    || TkGetCanvasCoord(canvasPtr, argv[2], &pItemPtr->x2) != TCL_OK
	    || TkGetCanvasCoord(canvasPtr, argv[3], &pItemPtr->y2) != TCL_OK)
	    return TCL_ERROR;

	/*
	 * Make sure our PhotoItem is the right way around.
	 */
	if( pItemPtr->x1 > pItemPtr->x2 ){
	    t = pItemPtr->x1;
	    pItemPtr->x1 = pItemPtr->x2;
	    pItemPtr->x2 = t;
	}
	if( pItemPtr->y1 > pItemPtr->y2 ){
	    t = pItemPtr->y1;
	    pItemPtr->y1 = pItemPtr->y2;
	    pItemPtr->y2 = t;
	}

	ComputePhotoItemBbox(canvasPtr, pItemPtr);

    } else {
	Tcl_AppendResult(canvasPtr->interp, "wrong number of coordinates",
			 " for PhotoItem: must have 4", (char *) NULL);
	return TCL_ERROR;
    }

    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * ConfigurePhotoItem --
 *
 *	This procedure is invoked to configure various aspects
 *	of a PhotoItem item such as the at, view, up vectors, etc.
 *
 * Results:
 *	A standard Tcl result code.  If an error occurs, then
 *	an error message is left in canvasPtr->interp->result.
 *
 * Side effects:
 *	Configuration information, such as the view
 *	transformation, may be set for itemPtr.
 *
 *--------------------------------------------------------------
 */

static int
ConfigurePhotoItem(canvasPtr, itemPtr, argc, argv, flags)
    Tk_Canvas *canvasPtr;	/* Canvas containing itemPtr. */
    Tk_Item *itemPtr;		/* PhotoItem item to reconfigure. */
    int argc;			/* Number of elements in argv.  */
    char **argv;		/* Arguments describing things to configure. */
    int flags;			/* Flags to pass to Tk_ConfigureWidget. */
{
    register PhotoItem *photoItemPtr = (PhotoItem *) itemPtr;

    return ConfigurePhoto(canvasPtr->interp, &photoItemPtr->photo,
			  (char *) photoItemPtr,
			  argc, argv, itemConfigSpecs, flags);
}

/*
 *--------------------------------------------------------------
 *
 * DeletePhotoItem --
 *
 *	This procedure is called to clean up the data structure
 *	associated with a PhotoItem item.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Resources associated with itemPtr are released.
 *
 *--------------------------------------------------------------
 */

static void
DeletePhotoItem(canvasPtr, itemPtr)
    Tk_Canvas *canvasPtr;		/* Info about overall canvas widget. */
    Tk_Item *itemPtr;			/* Item that is being deleted. */
{
    register PhotoItem *photoItemPtr = (PhotoItem *) itemPtr;

    DestroyPhoto((ClientData) &photoItemPtr->photo);
    if( photoItemPtr->name != NULL ){
	/* XXX should delete photoHash entry here */
	free(photoItemPtr->name);
    }
}

/*
 *--------------------------------------------------------------
 *
 * ComputePhotoItemBbox --
 *
 *	This procedure is invoked to compute the bounding box of
 *	all the pixels that may be drawn as part of a PhotoItem.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The fields x1, y1, x2, and y2 are updated in the header
 *	for itemPtr.
 *
 *--------------------------------------------------------------
 */

static void
ComputePhotoItemBbox(canvasPtr, photoItemPtr)
    register Tk_Canvas *canvasPtr;	/* Canvas that contains item. */
    PhotoItem *photoItemPtr;		/* Item whose bbox is to be updated */
{
    register Photo *php;

    photoItemPtr->header.x1 = (int) photoItemPtr->x1;
    photoItemPtr->header.x2 = (int) photoItemPtr->x2 + 1;
    photoItemPtr->header.y1 = (int) photoItemPtr->y1;
    photoItemPtr->header.y2 = (int) photoItemPtr->y2 + 1;

    php = &photoItemPtr->photo;

    php->width = php->reqWidth = photoItemPtr->x2 - photoItemPtr->x1 + 1;
    php->height = php->reqHeight = photoItemPtr->y2 - photoItemPtr->y1 + 1;
    php->xorg = photoItemPtr->x1;
    php->yorg = photoItemPtr->y1;
    php->flags |= USER_GEOMETRY;

    ComputePhotoGeometry(php);

}

/*
 *--------------------------------------------------------------
 *
 * DisplayPhotoItem --
 *
 *	This procedure is invoked to draw a PhotoItem item in a given
 *	drawable.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The photo item is redrawn.
 *
 *--------------------------------------------------------------
 */

static void
DisplayPhotoItem(canvasPtr, itemPtr, drawable)
    register Tk_Canvas *canvasPtr;	/* Canvas that contains item. */
    Tk_Item *itemPtr;			/* Item to be displayed. */
    Drawable drawable;			/* Pixmap or window in which to draw
					 * item. */
{
    register PhotoItem *photoItemPtr = (PhotoItem *) itemPtr;
    register Photo *php;
    int x, y, px, py;

    php = &photoItemPtr->photo;

    /*
     * Re-dither the image if necessary.
     */
    if( php->ditherLevel > DITHER_NONE ){
	if( php->ditherX != 0 )
	    Dither(php, php->ditherX, php->ditherY,
		   php->pixelWidth - php->ditherX, 1);
	if( php->ditherY < php->pixelHeight )
	    Dither(php, 0, php->ditherY, php->pixelWidth,
		   php->pixelHeight - php->ditherY);
    }

    /*
     * Subtract (drawableXOrigin, drawableYOrigin) from the coordinates
     * to allow for the window being scrolled or for only a part of the
     * window being redrawn.
     */
    x = php->xorg - canvasPtr->drawableXOrigin;
    y = php->yorg - canvasPtr->drawableYOrigin;
    px = 0;			/* origin within photo image */
    py = 0;
    if( x < 0 ){
	px = -x;
	x = 0;
    }
    if( y < 0 ){
	py = -y;
	y = 0;
    }
    if( px >= php->width || py >= php->height )
	return;

    XCopyArea(php->display, php->pixels, drawable, php->blankGC,
	      php->xshift + px, php->yshift + py,
	      php->width - px, php->height - py, x, y);
}

/*
 *--------------------------------------------------------------
 *
 * PhotoItemToPoint --
 *
 *	Computes the distance from a given point to a given
 *	PhotoItem, in canvas units.
 *
 * Results:
 *	The return value is 0 if the point whose x and y coordinates
 *	are pointPtr[0] and pointPtr[1] is inside the PhotoItem.
 *	If the point isn't inside the PhotoItem then the return value
 *	is the distance from the point to the PhotoItem.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

	/* ARGSUSED */
static double
PhotoItemToPoint(canvasPtr, itemPtr, pointPtr)
    Tk_Canvas *canvasPtr;	/* Canvas containing item. */
    Tk_Item *itemPtr;		/* Item to check against point. */
    double *pointPtr;		/* Pointer to coordinates of point. */
{
    PhotoItem *photoItemPtr = (PhotoItem *) itemPtr;
    double dx, dy;

    if( pointPtr[0] < photoItemPtr->x1 )
	dx = photoItemPtr->x1 - pointPtr[0];
    else if( pointPtr[0] < photoItemPtr->x2 )
	dx = 0;
    else
	dx = pointPtr[0] - photoItemPtr->x2;

    if( pointPtr[1] < photoItemPtr->y1 )
	dy = photoItemPtr->y1 - pointPtr[1];
    else if( pointPtr[1] < photoItemPtr->y2 )
	dy = 0;
    else
	dy = pointPtr[1] - photoItemPtr->y2;

    return hypot(dx, dy);
}

/*
 *--------------------------------------------------------------
 *
 * PhotoItemToArea --
 *
 *	This procedure is called to determine whether an item
 *	lies entirely inside, entirely outside, or overlapping
 *	a given rectangular area.
 *
 * Results:
 *	-1 is returned if the item is entirely outside the
 *	area, 0 if it overlaps, and 1 if it is entirely
 *	insde the given area.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

	/* ARGSUSED */
static int
PhotoItemToArea(canvasPtr, itemPtr, rectPtr)
    Tk_Canvas *canvasPtr;	/* Canvas containing item. */
    Tk_Item *itemPtr;		/* Item to check against PhotoItem. */
    double *rectPtr;		/* Pointer to array of four coordinates
				 * (x1, y1, x2, y2) describing rectangular
				 * area.  */
{
    PhotoItem *photoItemPtr = (PhotoItem *) itemPtr;

    if( rectPtr[0] >= photoItemPtr->x2 || rectPtr[2] < photoItemPtr->x1
       || rectPtr[1] >= photoItemPtr->y2 || rectPtr[3] < photoItemPtr->y1 )
	return -1;		/* PhotoItem is entirely outside */

    if( rectPtr[0] <= photoItemPtr->x1 && rectPtr[2] >= photoItemPtr->x2
       && rectPtr[1] <= photoItemPtr->y1 && rectPtr[3] >= photoItemPtr->y2 )
	return 1;		/* entirely inside */

    return 0;
}

/*
 *--------------------------------------------------------------
 *
 * ScalePhotoItem --
 *
 *	This procedure is invoked to rescale a PhotoItem item.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The PhotoItem referred to by itemPtr is rescaled so that the
 *	following transformation is applied to all point
 *	coordinates:
 *		x' = originX + scaleX*(x-originX)
 *		y' = originY + scaleY*(y-originY)
 *
 *--------------------------------------------------------------
 */

static void
ScalePhotoItem(canvasPtr, itemPtr, originX, originY, scaleX, scaleY)
    Tk_Canvas *canvasPtr;		/* Canvas containing PhotoItem. */
    Tk_Item *itemPtr;			/* PhotoItem to be scaled. */
    double originX, originY;		/* Origin about which to scale rect. */
    double scaleX;			/* Amount to scale in X direction. */
    double scaleY;			/* Amount to scale in Y direction. */
{
    PhotoItem *photoItemPtr = (PhotoItem *) itemPtr;

    photoItemPtr->x1 = originX + scaleX * (photoItemPtr->x1 - originX);
    photoItemPtr->y1 = originY + scaleY * (photoItemPtr->y1 - originY);
    photoItemPtr->x2 = originX + scaleX * (photoItemPtr->x2 - originX);
    photoItemPtr->y2 = originY + scaleY * (photoItemPtr->y2 - originY);

    ComputePhotoItemBbox(canvasPtr, photoItemPtr);
}

/*
 *--------------------------------------------------------------
 *
 * TranslatePhotoItem --
 *
 *	This procedure is called to move a PhotoItem by a given
 *	amount.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The position of the PhotoItem is offset by (xDelta, yDelta),
 *	and the bounding box is updated in the generic part of the
 *	item structure.
 *
 *--------------------------------------------------------------
 */

static void
TranslatePhotoItem(canvasPtr, itemPtr, deltaX, deltaY)
    Tk_Canvas *canvasPtr;		/* Canvas containing item. */
    Tk_Item *itemPtr;			/* Item that is being moved. */
    double deltaX, deltaY;		/* Amount by which item is to be
					 * moved. */
{
    PhotoItem *photoItemPtr = (PhotoItem *) itemPtr;

    photoItemPtr->x1 += deltaX;
    photoItemPtr->y1 += deltaY;
    photoItemPtr->x2 += deltaX;
    photoItemPtr->y2 += deltaY;

    ComputePhotoItemBbox(canvasPtr, photoItemPtr);
}
